Files
renovate/lib/modules/manager/argocd/extract.ts
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

144 lines
3.9 KiB
TypeScript

import { isNonEmptyObject, isTruthy } from '@sindresorhus/is';
import { logger } from '../../../logger/index.ts';
import { coerceArray } from '../../../util/array.ts';
import { regEx } from '../../../util/regex.ts';
import { withDebugMessage } from '../../../util/schema-utils/index.ts';
import { trimTrailingSlash } from '../../../util/url.ts';
import { GitTagsDatasource } from '../../datasource/git-tags/index.ts';
import { HelmDatasource } from '../../datasource/helm/index.ts';
import { getDep } from '../dockerfile/extract.ts';
import {
getOciChartDep,
isOCIRegistry,
removeOCIPrefix,
} from '../helmv3/oci.ts';
import type {
ExtractConfig,
PackageDependency,
PackageFileContent,
} from '../types.ts';
import {
type ApplicationDefinition,
ApplicationDefinitions,
type ApplicationSource,
type ApplicationSpec,
} from './schema.ts';
import { fileTestRegex } from './util.ts';
const kustomizeImageRe = regEx(/=(?<image>.+)$/);
export function extractPackageFile(
content: string,
packageFile: string,
config?: ExtractConfig,
): PackageFileContent | null {
// check for argo reference. API version for the kind attribute is used
if (fileTestRegex.test(content) === false) {
logger.debug(
`Skip file ${packageFile} as no argoproj.io apiVersion could be found in matched file`,
);
return null;
}
const definitions = ApplicationDefinitions.catch(
withDebugMessage([], `${packageFile} does not match schema`),
).parse(content);
const deps = definitions.flatMap((definition) =>
processAppSpec(definition, config?.registryAliases),
);
return deps.length ? { deps } : null;
}
function processSource(
source: ApplicationSource,
registryAliases: Record<string, string> | undefined,
): PackageDependency[] {
// a chart variable is defined this is helm declaration
if (source.chart) {
// assume OCI helm chart if repoURL doesn't contain explicit protocol
if (isOCIRegistry(source.repoURL) || !source.repoURL.includes('://')) {
const registryURL = trimTrailingSlash(removeOCIPrefix(source.repoURL));
return [
{
...getOciChartDep(source.repoURL, source.chart, registryAliases),
depName: `${registryURL}/${source.chart}`,
currentValue: source.targetRevision,
},
];
}
return [
{
depName: source.chart,
registryUrls: [source.repoURL],
currentValue: source.targetRevision,
datasource: HelmDatasource.id,
},
];
}
// Handle OCI Helm chart without explicit chart field
if (isOCIRegistry(source.repoURL)) {
return [
{
...getOciChartDep(source.repoURL, undefined, registryAliases),
depName: trimTrailingSlash(removeOCIPrefix(source.repoURL)),
currentValue: source.targetRevision,
},
];
}
const dependencies: PackageDependency[] = [
{
depName: source.repoURL,
currentValue: source.targetRevision,
datasource: GitTagsDatasource.id,
},
];
// Git repo is pointing to a Kustomize resources
if (source.kustomize?.images) {
dependencies.push(
...source.kustomize.images.map(processKustomizeImage).filter(isTruthy),
);
}
return dependencies;
}
function processAppSpec(
definition: ApplicationDefinition,
registryAliases: Record<string, string> | undefined,
): PackageDependency[] {
const spec: ApplicationSpec =
definition.kind === 'Application'
? definition.spec
: definition.spec.template.spec;
const deps: PackageDependency[] = [];
if (isNonEmptyObject(spec.source)) {
deps.push(...processSource(spec.source, registryAliases));
}
for (const source of coerceArray(spec.sources)) {
deps.push(...processSource(source, registryAliases));
}
return deps;
}
function processKustomizeImage(
kustomizeImage: string,
): PackageDependency | null {
const parts = kustomizeImageRe.exec(kustomizeImage);
if (!parts?.groups?.image) {
return null;
}
return getDep(parts.groups.image);
}