Files
Sebastian Poxhofer be13c9f5f7 feat(manager): share OCI Helm chart resolution across managers (#45760)
* feat(manager): share OCI Helm chart resolution across managers

Add getOciChartDep() to helmv3/oci.ts and use it in argocd, fleet, flux,
helmfile, helmsman, helmv3, kustomize, sveltos, terraform and vendir.

- set pinDigests: false for OCI charts in all of them
- apply registryAliases to OCI charts in argocd, helmfile and helmsman
- keep the registry port in terraform helm_release repositories
- skip fleet OCI charts without a version instead of using 'undefined'

* docs(manager/helmv3): correct the OCI chart pinDigests rationale

* refactor(manager/helmfile): drop redundant OCI repository url guard

The repository schema already requires url to be a string, so the falsy
branch could not be reached by parsed input and left the patch partially
covered.
2026-09-09 13:51:30 +00:00

103 lines
3.1 KiB
TypeScript

import upath from 'upath';
import { logger } from '../../../logger/index.ts';
import { regEx } from '../../../util/regex.ts';
import { parseUrl } from '../../../util/url.ts';
import type { PackageDependency } from '../types.ts';
import { getOciChartDep } from './oci.ts';
import type { ChartDefinition, Repository } from './types.ts';
export function parseRepository(
depName: string,
repositoryURL: string,
): PackageDependency {
const res: PackageDependency = {};
const url = parseUrl(repositoryURL);
if (!url) {
logger.debug({ repositoryURL }, 'Error parsing url');
res.skipReason = 'invalid-url';
return res;
}
switch (url.protocol) {
case 'oci:':
return getOciChartDep(repositoryURL, depName);
case 'file:':
res.skipReason = 'local-dependency';
break;
default:
res.registryUrls = [repositoryURL];
}
return res;
}
/**
* Resolves alias in repository string.
*
* @param repository to be resolved string
* @param registryAliases Records containing registryAliases as key and to be resolved URLs as values
*
* @returns resolved alias. If repository does not contain an alias the repository string will be returned. Should it contain an alias which can not be resolved using `registryAliases`, null will be returned
*/
export function resolveAlias(
repository: string,
registryAliases: Record<string, string>,
): string | null {
if (!isAlias(repository)) {
return repository;
}
const repoWithPrefixRemoved = repository.slice(
repository.startsWith('@') ? 1 : 6,
);
const alias = registryAliases[repoWithPrefixRemoved];
if (alias) {
return alias;
}
return null;
}
export function getRepositories(definitions: ChartDefinition[]): Repository[] {
const repositoryList = definitions
.flatMap((value) => value.dependencies)
.filter((dependency) => dependency.repository) // only keep non-local references --> if no repository is defined the chart will be searched in charts/<name>
.filter((dependency) => !isAlias(dependency.repository)) // do not add registryAliases
.filter((dependency) => !dependency.repository.startsWith('file:')) // skip repositories which are locally referenced
.map((dependency) => {
// remove additional keys to prevent interference at deduplication
return {
name: dependency.name,
repository: dependency.repository,
};
});
const dedup = new Set();
return repositoryList.filter((el) => {
const duplicate = dedup.has(el.repository);
dedup.add(el.repository);
return !duplicate;
});
}
export function isAlias(repository: string): boolean {
if (!repository) {
return false;
}
return repository.startsWith('@') || repository.startsWith('alias:');
}
export function aliasRecordToRepositories(
registryAliases: Record<string, string>,
): Repository[] {
return Object.entries(registryAliases)
.filter(([, url]) => regEx(/^(?:https?|oci):\/\/.+/).exec(url))
.map(([alias, url]) => {
return {
name: alias,
repository: url,
};
});
}
export function isFileInDir(dir: string, file: string): boolean {
return upath.dirname(file) === dir;
}