From 7583ecded470b93bc6423ca3355698f80a2b75cd Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Fri, 31 Jan 2025 14:00:41 +0100 Subject: [PATCH] refactor(schema): simplify handling (#33963) --- lib/modules/manager/argocd/extract.ts | 13 ++++++------- lib/modules/manager/argocd/schema.ts | 6 +++++- lib/util/schema-utils.spec.ts | 21 +++++++++++++++++++++ lib/util/schema-utils.ts | 22 +++++++++++++++++++++- lib/util/yaml.ts | 4 ++-- 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts index 520415366891b0..4ff2de9418bbf6 100644 --- a/lib/modules/manager/argocd/extract.ts +++ b/lib/modules/manager/argocd/extract.ts @@ -2,8 +2,8 @@ import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { coerceArray } from '../../../util/array'; import { regEx } from '../../../util/regex'; +import { withDebugMessage } from '../../../util/schema-utils'; import { trimTrailingSlash } from '../../../util/url'; -import { parseYaml } from '../../../util/yaml'; import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; import { HelmDatasource } from '../../datasource/helm'; @@ -15,7 +15,8 @@ import type { PackageFileContent, } from '../types'; import { - ApplicationDefinition, + type ApplicationDefinition, + ApplicationDefinitionSchema, type ApplicationSource, type ApplicationSpec, } from './schema'; @@ -36,11 +37,9 @@ export function extractPackageFile( return null; } - const definitions = parseYaml(content, { - customSchema: ApplicationDefinition, - failureBehaviour: 'filter', - removeTemplates: true, - }); + const definitions = ApplicationDefinitionSchema.catch( + withDebugMessage([], `error parsing ${packageFile}`), + ).parse(content); const deps = definitions.flatMap(processAppSpec); diff --git a/lib/modules/manager/argocd/schema.ts b/lib/modules/manager/argocd/schema.ts index 4fb8e636829282..395f7f9a9d6ec2 100644 --- a/lib/modules/manager/argocd/schema.ts +++ b/lib/modules/manager/argocd/schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { LooseArray } from '../../../util/schema-utils'; +import { LooseArray, multidocYaml } from '../../../util/schema-utils'; export const KubernetesResource = z.object({ apiVersion: z.string(), @@ -38,3 +38,7 @@ export const ApplicationSet = KubernetesResource.extend({ export const ApplicationDefinition = Application.or(ApplicationSet); export type ApplicationDefinition = z.infer; + +export const ApplicationDefinitionSchema = multidocYaml({ + removeTemplates: true, +}).pipe(LooseArray(ApplicationDefinition)); diff --git a/lib/util/schema-utils.spec.ts b/lib/util/schema-utils.spec.ts index 4614b7b80c86ab..7561b17287c81f 100644 --- a/lib/util/schema-utils.spec.ts +++ b/lib/util/schema-utils.spec.ts @@ -11,6 +11,7 @@ import { Toml, UtcDate, Yaml, + multidocYaml, withDebugMessage, withTraceMessage, } from './schema-utils'; @@ -447,6 +448,26 @@ describe('util/schema-utils', () => { }); }); + describe('multidocYaml()', () => { + const Schema = multidocYaml().pipe( + z.array( + z.object({ + foo: z.number(), + }), + ), + ); + + it('parses valid yaml', () => { + expect( + Schema.parse(codeBlock` + foo: 111 + --- + foo: 222 + `), + ).toEqual([{ foo: 111 }, { foo: 222 }]); + }); + }); + describe('Toml', () => { const Schema = Toml.pipe( z.object({ foo: z.object({ bar: z.literal('baz') }) }), diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index b9bc249d5066d3..4143400106c921 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -2,10 +2,17 @@ import JSON5 from 'json5'; import * as JSONC from 'jsonc-parser'; import { DateTime } from 'luxon'; import type { JsonArray, JsonValue } from 'type-fest'; -import { type ZodEffects, type ZodType, type ZodTypeDef, z } from 'zod'; +import { + type ZodEffects, + type ZodString, + type ZodType, + type ZodTypeDef, + z, +} from 'zod'; import { logger } from '../logger'; import type { PackageDependency } from '../modules/manager/types'; import { parse as parseToml } from './toml'; +import type { YamlOptions } from './yaml'; import { parseSingleYaml, parseYaml } from './yaml'; interface ErrorContext { @@ -257,6 +264,19 @@ export const MultidocYaml = z.string().transform((str, ctx): JsonArray => { } }); +export function multidocYaml( + opts?: Omit, +): ZodEffects { + return z.string().transform((str, ctx): JsonArray => { + try { + return parseYaml(str, opts) as JsonArray; + } catch { + ctx.addIssue({ code: 'custom', message: 'Invalid YAML' }); + return z.NEVER; + } + }); +} + export const Toml = z.string().transform((str, ctx) => { try { return parseToml(str); diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts index c0de0ede60feda..ff78f31db54246 100644 --- a/lib/util/yaml.ts +++ b/lib/util/yaml.ts @@ -11,7 +11,7 @@ import type { ZodType } from 'zod'; import { logger } from '../logger'; import { regEx } from './regex'; -interface YamlOptions< +export interface YamlOptions< ResT = unknown, Schema extends ZodType = ZodType, > extends ParseOptions, @@ -28,7 +28,7 @@ interface YamlParseDocumentOptions removeTemplates?: boolean; } -interface YamlOptionsMultiple< +export interface YamlOptionsMultiple< ResT = unknown, Schema extends ZodType = ZodType, > extends YamlOptions {