mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-13 15:36:25 +00:00
feat(manager/helmfile): support helm registry login
when updating helmfile.lock (#22494)
Co-authored-by: Michael Kriese <michael.kriese@visualon.de> Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
This commit is contained in:
parent
4e8591eda6
commit
b52d7255a6
6 changed files with 271 additions and 20 deletions
|
@ -152,6 +152,157 @@ describe('modules/manager/helmfile/artifacts', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
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 as never);
|
||||
fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock');
|
||||
const execSnapshots = mockExecAll();
|
||||
fs.readLocalFile.mockResolvedValueOnce(
|
||||
lockFileTwoWithoutRepositories as never
|
||||
);
|
||||
fs.privateCacheDir.mockReturnValue(
|
||||
'/tmp/renovate/cache/__renovate-private-cache'
|
||||
);
|
||||
fs.getParentDir.mockReturnValue('');
|
||||
const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }];
|
||||
expect(
|
||||
await helmfile.updateArtifacts({
|
||||
packageFileName: 'helmfile.yaml',
|
||||
updatedDeps,
|
||||
newPackageFileContent: helmfileYamlWithoutRepositories,
|
||||
config,
|
||||
})
|
||||
).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 as never);
|
||||
fs.getSiblingFileName.mockReturnValueOnce('helmfile.lock');
|
||||
const execSnapshots = mockExecAll();
|
||||
fs.readLocalFile.mockResolvedValueOnce(lockFileOCIPrivateRepoTwo as never);
|
||||
fs.privateCacheDir.mockReturnValue(
|
||||
'/tmp/renovate/cache/__renovate-private-cache'
|
||||
);
|
||||
fs.getParentDir.mockReturnValue('');
|
||||
const updatedDeps = [{ depName: 'dep1' }, { depName: 'dep2' }];
|
||||
expect(
|
||||
await helmfile.updateArtifacts({
|
||||
packageFileName: 'helmfile.yaml',
|
||||
updatedDeps,
|
||||
newPackageFileContent: helmfileYamlOCIPrivateRepo,
|
||||
config,
|
||||
})
|
||||
).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',
|
||||
|
@ -163,6 +314,10 @@ describe('modules/manager/helmfile/artifacts', () => {
|
|||
'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 HELM_EXPERIMENTAL_OCI ' +
|
||||
'-e HELM_REGISTRY_CONFIG ' +
|
||||
'-e HELM_REPOSITORY_CONFIG ' +
|
||||
'-e HELM_REPOSITORY_CACHE ' +
|
||||
'-e BUILDPACK_CACHE_DIR ' +
|
||||
'-e CONTAINERBASE_CACHE_DIR ' +
|
||||
'-w "/tmp/github/some/repo" ' +
|
||||
|
|
|
@ -2,6 +2,7 @@ import is from '@sindresorhus/is';
|
|||
import { quote } from 'shlex';
|
||||
import { TEMPORARY_ERROR } from '../../../constants/error-messages';
|
||||
import { logger } from '../../../logger';
|
||||
import { coerceArray } from '../../../util/array';
|
||||
import { exec } from '../../../util/exec';
|
||||
import type { ToolConstraint } from '../../../util/exec/types';
|
||||
import {
|
||||
|
@ -10,7 +11,10 @@ import {
|
|||
writeLocalFile,
|
||||
} from '../../../util/fs';
|
||||
import { getFile } from '../../../util/git';
|
||||
import { regEx } from '../../../util/regex';
|
||||
import { generateHelmEnvs } from '../helmv3/common';
|
||||
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
|
||||
import { generateRegistryLoginCmd, isOCIRegistry, parseDoc } from './utils';
|
||||
|
||||
export async function updateArtifacts({
|
||||
packageFileName,
|
||||
|
@ -59,9 +63,27 @@ export async function updateArtifacts({
|
|||
constraint: config.constraints?.kustomize,
|
||||
});
|
||||
}
|
||||
await exec(`helmfile deps -f ${quote(packageFileName)}`, {
|
||||
|
||||
const cmd: string[] = [];
|
||||
const doc = parseDoc(newPackageFileContent);
|
||||
|
||||
for (const value of coerceArray(doc.repositories).filter(isOCIRegistry)) {
|
||||
const loginCmd = generateRegistryLoginCmd(
|
||||
value.name,
|
||||
`https://${value.url}`,
|
||||
// this extracts the hostname from url like format ghcr.ip/helm-charts
|
||||
value.url.replace(regEx(/\/.*/), '')
|
||||
);
|
||||
|
||||
if (loginCmd) {
|
||||
cmd.push(loginCmd);
|
||||
}
|
||||
}
|
||||
|
||||
cmd.push(`helmfile deps -f ${quote(packageFileName)}`);
|
||||
await exec(cmd, {
|
||||
docker: {},
|
||||
extraEnv: {},
|
||||
extraEnv: generateHelmEnvs(),
|
||||
toolConstraints,
|
||||
});
|
||||
|
||||
|
|
|
@ -13,3 +13,33 @@ The `helmfile` manager defines this default registryAlias:
|
|||
If your Helm charts make use of repository aliases then you will need to configure an `registryAliases` object in your config to tell Renovate where to look for them. Be aware that alias values must be properly formatted URIs.
|
||||
|
||||
If you need to change the versioning format, read the [versioning](https://docs.renovatebot.com/modules/versioning/) documentation to learn more.
|
||||
|
||||
### Private repositories and registries
|
||||
|
||||
To use private sources of Helm charts, you must set the password and username you use to authenticate to the private source.
|
||||
For this you use a custom `hostRules` array.
|
||||
|
||||
#### OCI registries
|
||||
|
||||
```json5
|
||||
{
|
||||
hostRules: [
|
||||
{
|
||||
// global login
|
||||
matchHost: 'ghcr.io',
|
||||
hostType: 'docker',
|
||||
username: '<some-username>',
|
||||
password: '<some-password>',
|
||||
},
|
||||
{
|
||||
// login with encrypted password
|
||||
matchHost: 'https://ghci.io',
|
||||
hostType: 'docker',
|
||||
username: '<some-username>',
|
||||
encrypted: {
|
||||
password: 'some-encrypted-password',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
|
21
lib/modules/manager/helmfile/schema.ts
Normal file
21
lib/modules/manager/helmfile/schema.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const RepositorySchema = z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
oci: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ReleaseSchema = z.object({
|
||||
name: z.string(),
|
||||
chart: z.string(),
|
||||
version: z.string(),
|
||||
strategicMergePatches: z.unknown().optional(),
|
||||
jsonPatches: z.unknown().optional(),
|
||||
transformers: z.unknown().optional(),
|
||||
});
|
||||
|
||||
export const DocSchema = z.object({
|
||||
releases: z.array(ReleaseSchema).optional(),
|
||||
repositories: z.array(RepositorySchema).optional(),
|
||||
});
|
|
@ -1,19 +1,9 @@
|
|||
export interface Release {
|
||||
name: string;
|
||||
chart: string;
|
||||
version: string;
|
||||
strategicMergePatches?: unknown;
|
||||
jsonPatches?: unknown;
|
||||
transformers?: unknown;
|
||||
}
|
||||
import type { z } from 'zod';
|
||||
|
||||
interface Repository {
|
||||
name: string;
|
||||
url: string;
|
||||
oci?: boolean;
|
||||
}
|
||||
import type { DocSchema, ReleaseSchema, RepositorySchema } from './schema';
|
||||
|
||||
export interface Doc {
|
||||
releases?: Release[];
|
||||
repositories?: Repository[];
|
||||
}
|
||||
export type Release = z.infer<typeof ReleaseSchema>;
|
||||
|
||||
export type Repository = z.infer<typeof RepositorySchema>;
|
||||
|
||||
export type Doc = z.infer<typeof DocSchema>;
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import yaml from 'js-yaml';
|
||||
import upath from 'upath';
|
||||
|
||||
import { getParentDir, localPathExists } from '../../../util/fs';
|
||||
import type { Release } from './types';
|
||||
import * as hostRules from '../../../util/host-rules';
|
||||
import { DockerDatasource } from '../../datasource/docker';
|
||||
import { generateLoginCmd } from '../helmv3/common';
|
||||
import type { RepositoryRule } from '../helmv3/types';
|
||||
|
||||
import { DocSchema } from './schema';
|
||||
import type { Doc, Release, Repository } from './types';
|
||||
|
||||
/** Returns true if a helmfile release contains kustomize specific keys **/
|
||||
export function kustomizationsKeysUsed(release: Release): boolean {
|
||||
|
@ -23,3 +30,29 @@ export async function localChartHasKustomizationsYaml(
|
|||
upath.join(helmfileYamlParentDir, release.chart, 'kustomization.yaml')
|
||||
);
|
||||
}
|
||||
|
||||
export function parseDoc(packageFileContent: string): Doc {
|
||||
const doc = yaml.load(packageFileContent);
|
||||
return DocSchema.parse(doc);
|
||||
}
|
||||
|
||||
export function isOCIRegistry(repository: Repository): boolean {
|
||||
return repository.oci === true;
|
||||
}
|
||||
|
||||
export function generateRegistryLoginCmd(
|
||||
repositoryName: string,
|
||||
repositoryBaseURL: string,
|
||||
repositoryHost: string
|
||||
): string | null {
|
||||
const repositoryRule: RepositoryRule = {
|
||||
name: repositoryName,
|
||||
repository: repositoryHost,
|
||||
hostRule: hostRules.find({
|
||||
url: repositoryBaseURL,
|
||||
hostType: DockerDatasource.id,
|
||||
}),
|
||||
};
|
||||
|
||||
return generateLoginCmd(repositoryRule, 'helm registry login');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue