import { codeBlock } from 'common-tags'; import upath from 'upath'; import { mockDeep } from 'vitest-mock-extended'; import { envMock, mockExecAll } from '~test/exec-util.ts'; import { env, fs, git } from '~test/util.ts'; import { GlobalConfig } from '../../../config/global.ts'; import type { InternalGlobalConfigOptions, RepoGlobalConfig, } from '../../../config/types.ts'; import * as docker from '../../../util/exec/docker/index.ts'; import * as hostRules from '../../../util/host-rules.ts'; import * as _datasource from '../../datasource/index.ts'; import type { UpdateArtifactsConfig } from '../types.ts'; import * as helmfile from './index.ts'; vi.mock('../../datasource/index.ts', () => mockDeep()); vi.mock('../../../util/exec/env.ts'); vi.mock('../../../util/http/index.ts'); vi.mock('../../../util/fs/index.ts'); const datasource = vi.mocked(_datasource); process.env.CONTAINERBASE = 'true'; const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = { localDir: upath.join('/tmp/github/some/repo'), // `join` fixes Windows CI cacheDir: upath.join('/tmp/renovate/cache'), containerbaseDir: upath.join('/tmp/renovate/cache/containerbase'), dockerSidecarImage: 'ghcr.io/renovatebot/base-image', binarySource: 'global', }; const config: UpdateArtifactsConfig = {}; const helmfileYaml = codeBlock` repositories: - name: backstage url: https://backstage.github.io/charts - name: oauth2-proxy url: https://oauth2-proxy.github.io/manifests releases: - name: backstage chart: backstage/backstage version: 0.12.0 {{- if eq .Environment.Name "test" }} installed: false {{- end }} - name: oauth-proxy chart: oauth2-proxy/oauth2-proxy version: 6.8.0 `; const lockFile = codeBlock` version: 0.151.0 dependencies: - name: backstage repository: https://backstage.github.io/charts version: 0.11.0 - name: oauth2-proxy repository: https://oauth2-proxy.github.io/manifests version: 6.2.1 digest: sha256:e284706b71f37b757a536703da4cb148d67901afbf1ab431f7d60a9852ca6eef generated: "2023-03-08T21:32:06.122276997+01:00" `; const lockFileTwo = codeBlock` version: 0.151.0 dependencies: - name: backstage repository: https://backstage.github.io/charts version: 0.12.0 - name: oauth2-proxy repository: https://oauth2-proxy.github.io/manifests version: 6.8.0 digest: sha256:9d83889176d005effb86041d30c20361625561cbfb439cbd16d7243225bac17c generated: "2023-03-08T21:30:48.273709455+01:00" `; describe('modules/manager/helmfile/artifacts', () => { beforeEach(() => { env.getChildProcessEnv.mockReturnValue(envMock.basic); GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); hostRules.clear(); }); it('returns null if no helmfile.lock found', async () => { const updatedDeps = [{ depName: 'dep1' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: '', config, }), ).resolves.toBeNull(); }); it('returns null if updatedDeps is empty', async () => { await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps: [], newPackageFileContent: '', config, }), ).resolves.toBeNull(); }); it('returns null if unchanged', async () => { git.getFile.mockResolvedValueOnce(lockFile); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFile); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps: [{ depName: 'dep1' }], newPackageFileContent: helmfileYaml, config, }), ).resolves.toBeNull(); expect(execSnapshots).toMatchObject([ { cmd: 'helmfile deps -f helmfile.yaml' }, ]); }); it('returns updated helmfile.lock', async () => { git.getFile.mockResolvedValueOnce(lockFile); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFileTwo); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: helmfileYaml, config, }), ).resolves.toEqual([ { file: { type: 'addition', path: 'helmfile.lock', contents: lockFileTwo, }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'helmfile deps -f helmfile.yaml' }, ]); }); it('returns updated helmfile.lock if repositories were defined in ../helmfile-defaults.yaml.', async () => { const helmfileYamlWithoutRepositories = codeBlock` bases: - ../helmfile-defaults.yaml releases: - name: backstage chart: backstage/backstage version: 0.12.0 `; const lockFileWithoutRepositories = codeBlock` version: 0.151.0 dependencies: - name: backstage repository: https://backstage.github.io/charts version: 0.11.0 digest: sha256:e284706b71f37b757a536703da4cb148d67901afbf1ab431f7d60a9852ca6eef generated: "2023-03-08T21:32:06.122276997+01:00" `; const lockFileTwoWithoutRepositories = codeBlock` version: 0.151.0 dependencies: - name: backstage repository: https://backstage.github.io/charts version: 0.12.0 digest: sha256:9d83889176d005effb86041d30c20361625561cbfb439cbd16d7243225bac17c generated: "2023-03-08T21:30:48.273709455+01:00" `; git.getFile.mockResolvedValueOnce(lockFileWithoutRepositories); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFileTwoWithoutRepositories); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: helmfileYamlWithoutRepositories, config, }), ).resolves.toEqual([ { file: { type: 'addition', path: 'helmfile.lock', contents: lockFileTwoWithoutRepositories, }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'helmfile deps -f helmfile.yaml' }, ]); }); it('log into private OCI registries, returns updated helmfile.lock', async () => { const helmfileYamlOCIPrivateRepo = codeBlock` repositories: - name: private-charts url: ghcr.io/charts oci: true releases: - name: chart chart: private-charts/chart version: 0.12.0 `; const lockFileOCIPrivateRepo = codeBlock` version: 0.151.0 dependencies: - name: chart repository: oci://ghcr.io/private-charts version: 0.11.0 digest: sha256:e284706b71f37b757a536703da4cb148d67901afbf1ab431f7d60a9852ca6eef generated: "2023-03-08T21:32:06.122276997+01:00" `; const lockFileOCIPrivateRepoTwo = codeBlock` version: 0.151.0 dependencies: - name: chart repository: oci://ghcr.io/private-charts version: 0.12.0 digest: sha256:9d83889176d005effb86041d30c20361625561cbfb439cbd16d7243225bac17c generated: "2023-03-08T21:30:48.273709455+01:00" `; hostRules.add({ username: 'test', password: 'password', hostType: 'docker', matchHost: 'ghcr.io', }); git.getFile.mockResolvedValueOnce(lockFileOCIPrivateRepo); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFileOCIPrivateRepoTwo); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: helmfileYamlOCIPrivateRepo, config, }), ).resolves.toEqual([ { file: { type: 'addition', path: 'helmfile.lock', contents: lockFileOCIPrivateRepoTwo, }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'helm registry login --username test --password password ghcr.io', options: { env: { HELM_REGISTRY_CONFIG: '/tmp/renovate/cache/__renovate-private-cache/registry.json', HELM_REPOSITORY_CONFIG: '/tmp/renovate/cache/__renovate-private-cache/repositories.yaml', HELM_REPOSITORY_CACHE: '/tmp/renovate/cache/__renovate-private-cache/repositories', }, }, }, { cmd: 'helmfile deps -f helmfile.yaml', options: { env: { HELM_REGISTRY_CONFIG: '/tmp/renovate/cache/__renovate-private-cache/registry.json', HELM_REPOSITORY_CONFIG: '/tmp/renovate/cache/__renovate-private-cache/repositories.yaml', HELM_REPOSITORY_CACHE: '/tmp/renovate/cache/__renovate-private-cache/repositories', }, }, }, ]); }); it.each([ { binarySource: 'docker' as const, expectedCommands: [ { cmd: 'docker pull ghcr.io/renovatebot/base-image' }, { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, { cmd: 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + '-v "/tmp/renovate/cache":"/tmp/renovate/cache" ' + '-e CI ' + '-e HELM_EXPERIMENTAL_OCI ' + '-e HELM_REGISTRY_CONFIG ' + '-e HELM_REPOSITORY_CONFIG ' + '-e HELM_REPOSITORY_CACHE ' + '-e CONTAINERBASE_CACHE_DIR ' + '-w "/tmp/github/some/repo" ' + 'ghcr.io/renovatebot/base-image ' + "bash -l -c '" + 'install-tool helm v3.7.2' + ' && ' + 'install-tool helmfile 0.151.0' + ' && ' + 'install-tool kustomize 5.0.0' + ' && ' + 'helmfile deps -f helmfile.yaml' + "'", }, ], }, { binarySource: 'install' as const, expectedCommands: [ { cmd: 'install-tool helm v3.7.2' }, { cmd: 'install-tool helmfile 0.151.0' }, { cmd: 'install-tool kustomize 5.0.0' }, { cmd: 'helmfile deps -f helmfile.yaml' }, ], }, ])( 'returns updated helmfile.lock with binarySource=$binarySource', async ({ binarySource, expectedCommands }) => { GlobalConfig.set({ ...adminConfig, binarySource }); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); git.getFile.mockResolvedValueOnce(lockFile); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFileTwo); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); // helm datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: 'v3.7.2' }], }); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '5.0.0' }], }); const updatedDeps = [ { depName: 'dep1', managerData: { needKustomize: true } }, ]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: helmfileYaml, config, }), ).resolves.toEqual([ { file: { type: 'addition', path: 'helmfile.lock', contents: lockFileTwo, }, }, ]); expect(execSnapshots).toMatchObject(expectedCommands); }, ); it.each([ 'not found', "Error: cannot load Chart.lock: error converting YAML to JSON: yaml: line 1: did not find expected ',' or '}'", ])('catches error: %s', async (errorMessage) => { fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); git.getFile.mockResolvedValueOnce(lockFile); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.writeLocalFile.mockImplementationOnce(() => { throw new Error(errorMessage); }); const updatedDeps = [{ depName: 'dep1' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: helmfileYaml, config, }), ).resolves.toEqual([ { artifactError: { fileName: 'helmfile.lock', stderr: errorMessage, }, }, ]); }); it('updates lockfile with multidoc YAML', async () => { const multidocYaml = codeBlock` apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: HelmRepository metadata: name: metallb namespace: flux-system spec: interval: 30m url: https://metallb.github.io/metallb --- apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: metallb namespace: flux-system spec: interval: 5m install: createNamespace: true targetNamespace: metallb-system chart: spec: chart: metallb version: 0.13.10 sourceRef: kind: HelmRepository name: metallb namespace: flux-system values: controller: image: repository: quay.io/metallb/controller tag: v0.13.10 speaker: image: repository: quay.io/metallb/speaker tag: v0.13.10 frr: enabled: false `; const lockFileMultidoc = codeBlock` version: 0.151.0 dependencies: - name: metallb repository: https://metallb.github.io/metallb version: 0.13.9 digest: sha256:e284706b71f37b757a536703da4cb148d67901afbf1ab431f7d60a9852ca6eef generated: "2023-03-08T21:32:06.122276997+01:00" `; const lockFileMultidocUpdated = codeBlock` version: 0.151.0 dependencies: - name: metallb repository: https://metallb.github.io/metallb version: 0.13.10 digest: sha256:9d83889176d005effb86041d30c20361625561cbfb439cbd16d7243225bac17c generated: "2023-03-08T21:30:48.273709455+01:00" `; git.getFile.mockResolvedValueOnce(lockFileMultidoc); fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock'); const execSnapshots = mockExecAll(); fs.readLocalFile.mockResolvedValueOnce(lockFileMultidocUpdated); fs.privateCacheDir.mockReturnValue( '/tmp/renovate/cache/__renovate-private-cache', ); fs.getParentDir.mockReturnValue(''); const updatedDeps = [{ depName: 'metallb' }]; await expect( helmfile.updateArtifacts({ packageFileName: 'helmfile.yaml', updatedDeps, newPackageFileContent: multidocYaml, config, }), ).resolves.toEqual([ { file: { type: 'addition', path: 'helmfile.lock', contents: lockFileMultidocUpdated, }, }, ]); expect(execSnapshots).toMatchObject([ { cmd: 'helmfile deps -f helmfile.yaml' }, ]); }); });