renovate/lib/modules/manager/argocd/extract.ts

131 lines
3.5 KiB
TypeScript

import is from '@sindresorhus/is';
import { logger } from '../../../logger';
import { coerceArray } from '../../../util/array';
import { regEx } from '../../../util/regex';
import { trimTrailingSlash } from '../../../util/url';
import { parseYaml } from '../../../util/yaml';
import { DockerDatasource } from '../../datasource/docker';
import { GitTagsDatasource } from '../../datasource/git-tags';
import { HelmDatasource } from '../../datasource/helm';
import { getDep } from '../dockerfile/extract';
import { isOCIRegistry, removeOCIPrefix } from '../helmv3/oci';
import type {
ExtractConfig,
PackageDependency,
PackageFileContent,
} from '../types';
import {
ApplicationDefinition,
type ApplicationSource,
type ApplicationSpec,
} from './schema';
import { fileTestRegex } from './util';
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;
}
let definitions: ApplicationDefinition[];
try {
definitions = parseYaml(content, {
customSchema: ApplicationDefinition,
failureBehaviour: 'filter',
removeTemplates: true,
});
} catch (err) {
logger.debug({ err, packageFile }, 'Failed to parse ArgoCD definition.');
return null;
}
const deps = definitions.flatMap(processAppSpec);
return deps.length ? { deps } : null;
}
function processSource(source: ApplicationSource): 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 [
{
depName: `${registryURL}/${source.chart}`,
currentValue: source.targetRevision,
datasource: DockerDatasource.id,
},
];
}
return [
{
depName: source.chart,
registryUrls: [source.repoURL],
currentValue: source.targetRevision,
datasource: HelmDatasource.id,
},
];
}
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(is.truthy),
);
}
return dependencies;
}
function processAppSpec(
definition: ApplicationDefinition,
): PackageDependency[] {
const spec: ApplicationSpec =
definition.kind === 'Application'
? definition.spec
: definition.spec.template.spec;
const deps: PackageDependency[] = [];
if (is.nonEmptyObject(spec.source)) {
deps.push(...processSource(spec.source));
}
for (const source of coerceArray(spec.sources)) {
deps.push(...processSource(source));
}
return deps;
}
function processKustomizeImage(
kustomizeImage: string,
): PackageDependency | null {
const parts = kustomizeImageRe.exec(kustomizeImage);
if (!parts?.groups?.image) {
return null;
}
return getDep(parts.groups.image);
}