diff --git a/lib/modules/manager/helmfile/__fixtures__/go-template.yaml b/lib/modules/manager/helmfile/__fixtures__/go-template.yaml index d9179d9a170271c589f5e1940f25a37994178c7d..29553475bd178d1bf4ce9dbd5beb8c0eb37c0b05 100644 --- a/lib/modules/manager/helmfile/__fixtures__/go-template.yaml +++ b/lib/modules/manager/helmfile/__fixtures__/go-template.yaml @@ -12,10 +12,10 @@ releases: chart: ./foo - name: "{{ requiredEnv \"RELEASE_NAME\" }}" namespace: default - chart: ../bar + chart: ../repo/bar - name: "{{ requiredEnv \"RELEASE_NAME\" }}" namespace: default - chart: /baz + chart: /tmp/github/some/repo/baz - name: {{ requiredEnv "RELEASE_NAME" }} namespace: default chart: ./foo diff --git a/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml b/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml index d42bd70e57cfd204639fae00a7ac7ef445109963..f194b9c30410a9a41e83867724590e895cc85a42 100644 --- a/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml +++ b/lib/modules/manager/helmfile/__fixtures__/multidoc.yaml @@ -25,8 +25,6 @@ repositories: url: https://charts.bitnami.com/bitnami - name: prometheus-community url: https://prometheus-community.github.io/helm-charts -- name: incubator - url: https://charts.helm.sh/incubator/ templates: external-chart: &external-chart diff --git a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap index 49b560fae84aa4a006c900df2409e68f97db0fa5..a81b5d69302566c7e91ea2d0c4fee549c1208607 100644 --- a/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/helmfile/__snapshots__/extract.spec.ts.snap @@ -33,14 +33,14 @@ exports[`modules/manager/helmfile/extract extractPackageFile() parses multidoc y { "currentValue": "0.1.0", "depName": "raw", - "managerData": { - "needKustomize": true, - }, "registryUrls": [ "https://charts.helm.sh/incubator/", ], }, ], + "managerData": { + "needKustomize": true, + }, } `; diff --git a/lib/modules/manager/helmfile/extract.spec.ts b/lib/modules/manager/helmfile/extract.spec.ts index 68a7bbb8cd1b054802bbd393037885fb2ded1dda..5405340710b20189a16a812ca5eaec66f47bf123 100644 --- a/lib/modules/manager/helmfile/extract.spec.ts +++ b/lib/modules/manager/helmfile/extract.spec.ts @@ -1,20 +1,25 @@ import { Fixtures } from '../../../../test/fixtures'; +import { fs } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; import { extractPackageFile } from '.'; +jest.mock('../../../util/fs'); + describe('modules/manager/helmfile/extract', () => { describe('extractPackageFile()', () => { beforeEach(() => { + GlobalConfig.set({ localDir: '/tmp/github/some/repo' }); jest.resetAllMocks(); }); - it('returns null if no releases', () => { + it('returns null if no releases', async () => { const content = ` repositories: - name: kiwigrid url: https://kiwigrid.github.io `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -22,7 +27,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result).toBeNull(); }); - it('do not crash on invalid helmfile.yaml', () => { + it('do not crash on invalid helmfile.yaml', async () => { const content = ` repositories: - name: kiwigrid @@ -31,7 +36,7 @@ describe('modules/manager/helmfile/extract', () => { releases: [ `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -39,7 +44,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result).toBeNull(); }); - it('skip if repository details are not specified', () => { + it('skip if repository details are not specified', async () => { const content = ` repositories: - name: kiwigrid @@ -50,7 +55,7 @@ describe('modules/manager/helmfile/extract', () => { chart: experimental/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -60,7 +65,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result?.deps.every((dep) => dep.skipReason)).toBeTruthy(); }); - it('skip templetized release with invalid characters', () => { + it('skip templetized release with invalid characters', async () => { const content = ` repositories: - name: kiwigrid @@ -74,7 +79,7 @@ describe('modules/manager/helmfile/extract', () => { chart: stable/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -94,7 +99,7 @@ describe('modules/manager/helmfile/extract', () => { }); }); - it('skip local charts', () => { + it('skip local charts', async () => { const content = ` repositories: - name: kiwigrid @@ -105,7 +110,7 @@ describe('modules/manager/helmfile/extract', () => { chart: ./charts/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -115,7 +120,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result?.deps.every((dep) => dep.skipReason)).toBeTruthy(); }); - it('skip chart with unknown repository', () => { + it('skip chart with unknown repository', async () => { const content = ` repositories: - name: kiwigrid @@ -126,7 +131,7 @@ describe('modules/manager/helmfile/extract', () => { chart: example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -136,7 +141,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result?.deps.every((dep) => dep.skipReason)).toBeTruthy(); }); - it('skip chart with special character in the name', () => { + it('skip chart with special character in the name', async () => { const content = ` repositories: - name: kiwigrid @@ -150,7 +155,7 @@ describe('modules/manager/helmfile/extract', () => { chart: kiwigrid/example?example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -160,7 +165,7 @@ describe('modules/manager/helmfile/extract', () => { expect(result?.deps.every((dep) => dep.skipReason)).toBeTruthy(); }); - it('skip chart that does not have specified version', () => { + it('skip chart that does not have specified version', async () => { const content = ` repositories: - name: kiwigrid @@ -170,7 +175,7 @@ describe('modules/manager/helmfile/extract', () => { chart: stable/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -180,9 +185,9 @@ describe('modules/manager/helmfile/extract', () => { expect(result?.deps.every((dep) => dep.skipReason)).toBeTruthy(); }); - it('parses multidoc yaml', () => { + it('parses multidoc yaml', async () => { const fileName = 'helmfile.yaml'; - const result = extractPackageFile( + const result = await extractPackageFile( Fixtures.get('multidoc.yaml'), fileName, { @@ -199,12 +204,13 @@ describe('modules/manager/helmfile/extract', () => { { depName: 'kube-prometheus-stack', currentValue: '13.7' }, { depName: 'invalid', skipReason: 'invalid-name' }, { depName: 'external-dns', skipReason: 'invalid-version' }, - { depName: 'raw', managerData: { needKustomize: true } }, + { depName: 'raw' }, ], + managerData: { needKustomize: true }, }); }); - it('parses a chart with a go templating', () => { + it('parses a chart with a go templating', async () => { const content = ` repositories: - name: kiwigrid @@ -222,7 +228,7 @@ describe('modules/manager/helmfile/extract', () => { chart: stable/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -242,7 +248,7 @@ describe('modules/manager/helmfile/extract', () => { }); }); - it('parses a chart with empty strings for template values', () => { + it('parses a chart with empty strings for template values', async () => { const content = ` repositories: - name: kiwigrid @@ -259,7 +265,7 @@ describe('modules/manager/helmfile/extract', () => { chart: stable/example `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -281,7 +287,7 @@ describe('modules/manager/helmfile/extract', () => { }); }); - it('parses a chart with an oci repository and non-oci one', () => { + it('parses a chart with an oci repository and non-oci one', async () => { const content = ` repositories: - name: oci-repo @@ -299,7 +305,7 @@ describe('modules/manager/helmfile/extract', () => { version: 3.3.0 `; const fileName = 'helmfile.yaml'; - const result = extractPackageFile(content, fileName, { + const result = await extractPackageFile(content, fileName, { registryAliases: { stable: 'https://charts.helm.sh/stable', }, @@ -321,9 +327,10 @@ describe('modules/manager/helmfile/extract', () => { }); }); - it('parses and replaces templating strings', () => { + it('parses and replaces templating strings', async () => { const filename = 'helmfile.yaml'; - const result = extractPackageFile( + fs.localPathExists.mockReturnValue(Promise.resolve(true)); + const result = await extractPackageFile( Fixtures.get('go-template.yaml'), filename, { @@ -347,7 +354,10 @@ describe('modules/manager/helmfile/extract', () => { depName: '', skipReason: 'local-chart', }, - { depName: null, skipReason: 'local-chart' }, + { + depName: null, + skipReason: 'local-chart', + }, { depName: 'ingress-nginx', currentValue: '3.37.0', @@ -372,6 +382,7 @@ describe('modules/manager/helmfile/extract', () => { registryUrls: ['https://charts.helm.sh/stable'], }, ], + managerData: { needKustomize: true }, }); }); }); diff --git a/lib/modules/manager/helmfile/extract.ts b/lib/modules/manager/helmfile/extract.ts index 4f65fbd823c1bebd7a98ad6f959f4e9fc8c15509..347f8cf77f7cbdad46b54ea0dfdc972f02a91626 100644 --- a/lib/modules/manager/helmfile/extract.ts +++ b/lib/modules/manager/helmfile/extract.ts @@ -10,7 +10,10 @@ import type { PackageFileContent, } from '../types'; import type { Doc } from './types'; -import { areKustomizationsUsed } from './utils'; +import { + kustomizationsKeysUsed, + localChartHasKustomizationsYaml, +} from './utils'; const isValidChartName = (name: string | undefined): boolean => !!name && !regEx(/[!@#$%^&*(),.?":{}/|<>A-Z]/).test(name); @@ -22,14 +25,22 @@ function extractYaml(content: string): string { .replace(regEx(/{{.+?}}/g), ''); } -export function extractPackageFile( +function isLocalPath(possiblePath: string): boolean { + return ['./', '../', '/'].some((localPrefix) => + possiblePath.startsWith(localPrefix) + ); +} + +export async function extractPackageFile( content: string, fileName: string, config: ExtractConfig -): PackageFileContent | null { - let deps: PackageDependency[] = []; +): Promise<PackageFileContent | null> { + const deps: PackageDependency[] = []; let docs: Doc[]; const registryAliases: Record<string, string> = {}; + // Record kustomization usage for all deps, since updating artifacts is run on the helmfile.yaml as a whole. + let needKustomize = false; try { docs = loadAll(extractYaml(content), null, { json: true }) as Doc[]; } catch (err) { @@ -48,23 +59,31 @@ export function extractPackageFile( } logger.debug({ registryAliases }, 'repositories discovered.'); - deps = doc.releases.map((dep) => { + for (const dep of doc.releases) { let depName = dep.chart; let repoName: string | null = null; if (!is.string(dep.chart)) { - return { + deps.push({ depName: dep.name, skipReason: 'invalid-name', - }; + }); + continue; } // If it starts with ./ ../ or / then it's a local path - if (['./', '../', '/'].some((val) => dep.chart.startsWith(val))) { - return { + if (isLocalPath(dep.chart)) { + if ( + kustomizationsKeysUsed(dep) || + (await localChartHasKustomizationsYaml(dep)) + ) { + needKustomize = true; + } + deps.push({ depName: dep.name, skipReason: 'local-chart', - }; + }); + continue; } if (is.number(dep.version)) { @@ -80,10 +99,11 @@ export function extractPackageFile( } if (!is.string(dep.version)) { - return { + deps.push({ depName, skipReason: 'invalid-version', - }; + }); + continue; } const res: PackageDependency = { @@ -93,8 +113,8 @@ export function extractPackageFile( .concat([config.registryAliases?.[repoName]] as string[]) .filter(is.string), }; - if (areKustomizationsUsed(dep)) { - res.managerData = { needKustomize: true }; + if (kustomizationsKeysUsed(dep)) { + needKustomize = true; } // in case of OCI repository, we need a PackageDependency with a DockerDatasource and a packageName const repository = doc.repositories?.find( @@ -116,9 +136,15 @@ export function extractPackageFile( res.skipReason = 'unknown-registry'; } - return res; - }); + deps.push(res); + } } - return deps.length ? { deps, datasource: HelmDatasource.id } : null; + return deps.length + ? { + deps, + datasource: HelmDatasource.id, + ...(needKustomize && { managerData: { needKustomize } }), + } + : null; } diff --git a/lib/modules/manager/helmfile/utils.ts b/lib/modules/manager/helmfile/utils.ts index 415925b9d10aa03d2a7ec804f91058cb81d56f1d..16abccda2a3997e26b8534d6ddfbe5457f442753 100644 --- a/lib/modules/manager/helmfile/utils.ts +++ b/lib/modules/manager/helmfile/utils.ts @@ -1,10 +1,21 @@ +import upath from 'upath'; + +import { localPathExists } from '../../../util/fs'; import type { Release } from './types'; -/** Returns true if kustomize specific keys exist in a helmfile release */ -export function areKustomizationsUsed(release: Release): boolean { +/** Returns true if a helmfile release contains kustomize specific keys **/ +export function kustomizationsKeysUsed(release: Release): boolean { return ( release.strategicMergePatches !== undefined || release.jsonPatches !== undefined || release.transformers !== undefined ); } + +/** Returns true if a helmfile release uses a local chart with a kustomization.yaml file **/ +// eslint-disable-next-line require-await +export async function localChartHasKustomizationsYaml( + release: Release +): Promise<boolean> { + return localPathExists(upath.join(release.chart, 'kustomization.yaml')); +}