UNPKG

renovate

Version:

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

238 lines (237 loc) • 9.68 kB
import { PAGE_NOT_FOUND_ERROR } from "../../../constants/error-messages.js"; import { regEx } from "../../../util/regex.js"; import { addSecretForSanitizing } from "../../../util/sanitize.js"; import { logger } from "../../../logger/index.js"; import { ensureTrailingSlash, parseUrl, trimTrailingSlash } from "../../../util/url.js"; import { find } from "../../../util/host-rules.js"; import { detectPlatform } from "../../../util/common.js"; import { coerceArray } from "../../../util/array.js"; import { toSha256 } from "../../../util/hash.js"; import { ExternalHostError } from "../../../types/errors/external-host-error.js"; import api from "../../versioning/docker/index.js"; import { parseGitUrl } from "../../../util/git/url.js"; import { memCacheProvider } from "../../../util/http/cache/memory-http-cache-provider.js"; import { getGoogleAuthToken } from "../util.js"; import { BearerScheme, parse } from "../../../util/http/www-authenticate.js"; import { ecrRegex, getECRAuthToken } from "./ecr.js"; import { googleRegex } from "./google.js"; import { isNonEmptyString, isString } from "@sindresorhus/is"; //#region lib/modules/datasource/docker/common.ts const dockerDatasourceId = "docker"; const imageUrlLabel = "org.opencontainers.image.url"; const sourceLabel = "org.opencontainers.image.source"; const sourceLabels = [sourceLabel, "org.label-schema.vcs-url"]; const gitRefLabel = "org.opencontainers.image.revision"; const DOCKER_HUB = "https://index.docker.io"; function isDockerHost(host) { return regEx(/(?:^|\.)docker\.io$/).test(host); } async function getAuthHeaders(http, registryHost, dockerRepository, apiCheckUrl = `${registryHost}/v2/`) { try { const options = { throwHttpErrors: false, noAuth: true, cacheProvider: memCacheProvider }; const apiCheckResponse = apiCheckUrl.endsWith("/v2/") ? await http.get(apiCheckUrl, options) : await http.getJsonUnchecked(apiCheckUrl, options); if (apiCheckResponse.statusCode === 200) { logger.debug(`No registry auth required for ${apiCheckUrl}`); return {}; } if (apiCheckResponse.statusCode === 404) { logger.debug(`Page Not Found ${apiCheckUrl}`); throw new Error(PAGE_NOT_FOUND_ERROR); } if (apiCheckResponse.statusCode !== 401 || !isNonEmptyString(apiCheckResponse.headers["www-authenticate"])) { logger.warn({ apiCheckUrl, res: apiCheckResponse }, "Invalid registry response"); return null; } const rule = find({ hostType: dockerDatasourceId, url: apiCheckUrl }); const opts = {}; if (ecrRegex.test(registryHost)) { logger.once.debug(`hostRules: ecr auth for ${registryHost}`); logger.trace({ registryHost, dockerRepository }, `Using ecr auth for Docker registry`); const [, region] = coerceArray(ecrRegex.exec(registryHost)); const auth = await getECRAuthToken(region, rule); if (auth) opts.headers = { authorization: `Basic ${auth}` }; } else if (googleRegex.test(registryHost) && typeof rule.username === "undefined" && typeof rule.password === "undefined" && typeof rule.token === "undefined") { logger.once.debug(`hostRules: google auth for ${registryHost}`); logger.trace({ registryHost, dockerRepository }, `Using google auth for Docker registry`); const auth = await getGoogleAuthToken(); if (auth) opts.headers = { authorization: `Basic ${auth}` }; else logger.once.debug({ registryHost, dockerRepository }, "Could not get Google access token, using no auth"); } else if (rule.username && rule.password) { logger.once.debug(`hostRules: basic auth for ${registryHost}`); logger.trace({ registryHost, dockerRepository }, `Using basic auth for Docker registry`); opts.headers = { authorization: `Basic ${Buffer.from(`${rule.username}:${rule.password}`).toString("base64")}` }; } else if (rule.token) { const authType = rule.authType ?? "Bearer"; logger.once.debug(`hostRules: ${authType} token auth for ${registryHost}`); logger.trace({ registryHost, dockerRepository }, `Using ${authType} token for Docker registry`); opts.headers = { authorization: `${authType} ${rule.token}` }; } const authenticateHeader = parse(apiCheckResponse.headers["www-authenticate"]).find((c) => c.scheme === BearerScheme); if (!authenticateHeader || !isString(authenticateHeader.params?.realm) || parseUrl(authenticateHeader.params.realm) === null) { logger.once.debug(`hostRules: testing direct auth for ${registryHost}`); logger.trace({ registryHost, dockerRepository, authenticateHeader }, `Invalid realm, testing direct auth`); return opts.headers ?? null; } const authUrl = parseUrl(`${authenticateHeader.params.realm}`); if (isString(authenticateHeader.params.scope) && !apiCheckUrl.endsWith("/v2/")) authUrl.searchParams.append("scope", authenticateHeader.params.scope); else authUrl.searchParams.append("scope", `repository:${dockerRepository}:pull`); if (isString(authenticateHeader.params.service)) authUrl.searchParams.append("service", authenticateHeader.params.service); logger.trace({ registryHost, dockerRepository, authUrl: authUrl.href }, `Obtaining docker registry token`); opts.noAuth = true; opts.cacheProvider = memCacheProvider; const authResponse = (await http.getJsonUnchecked(authUrl.href, opts)).body; const token = authResponse.token ?? authResponse.access_token; /* v8 ignore next 4 -- TODO: add test */ if (!token) { logger.warn("Failed to obtain docker registry token"); return null; } addSecretForSanitizing(token); return { authorization: `Bearer ${token}` }; } catch (err) /* istanbul ignore next */ { /* v8 ignore if */ if (err.host === "quay.io") return null; /* v8 ignore if */ if (err.statusCode === 401) { logger.debug({ registryHost, dockerRepository }, "Unauthorized docker lookup"); logger.debug({ err }); return null; } /* v8 ignore if */ if (err.statusCode === 403) { logger.debug({ registryHost, dockerRepository }, "Not allowed to access docker registry"); logger.debug({ err }); return null; } if (err.name === "RequestError" && isDockerHost(registryHost)) throw new ExternalHostError(err); /* v8 ignore if */ if (err.statusCode === 429 && isDockerHost(registryHost)) throw new ExternalHostError(err); /* v8 ignore if */ if (err.statusCode >= 500 && err.statusCode < 600) throw new ExternalHostError(err); if (err.message === "page-not-found") throw err; /* v8 ignore if */ if (err.message === "host-disabled") { logger.trace({ registryHost, dockerRepository, err }, "Host disabled"); return null; } logger.warn({ registryHost, dockerRepository, err }, "Error obtaining docker token"); return null; } } function getRegistryRepository(packageName, registryUrl) { if (registryUrl !== "https://index.docker.io") { const registryEndingWithSlash = ensureTrailingSlash(registryUrl.replace(regEx(/^https?:\/\//), "")); if (packageName.startsWith(registryEndingWithSlash)) { let registryHost = trimTrailingSlash(registryUrl); if (!regEx(/^https?:\/\//).test(registryHost)) registryHost = `https://${registryHost}`; let dockerRepository = packageName.replace(registryEndingWithSlash, ""); const parsedFullUrl = parseUrl(`${registryHost}/${dockerRepository}`); if (!parsedFullUrl) return { registryHost, dockerRepository }; registryHost = parsedFullUrl.origin; dockerRepository = parsedFullUrl.pathname.substring(1); return { registryHost, dockerRepository }; } } let registryHost = registryUrl; const split = packageName.split("/"); if (split.length > 1 && (split[0].includes(".") || split[0].includes(":"))) { [registryHost] = split; split.shift(); } let dockerRepository = split.join("/"); if (!regEx(/^https?:\/\//).test(registryHost)) registryHost = `https://${registryHost}`; const { path, base } = regEx(/^(?<base>https:\/\/[^/]+)\/(?<path>.+)$/).exec(registryHost)?.groups ?? {}; if (base && path) { registryHost = base; dockerRepository = `${trimTrailingSlash(path)}/${dockerRepository}`; } registryHost = registryHost.replace("https://docker.io", "https://index.docker.io").replace("https://registry-1.docker.io", "https://index.docker.io"); if (find({ hostType: "docker", url: registryHost })?.insecureRegistry) registryHost = registryHost.replace("https", "http"); if (registryHost.endsWith(".docker.io") && !dockerRepository.includes("/")) dockerRepository = `library/${dockerRepository}`; return { registryHost, dockerRepository }; } function extractDigestFromResponseBody(manifestResponse) { return `sha256:${toSha256(manifestResponse.body)}`; } function findLatestStable(tags) { let stable = null; for (const tag of tags) { if (!api.isValid(tag) || !api.isStable(tag)) continue; if (!stable || api.isGreaterThan(tag, stable)) stable = tag; } return stable; } const chartRepo = regEx(/charts?|helm|helm-charts?/i); function isPossibleChartRepo(url) { if (detectPlatform(url) === null) return false; const parsed = parseGitUrl(url); return chartRepo.test(parsed.name); } function findHelmSourceUrl(release) { if (release.home && isPossibleChartRepo(release.home)) return release.home; if (!release.sources?.length) return null; for (const url of release.sources) if (isPossibleChartRepo(url)) return url; return release.sources[0]; } //#endregion export { DOCKER_HUB, dockerDatasourceId, extractDigestFromResponseBody, findHelmSourceUrl, findLatestStable, getAuthHeaders, getRegistryRepository, gitRefLabel, imageUrlLabel, isDockerHost, sourceLabel, sourceLabels }; //# sourceMappingURL=common.js.map