Files
renovate/lib/modules/manager/devcontainer/extract.ts
T
Sebastian Poxhofer 19306f7e17 feat(manager/dockerfile): options form of getDep for CI managers (#46183)
* refactor(manager/helmv3): share generateHelmEnvs with kustomize

Move kustomize's generateHelmEnvs into helmv3/common.ts and delete the
kustomize copy. The merged function always sets the three HELM_*
path variables and sets HELM_EXPERIMENTAL_OCI unless a helm
constraint is present and proves helm >= 3.8 (which ignores the
variable).

Behaviour change: kustomize now sets HELM_EXPERIMENTAL_OCI when the
helm constraint is unknown (previously it only set it for a known
constraint below 3.8). helmv3 and helmfile now omit the variable
once a resolved helm constraint proves helm >= 3.8.

* refactor(manager/helmv3): share OCI registry login helper with helmfile

Extract a shared generateRegistryLoginCmd(name, registry) helper in
helmv3/common.ts that strips the oci:// prefix, derives the host and
looks up the matching host rule before delegating to generateLoginCmd.

helmv3/artifacts.ts and helmfile/artifacts.ts now both call this
helper instead of each building a RepositoryRule inline, removing the
stale oci:// TODO. helmfile/utils.ts loses its duplicate
generateRegistryLoginCmd, and its unrelated isOCIRegistry(repository)
(checking the oci flag) is renamed to isOciRepositoryFlagSet so it no
longer collides in name with helmv3/oci.ts's prefix-based
isOCIRegistry(string), which helmfile/extract.ts also imports.

Addresses audit finding M8. Generated `helm registry login` commands
are unchanged, proved by the existing artifacts specs passing with
their original expectations untouched.

* fix(manager): share one local Helm chart path check

helmfile, flux, fleet and terraform each decided differently whether a Helm chart reference points at a local path. fleet and terraform used terraform's checkIfStringIsPath, whose regex was unanchored with unescaped dots, so it matched any string containing a slash followed by a non-slash character.

Add isLocalChartPath to helmv3/utils.ts with helmfile's prefix check and use it in all four managers. This drops the fleet to terraform dependency and removes checkIfStringIsPath, which had no other users.

* refactor(manager/helm-requirements): reuse helmv3 alias and repository helpers

Replace the duplicated alias-resolution and repository-classification logic in helm-requirements/extract.ts with the shared isAlias, resolveAlias and parseRepository helpers from helmv3/utils.ts (audit finding M9). Behaviour is unchanged: same skipReasons, same registryUrls, same handling of a missing repository or missing registryAliases config.

* refactor(manager/dockerfile): add an options form of getDep for CI managers

`getDep()` gains an overload taking a `GetDepOptions` object with
`specifyReplaceString`, `registryAliases` and `depType`, replacing the
opaque boolean second positional argument at the CI manager call sites.
The positional form keeps working for all other callers.

Migrated: bitbucket-pipelines, droneci, circleci, github-actions,
azure-pipelines and woodpecker.

Behaviour changes:

- azure-pipelines container resources now honour `registryAliases`, which
  `extractContainer()` previously ignored
- woodpecker dependencies now carry `depType: 'docker'`, matching the
  other CI managers

* refactor(manager/woodpecker): do not set depType on docker image deps

* refactor(manager/dockerfile): drop the positional getDep signature

* refactor(manager/helm-requirements): reuse helmv3 alias and repository helpers

Replace the duplicated alias-resolution and repository-classification logic in helm-requirements/extract.ts with the shared isAlias, resolveAlias and parseRepository helpers from helmv3/utils.ts (audit finding M9). Behaviour is unchanged: same skipReasons, same registryUrls, same handling of a missing repository or missing registryAliases config.
2026-09-29 15:24:23 +00:00

134 lines
3.8 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import { GolangVersionDatasource } from '../../datasource/golang-version/index.ts';
import { NodeVersionDatasource } from '../../datasource/node-version/index.ts';
import { PythonVersionDatasource } from '../../datasource/python-version/index.ts';
import { RubyVersionDatasource } from '../../datasource/ruby-version/index.ts';
import { isValidDependency } from '../custom/utils.ts';
import { getDep as getDockerDep } from '../dockerfile/extract.ts';
import type {
ExtractConfig,
PackageDependency,
PackageFileContent,
} from '../types.ts';
import { DevContainerFile } from './schema.ts';
export function extractPackageFile(
content: string,
packageFile: string,
extractConfig: ExtractConfig,
): PackageFileContent | null {
try {
const file = DevContainerFile.parse(content);
const deps: PackageDependency[] = [];
const image = file?.image ?? null;
const imageDep = getDep(image, packageFile, extractConfig.registryAliases);
if (imageDep) {
imageDep.depType = 'image';
deps.push(imageDep);
} else {
logger.trace(
{ packageFile },
'No image defined in dev container JSON file.',
);
}
const features = file.features;
if (features) {
for (const [feature, value] of Object.entries(features)) {
const featureDep = getDep(
feature,
packageFile,
extractConfig.registryAliases,
);
if (featureDep) {
featureDep.depType = 'feature';
featureDep.pinDigests = false;
deps.push(featureDep);
let dep: PackageDependency;
switch (featureDep.depName) {
case 'ghcr.io/devcontainers/features/node':
dep = {
depName: 'node',
datasource: NodeVersionDatasource.id,
currentValue: value.version,
};
break;
case 'ghcr.io/devcontainers/features/go':
dep = {
depName: 'go',
datasource: GolangVersionDatasource.id,
currentValue: value.version,
};
break;
case 'ghcr.io/devcontainers/features/python':
dep = {
depName: 'python',
datasource: PythonVersionDatasource.id,
currentValue: value.version,
};
break;
case 'ghcr.io/devcontainers/features/ruby':
dep = {
depName: 'ruby',
datasource: RubyVersionDatasource.id,
currentValue: value.version,
};
break;
default:
// skip additional checks if not a known feature
continue;
}
if (!value.version) {
dep.skipReason = 'unspecified-version';
}
deps.push(dep);
continue;
}
logger.trace(
{ feature, packageFile },
'Skipping invalid dependency in dev container JSON file.',
);
}
}
if (deps.length < 1) {
logger.trace(
{ packageFile },
'No dependencies to process for dev container JSON file.',
);
return null;
}
return { deps };
} catch (err) {
logger.debug(
{ err, packageFile },
'Error extracting dev container JSON file.',
);
return null;
}
}
function getDep(
subject: string | null,
packageFile: string,
registryAliases?: Record<string, string>,
): PackageDependency | null {
if (!subject) {
return null;
}
const dep = getDockerDep(subject, { registryAliases });
if (!isValidDependency(dep)) {
logger.trace(
{ subject, packageFile },
'Skipping invalid docker dependency in dev container JSON file.',
);
return null;
}
return dep;
}