diff --git a/lib/modules/manager/argocd/__fixtures__/malformedApplications.yml b/lib/modules/manager/argocd/__fixtures__/malformedApplications.yml index 574b683230cc89..98e9465c74c0d7 100644 --- a/lib/modules/manager/argocd/__fixtures__/malformedApplications.yml +++ b/lib/modules/manager/argocd/__fixtures__/malformedApplications.yml @@ -9,3 +9,15 @@ spec: # malformed application as the source section is missing apiVersion: argoproj.io/v1alpha1 kind: Application +--- +# malformed application as the sources array is empty +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + sources: [] +--- +# malformed application as the source is null +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + source: null diff --git a/lib/modules/manager/argocd/__fixtures__/validApplication.yml b/lib/modules/manager/argocd/__fixtures__/validApplication.yml index e06d78e6589295..83f87c47ebfc0a 100644 --- a/lib/modules/manager/argocd/__fixtures__/validApplication.yml +++ b/lib/modules/manager/argocd/__fixtures__/validApplication.yml @@ -75,3 +75,55 @@ spec: chart: some/image3 repoURL: somecontainer.registry.io:443/ targetRevision: 1.0.0 +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + sources: + - chart: some/image3 + repoURL: somecontainer.registry.io:443/ + targetRevision: 1.0.0 +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + sources: + - ref: foo + repoURL: https://git.example.com/foo/bar.git + targetRevision: v1.2.0 + - chart: some/image3 + repoURL: somecontainer.registry.io:443/ + targetRevision: 1.0.0 + +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + sources: + - ref: foo + repoURL: https://git.example.com/foo/bar.git + targetRevision: v1.2.0 + path: bar + - chart: traefik + repoURL: gs://helm-charts-internal + targetRevision: 0.0.2 + helm: + valueFiles: + - $foo/values.yaml + +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +spec: + sources: + - ref: foo + repoURL: https://git.example.com/foo/bar.git + targetRevision: v1.2.0 + path: bar + - chart: somechart + repoURL: https://foo.io/repo + targetRevision: 0.0.2 + helm: + valueFiles: + - $foo/values.yaml + diff --git a/lib/modules/manager/argocd/__fixtures__/validApplicationSet.yml b/lib/modules/manager/argocd/__fixtures__/validApplicationSet.yml index e17a7e7bdc7fc7..4207c92ddffbf7 100644 --- a/lib/modules/manager/argocd/__fixtures__/validApplicationSet.yml +++ b/lib/modules/manager/argocd/__fixtures__/validApplicationSet.yml @@ -73,3 +73,73 @@ spec: destination: name: '{{server}}' namespace: podinfo +--- +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: podinfo +spec: + generators: + - clusters: {} + template: + metadata: + name: '{{name}}-podinfo' + spec: + project: default + sources: + - chart: some/image3 + repoURL: somecontainer.registry.io:443/ + targetRevision: 1.0.0 + destination: + name: '{{server}}' + namespace: podinfo +--- +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: podinfo +spec: + generators: + - clusters: {} + template: + metadata: + name: '{{name}}-podinfo' + spec: + project: default + sources: + - ref: foo + repoURL: https://git.example.com/foo/bar.git + targetRevision: v1.2.0 + - chart: some/image3 + repoURL: somecontainer.registry.io:443/ + targetRevision: 1.0.0 + destination: + name: '{{server}}' + namespace: podinfo +--- +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: podinfo +spec: + generators: + - clusters: {} + template: + metadata: + name: '{{name}}-podinfo' + spec: + project: default + sources: + - ref: foo + repoURL: https://git.example.com/foo/bar.git + targetRevision: v1.2.0 + path: bar + - chart: somechart + repoURL: https://foo.io/repo + targetRevision: 0.0.2 + helm: + valueFiles: + - $foo/values.yaml + destination: + name: '{{server}}' + namespace: podinfo diff --git a/lib/modules/manager/argocd/extract.spec.ts b/lib/modules/manager/argocd/extract.spec.ts index 3413c86012edc1..003276cd78b521 100644 --- a/lib/modules/manager/argocd/extract.spec.ts +++ b/lib/modules/manager/argocd/extract.spec.ts @@ -74,6 +74,43 @@ describe('modules/manager/argocd/extract', () => { datasource: 'docker', depName: 'somecontainer.registry.io:443/some/image3', }, + { + currentValue: '1.0.0', + datasource: 'docker', + depName: 'somecontainer.registry.io:443/some/image3', + }, + { + currentValue: 'v1.2.0', + datasource: 'git-tags', + depName: 'https://git.example.com/foo/bar.git', + }, + { + currentValue: '1.0.0', + datasource: 'docker', + depName: 'somecontainer.registry.io:443/some/image3', + }, + { + currentValue: 'v1.2.0', + datasource: 'git-tags', + depName: 'https://git.example.com/foo/bar.git', + }, + { + currentValue: '0.0.2', + datasource: 'helm', + depName: 'traefik', + registryUrls: ['gs://helm-charts-internal'], + }, + { + currentValue: 'v1.2.0', + datasource: 'git-tags', + depName: 'https://git.example.com/foo/bar.git', + }, + { + currentValue: '0.0.2', + datasource: 'helm', + depName: 'somechart', + registryUrls: ['https://foo.io/repo'], + }, ], }); }); @@ -110,6 +147,32 @@ describe('modules/manager/argocd/extract', () => { depName: 'podinfo', registryUrls: ['https://stefanprodan.github.io/podinfo'], }, + { + currentValue: '1.0.0', + datasource: 'docker', + depName: 'somecontainer.registry.io:443/some/image3', + }, + { + currentValue: 'v1.2.0', + datasource: 'git-tags', + depName: 'https://git.example.com/foo/bar.git', + }, + { + currentValue: '1.0.0', + datasource: 'docker', + depName: 'somecontainer.registry.io:443/some/image3', + }, + { + currentValue: 'v1.2.0', + datasource: 'git-tags', + depName: 'https://git.example.com/foo/bar.git', + }, + { + currentValue: '0.0.2', + datasource: 'helm', + depName: 'somechart', + registryUrls: ['https://foo.io/repo'], + }, ], }); }); diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts index eed4b535357c83..93a3ce116f2be3 100644 --- a/lib/modules/manager/argocd/extract.ts +++ b/lib/modules/manager/argocd/extract.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { loadAll } from 'js-yaml'; import { logger } from '../../../logger'; +import { coerceArray } from '../../../util/array'; import { trimTrailingSlash } from '../../../util/url'; import { DockerDatasource } from '../../datasource/docker'; import { GitTagsDatasource } from '../../datasource/git-tags'; @@ -10,22 +11,37 @@ import type { PackageDependency, PackageFileContent, } from '../types'; -import type { ApplicationDefinition, ApplicationSource } from './types'; +import type { + ApplicationDefinition, + ApplicationSource, + ApplicationSpec, +} from './types'; import { fileTestRegex } from './util'; -function createDependency( - definition: ApplicationDefinition -): PackageDependency | null { - let source: ApplicationSource; - switch (definition.kind) { - case 'Application': - source = definition?.spec?.source; - break; - case 'ApplicationSet': - source = definition?.spec?.template?.spec?.source; - break; +export function extractPackageFile( + content: string, + fileName: string, + _config?: ExtractConfig +): PackageFileContent | null { + // check for argo reference. API version for the kind attribute is used + if (fileTestRegex.test(content) === false) { + return null; } + let definitions: ApplicationDefinition[]; + try { + definitions = loadAll(content) as ApplicationDefinition[]; + } catch (err) { + logger.debug({ err, fileName }, 'Failed to parse ArgoCD definition.'); + return null; + } + + const deps = definitions.filter(is.plainObject).flatMap(processAppSpec); + + return deps.length ? { deps } : null; +} + +function processSource(source: ApplicationSource): PackageDependency | null { if ( !source || !is.nonEmptyString(source.repoURL) || @@ -66,28 +82,27 @@ function createDependency( }; } -export function extractPackageFile( - content: string, - fileName: string, - _config?: ExtractConfig -): PackageFileContent | null { - // check for argo reference. API version for the kind attribute is used - if (fileTestRegex.test(content) === false) { - return null; +function processAppSpec( + definition: ApplicationDefinition +): PackageDependency[] { + const spec: ApplicationSpec | null | undefined = + definition.kind === 'Application' + ? definition?.spec + : definition?.spec?.template?.spec; + + if (is.nullOrUndefined(spec)) { + return []; } - let definitions: ApplicationDefinition[]; - try { - definitions = loadAll(content) as ApplicationDefinition[]; - } catch (err) { - logger.debug({ err, fileName }, 'Failed to parse ArgoCD definition.'); - return null; + const deps: (PackageDependency | null)[] = []; + + if (is.nonEmptyObject(spec.source)) { + deps.push(processSource(spec.source)); } - const deps = definitions - .filter(is.plainObject) - .map((definition) => createDependency(definition)) - .filter(is.truthy); + for (const source of coerceArray(spec.sources)) { + deps.push(processSource(source)); + } - return deps.length ? { deps } : null; + return deps.filter(is.truthy); } diff --git a/lib/modules/manager/argocd/types.ts b/lib/modules/manager/argocd/types.ts index 95420e8c0fd59f..f8b473e6d195af 100644 --- a/lib/modules/manager/argocd/types.ts +++ b/lib/modules/manager/argocd/types.ts @@ -8,20 +8,21 @@ export interface ApplicationSource { targetRevision: string; } +export interface ApplicationSpec { + source?: ApplicationSource; + sources?: ApplicationSource[]; +} + export interface Application extends KubernetesResource { kind: 'Application'; - spec: { - source: ApplicationSource; - }; + spec: ApplicationSpec; } export interface ApplicationSet extends KubernetesResource { kind: 'ApplicationSet'; spec: { template: { - spec: { - source: ApplicationSource; - }; + spec: ApplicationSpec; }; }; }