renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
238 lines (237 loc) • 9.68 kB
JavaScript
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