UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

321 lines (320 loc) • 13.3 kB
import { regEx } from "../../../util/regex.js"; import { logger } from "../../../logger/index.js"; import { isHttpUrl } from "../../../util/url.js"; import { parseYaml } from "../../../util/yaml.js"; import { coerceArray } from "../../../util/array.js"; import { readLocalFile } from "../../../util/fs/index.js"; import { BitbucketTagsDatasource } from "../../datasource/bitbucket-tags/index.js"; import { DockerDatasource } from "../../datasource/docker/index.js"; import { GitRefsDatasource } from "../../datasource/git-refs/index.js"; import { GitTagsDatasource } from "../../datasource/git-tags/index.js"; import { GithubReleasesDatasource } from "../../datasource/github-releases/index.js"; import { GithubTagsDatasource } from "../../datasource/github-tags/index.js"; import { GitlabTagsDatasource } from "../../datasource/gitlab-tags/index.js"; import { HelmDatasource } from "../../datasource/helm/index.js"; import { getDep } from "../dockerfile/extract.js"; import { isOCIRegistry, removeOCIPrefix } from "../helmv3/oci.js"; import { collectHelmRepos, isSystemManifest, systemManifestHeaderRegex } from "./common.js"; import { findDependencies } from "../helm-values/extract.js"; import { extractImage } from "../kustomize/extract.js"; import { FluxResource } from "./schema.js"; import { isString } from "@sindresorhus/is"; import { isMap, isPair, isScalar, parseAllDocuments } from "yaml"; //#region lib/modules/manager/flux/extract.ts function readManifest(content, packageFile) { if (isSystemManifest(packageFile)) { const versionMatch = regEx(systemManifestHeaderRegex).exec(content); if (!versionMatch) return null; return { kind: "system", file: packageFile, content, version: versionMatch[1], components: versionMatch[2] }; } return { kind: "resource", file: packageFile, content, resources: parseYaml(content, { customSchema: FluxResource, failureBehaviour: "filter" }) }; } const githubUrlRegex = regEx(/^(?:https:\/\/|git@)github\.com[/:](?<packageName>[^/]+\/[^/]+?)(?:\.git)?$/); const gitlabUrlRegex = regEx(/^(?:https:\/\/|git@)gitlab\.com[/:](?<packageName>[^/]+\/[^/]+?)(?:\.git)?$/); const bitbucketUrlRegex = regEx(/^(?:https:\/\/|git@)bitbucket\.org[/:](?<packageName>[^/]+\/[^/]+?)(?:\.git)?$/); function resolveGitRepositoryPerSourceTag(dep, gitUrl) { const githubMatchGroups = githubUrlRegex.exec(gitUrl)?.groups; if (githubMatchGroups) { dep.datasource = GithubTagsDatasource.id; dep.packageName = githubMatchGroups.packageName; dep.sourceUrl = `https://github.com/${dep.packageName}`; return; } const gitlabMatchGroups = gitlabUrlRegex.exec(gitUrl)?.groups; if (gitlabMatchGroups) { dep.datasource = GitlabTagsDatasource.id; dep.packageName = gitlabMatchGroups.packageName; dep.sourceUrl = `https://gitlab.com/${dep.packageName}`; return; } const bitbucketMatchGroups = bitbucketUrlRegex.exec(gitUrl)?.groups; if (bitbucketMatchGroups) { dep.datasource = BitbucketTagsDatasource.id; dep.packageName = bitbucketMatchGroups.packageName; dep.sourceUrl = `https://bitbucket.org/${dep.packageName}`; return; } dep.datasource = GitTagsDatasource.id; dep.packageName = gitUrl; if (isHttpUrl(gitUrl)) dep.sourceUrl = gitUrl.replace(/\.git$/, ""); } function resolveHelmRepository(dep, matchingRepositories, registryAliases, sourceRefName) { if (matchingRepositories.length) { dep.registryUrls = matchingRepositories.map((repo) => { if (repo.spec.type === "oci" || isOCIRegistry(repo.spec.url)) { dep.datasource = DockerDatasource.id; dep.packageName = getDep(`${removeOCIPrefix(repo.spec.url)}/${dep.depName}`, false, registryAliases).packageName; return null; } else return repo.spec.url; }).filter(isString); if (!dep.registryUrls?.length) delete dep.registryUrls; return; } if (sourceRefName && registryAliases) { const aliasUrl = registryAliases[sourceRefName]; if (aliasUrl) { if (isOCIRegistry(aliasUrl)) { dep.datasource = DockerDatasource.id; dep.packageName = getDep(`${removeOCIPrefix(aliasUrl)}/${dep.depName}`, false, registryAliases).packageName; } else dep.registryUrls = [aliasUrl]; return; } } dep.skipReason = "unknown-registry"; } function resolveSystemManifest(manifest) { return [{ depName: "fluxcd/flux2", datasource: GithubReleasesDatasource.id, currentValue: manifest.version, managerData: { components: manifest.components } }]; } /** * Returns all `spec.ref` map nodes for OCIRepository resources matching `resourceName`. */ function findOCIRefNodes(docs, resourceName) { const refNodes = []; for (const doc of docs) { const docContents = doc.contents; if (!isMap(docContents)) continue; const kindNode = docContents.get("kind", true); if (!isScalar(kindNode) || kindNode.value !== "OCIRepository") continue; const nameNode = docContents.getIn(["metadata", "name"], true); if (!isScalar(nameNode) || nameNode.value !== resourceName) continue; const specNode = docContents.get("spec"); if (!isMap(specNode)) continue; const refNode = specNode.get("ref"); if (isMap(refNode)) refNodes.push(refNode); } return refNodes; } function extractOCIRefTagAndDigestRange(docs, content, resourceName) { for (const refNode of findOCIRefNodes(docs, resourceName)) { let tagKey; let tagValue; let digestKey; let digestValue; for (const item of refNode.items) { if (!isPair(item) || !isScalar(item.key)) continue; if (item.key.value === "tag" && isScalar(item.value)) { tagKey = item.key; tagValue = item.value; } else if (item.key.value === "digest" && isScalar(item.value)) { digestKey = item.key; digestValue = item.value; } } if (!tagKey?.range || !tagValue?.range || !digestKey?.range || !digestValue?.range) continue; const tagFirst = tagKey.range[0] < digestKey.range[0]; const start = tagFirst ? tagKey.range[0] : digestKey.range[0]; const end = tagFirst ? digestValue.range[1] : tagValue.range[1]; return { replaceString: content.slice(start, end), tagFirst }; } return null; } function extractOCIRefTagRange(docs, content, resourceName) { for (const refNode of findOCIRefNodes(docs, resourceName)) for (const item of refNode.items) if (isPair(item) && isScalar(item.key) && item.key.value === "tag" && isScalar(item.value) && item.key.range && item.value.range) { const keyStart = item.key.range[0]; const valueEnd = item.value.range[1]; const lineStart = content.lastIndexOf("\n", keyStart - 1) + 1; return { replaceString: content.slice(keyStart, valueEnd), indentation: content.slice(lineStart, keyStart) }; } return null; } function resolveResourceManifest(manifest, helmRepositories, registryAliases, content) { let docs; const deps = []; for (const resource of manifest.resources) switch (resource.kind) { case "HelmRelease": if (resource.spec.chartRef) logger.trace("HelmRelease using chartRef was found, skipping as version will be handled via referenced resource directly"); else if (resource.spec.chart) { const chartSpec = resource.spec.chart.spec; const depName = chartSpec.chart; const dep = { depName, currentValue: resource.spec.chart.spec.version, datasource: HelmDatasource.id }; if (depName.startsWith("./")) { dep.skipReason = "local-chart"; delete dep.datasource; } else { const sourceRef = chartSpec.sourceRef; resolveHelmRepository(dep, helmRepositories.filter((rep) => rep.kind === sourceRef?.kind && rep.metadata.name === sourceRef.name && rep.metadata.namespace === (sourceRef?.namespace ?? resource.metadata?.namespace)), registryAliases, sourceRef?.name); } deps.push(dep); } else logger.debug(`invalid or incomplete ${resource.metadata.name} HelmRelease spec, skipping`); if (resource.spec.values) { logger.trace("detecting dependencies in HelmRelease values"); deps.push(...findDependencies(resource.spec.values, registryAliases)); } break; case "HelmChart": { if (resource.spec.sourceRef.kind === "GitRepository") { logger.trace("HelmChart using GitRepository was found, skipping as version will be handled via referenced resource directly"); continue; } const dep = { depName: resource.spec.chart }; if (resource.spec.sourceRef.kind === "HelmRepository") { dep.currentValue = resource.spec.version; dep.datasource = HelmDatasource.id; const sourceRef = resource.spec.sourceRef; resolveHelmRepository(dep, helmRepositories.filter((rep) => rep.kind === sourceRef?.kind && rep.metadata.name === sourceRef.name && rep.metadata.namespace === resource.metadata?.namespace), registryAliases, sourceRef?.name); } else dep.skipReason = "unsupported-datasource"; deps.push(dep); break; } case "GitRepository": { const dep = { depName: resource.metadata.name }; if (resource.spec.ref?.commit) { const gitUrl = resource.spec.url; dep.currentDigest = resource.spec.ref.commit; dep.datasource = GitRefsDatasource.id; dep.packageName = gitUrl; dep.replaceString = resource.spec.ref.commit; if (isHttpUrl(gitUrl)) dep.sourceUrl = gitUrl.replace(/\.git$/, ""); } else if (resource.spec.ref?.tag) { dep.currentValue = resource.spec.ref.tag; resolveGitRepositoryPerSourceTag(dep, resource.spec.url); } else dep.skipReason = "unversioned-reference"; deps.push(dep); break; } case "OCIRepository": { const container = removeOCIPrefix(resource.spec.url); if (resource.spec.ref?.digest && resource.spec.ref?.tag) { const combinedDep = getDep(`${container}@${resource.spec.ref.digest}`, false, registryAliases); combinedDep.currentValue = resource.spec.ref.tag; const refRange = extractOCIRefTagAndDigestRange(docs ??= parseAllDocuments(content, { strict: false }), content, resource.metadata.name); if (refRange) { combinedDep.replaceString = refRange.replaceString; if (refRange.tagFirst) combinedDep.autoReplaceStringTemplate = refRange.replaceString.replace(resource.spec.ref.tag, "{{newValue}}").replace(resource.spec.ref.digest, "{{newDigest}}"); else combinedDep.autoReplaceStringTemplate = refRange.replaceString.replace(resource.spec.ref.digest, "{{newDigest}}").replace(resource.spec.ref.tag, "{{newValue}}"); } else { logger.debug({ file: manifest.file, name: resource.metadata.name }, "Could not find tag/digest nodes in content, skipping replacement"); combinedDep.skipReason = "invalid-value"; } deps.push(combinedDep); } else if (resource.spec.ref?.digest) { const dep = getDep(`${container}@${resource.spec.ref.digest}`, false, registryAliases); deps.push(dep); } else if (resource.spec.ref?.tag) { const dep = getDep(`${container}:${resource.spec.ref.tag}`, false, registryAliases); const refTagRange = extractOCIRefTagRange(docs ??= parseAllDocuments(content, { strict: false }), content, resource.metadata.name); if (refTagRange) { dep.replaceString = refTagRange.replaceString; const newline = content.includes("\r\n") ? "\r\n" : "\n"; dep.autoReplaceStringTemplate = `${refTagRange.replaceString.replace(resource.spec.ref.tag, "{{newValue}}")}{{#if newDigest}}${newline}${refTagRange.indentation}digest: {{newDigest}}{{/if}}`; } else { logger.debug({ file: manifest.file, name: resource.metadata.name }, "Unable to locate tag node for replacement (may be YAML alias or alias reference), digest pinning will not be possible"); dep.replaceString = resource.spec.ref.tag; dep.autoReplaceStringTemplate = "{{newValue}}"; } deps.push(dep); } else { const dep = getDep(container, false, registryAliases); dep.skipReason = "unversioned-reference"; deps.push(dep); } break; } case "Kustomization": for (const image of coerceArray(resource.spec.images)) { const dep = extractImage(image, registryAliases); if (dep) deps.push(dep); } } return deps; } function extractPackageFile(content, packageFile, config) { const manifest = readManifest(content, packageFile); if (!manifest) return null; const helmRepositories = collectHelmRepos([manifest]); let deps = null; switch (manifest.kind) { case "system": deps = resolveSystemManifest(manifest); break; case "resource": deps = resolveResourceManifest(manifest, helmRepositories, config?.registryAliases, content); break; } return deps?.length ? { deps } : null; } async function extractAllPackageFiles(config, packageFiles) { const manifests = []; const results = []; for (const file of packageFiles) { const content = await readLocalFile(file, "utf8"); if (content) { const manifest = readManifest(content, file); if (manifest) manifests.push(manifest); } } const helmRepositories = collectHelmRepos(manifests); for (const manifest of manifests) { let deps = null; switch (manifest.kind) { case "system": deps = resolveSystemManifest(manifest); break; case "resource": deps = resolveResourceManifest(manifest, helmRepositories, config.registryAliases, manifest.content); break; } if (deps?.length) results.push({ packageFile: manifest.file, deps }); } return results.length ? results : null; } //#endregion export { extractAllPackageFiles, extractPackageFile }; //# sourceMappingURL=extract.js.map