renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
275 lines • 13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOCKER_HUB = exports.gitRefLabel = exports.sourceLabels = exports.sourceLabel = exports.imageUrlLabel = exports.dockerDatasourceId = void 0;
exports.isDockerHost = isDockerHost;
exports.getAuthHeaders = getAuthHeaders;
exports.getRegistryRepository = getRegistryRepository;
exports.extractDigestFromResponseBody = extractDigestFromResponseBody;
exports.findLatestStable = findLatestStable;
exports.findHelmSourceUrl = findHelmSourceUrl;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const auth_header_1 = require("auth-header");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const array_1 = require("../../../util/array");
const common_1 = require("../../../util/common");
const url_1 = require("../../../util/git/url");
const hash_1 = require("../../../util/hash");
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const memory_http_cache_provider_1 = require("../../../util/http/cache/memory-http-cache-provider");
const regex_1 = require("../../../util/regex");
const sanitize_1 = require("../../../util/sanitize");
const url_2 = require("../../../util/url");
const docker_1 = require("../../versioning/docker");
const util_1 = require("../util");
const ecr_1 = require("./ecr");
const google_1 = require("./google");
exports.dockerDatasourceId = 'docker';
exports.imageUrlLabel = 'org.opencontainers.image.url';
exports.sourceLabel = 'org.opencontainers.image.source';
exports.sourceLabels = [exports.sourceLabel, 'org.label-schema.vcs-url'];
exports.gitRefLabel = 'org.opencontainers.image.revision';
exports.DOCKER_HUB = 'https://index.docker.io';
function isDockerHost(host) {
const regex = (0, regex_1.regEx)(/(?:^|\.)docker\.io$/);
return regex.test(host);
}
async function getAuthHeaders(http, registryHost, dockerRepository, apiCheckUrl = `${registryHost}/v2/`) {
try {
const options = {
throwHttpErrors: false,
noAuth: true,
cacheProvider: memory_http_cache_provider_1.memCacheProvider,
};
const apiCheckResponse = apiCheckUrl.endsWith('/v2/')
? await http.get(apiCheckUrl, options)
: // use json request, as this will be cached for tags, so it returns json
// TODO: add cache test
await http.getJsonUnchecked(apiCheckUrl, options);
if (apiCheckResponse.statusCode === 200) {
logger_1.logger.debug(`No registry auth required for ${apiCheckUrl}`);
return {};
}
if (apiCheckResponse.statusCode === 404) {
logger_1.logger.debug(`Page Not Found ${apiCheckUrl}`);
// throw error up to be caught and potentially retried with library/ prefix
throw new Error(error_messages_1.PAGE_NOT_FOUND_ERROR);
}
if (apiCheckResponse.statusCode !== 401 ||
!is_1.default.nonEmptyString(apiCheckResponse.headers['www-authenticate'])) {
logger_1.logger.warn({ apiCheckUrl, res: apiCheckResponse }, 'Invalid registry response');
return null;
}
const authenticateHeader = (0, auth_header_1.parse)(apiCheckResponse.headers['www-authenticate']);
const opts = hostRules.find({
hostType: exports.dockerDatasourceId,
url: apiCheckUrl,
});
if (ecr_1.ecrRegex.test(registryHost)) {
logger_1.logger.once.debug(`hostRules: ecr auth for ${registryHost}`);
logger_1.logger.trace({ registryHost, dockerRepository }, `Using ecr auth for Docker registry`);
const [, region] = (0, array_1.coerceArray)(ecr_1.ecrRegex.exec(registryHost));
const auth = await (0, ecr_1.getECRAuthToken)(region, opts);
if (auth) {
opts.headers = { authorization: `Basic ${auth}` };
}
}
else if (google_1.googleRegex.test(registryHost) &&
typeof opts.username === 'undefined' &&
typeof opts.password === 'undefined' &&
typeof opts.token === 'undefined') {
logger_1.logger.once.debug(`hostRules: google auth for ${registryHost}`);
logger_1.logger.trace({ registryHost, dockerRepository }, `Using google auth for Docker registry`);
const auth = await (0, util_1.getGoogleAuthToken)();
if (auth) {
opts.headers = { authorization: `Basic ${auth}` };
}
else {
logger_1.logger.once.debug({ registryHost, dockerRepository }, 'Could not get Google access token, using no auth');
}
}
else if (opts.username && opts.password) {
logger_1.logger.once.debug(`hostRules: basic auth for ${registryHost}`);
logger_1.logger.trace({ registryHost, dockerRepository }, `Using basic auth for Docker registry`);
const auth = Buffer.from(`${opts.username}:${opts.password}`).toString('base64');
opts.headers = { authorization: `Basic ${auth}` };
}
else if (opts.token) {
const authType = opts.authType ?? 'Bearer';
logger_1.logger.once.debug(`hostRules: ${authType} token auth for ${registryHost}`);
logger_1.logger.trace({ registryHost, dockerRepository }, `Using ${authType} token for Docker registry`);
opts.headers = { authorization: `${authType} ${opts.token}` };
}
delete opts.username;
delete opts.password;
delete opts.token;
// If realm isn't an url, we should directly use auth header
// Can happen when we get a Basic auth or some other auth type
// * WWW-Authenticate: Basic realm="Artifactory Realm"
// * Www-Authenticate: Basic realm="https://123456789.dkr.ecr.eu-central-1.amazonaws.com/",service="ecr.amazonaws.com"
// * www-authenticate: Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull"
// * www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io"
if (authenticateHeader.scheme.toUpperCase() !== 'BEARER' ||
!is_1.default.string(authenticateHeader.params.realm) ||
(0, url_2.parseUrl)(authenticateHeader.params.realm) === null) {
logger_1.logger.once.debug(`hostRules: testing direct auth for ${registryHost}`);
logger_1.logger.trace({ registryHost, dockerRepository, authenticateHeader }, `Invalid realm, testing direct auth`);
return opts.headers ?? null;
}
const authUrl = new URL(`${authenticateHeader.params.realm}`);
// repo isn't known to server yet, so causing wrong scope `repository:user/image:pull`
if (is_1.default.string(authenticateHeader.params.scope) &&
!apiCheckUrl.endsWith('/v2/')) {
authUrl.searchParams.append('scope', authenticateHeader.params.scope);
}
else {
authUrl.searchParams.append('scope', `repository:${dockerRepository}:pull`);
}
if (is_1.default.string(authenticateHeader.params.service)) {
authUrl.searchParams.append('service', authenticateHeader.params.service);
}
logger_1.logger.trace({ registryHost, dockerRepository, authUrl: authUrl.href }, `Obtaining docker registry token`);
opts.noAuth = true;
opts.cacheProvider = memory_http_cache_provider_1.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_1.logger.warn('Failed to obtain docker registry token');
return null;
}
// sanitize token
(0, sanitize_1.addSecretForSanitizing)(token);
return {
authorization: `Bearer ${token}`,
};
}
catch (err) /* istanbul ignore next */ {
if (err.host === 'quay.io') {
// TODO: debug why quay throws errors (#9604)
return null;
}
if (err.statusCode === 401) {
logger_1.logger.debug({ registryHost, dockerRepository }, 'Unauthorized docker lookup');
logger_1.logger.debug({ err });
return null;
}
if (err.statusCode === 403) {
logger_1.logger.debug({ registryHost, dockerRepository }, 'Not allowed to access docker registry');
logger_1.logger.debug({ err });
return null;
}
if (err.name === 'RequestError' && isDockerHost(registryHost)) {
throw new external_host_error_1.ExternalHostError(err);
}
if (err.statusCode === 429 && isDockerHost(registryHost)) {
throw new external_host_error_1.ExternalHostError(err);
}
if (err.statusCode >= 500 && err.statusCode < 600) {
throw new external_host_error_1.ExternalHostError(err);
}
if (err.message === error_messages_1.PAGE_NOT_FOUND_ERROR) {
throw err;
}
if (err.message === error_messages_1.HOST_DISABLED) {
logger_1.logger.trace({ registryHost, dockerRepository, err }, 'Host disabled');
return null;
}
logger_1.logger.warn({ registryHost, dockerRepository, err }, 'Error obtaining docker token');
return null;
}
}
function getRegistryRepository(packageName, registryUrl) {
if (registryUrl !== exports.DOCKER_HUB) {
const registryEndingWithSlash = (0, url_2.ensureTrailingSlash)(registryUrl.replace((0, regex_1.regEx)(/^https?:\/\//), ''));
if (packageName.startsWith(registryEndingWithSlash)) {
let registryHost = (0, url_2.trimTrailingSlash)(registryUrl);
if (!(0, regex_1.regEx)(/^https?:\/\//).test(registryHost)) {
registryHost = `https://${registryHost}`;
}
let dockerRepository = packageName.replace(registryEndingWithSlash, '');
const fullUrl = `${registryHost}/${dockerRepository}`;
const { origin, pathname } = (0, url_2.parseUrl)(fullUrl);
registryHost = origin;
dockerRepository = 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 (!(0, regex_1.regEx)(/^https?:\/\//).test(registryHost)) {
registryHost = `https://${registryHost}`;
}
const { path, base } = (0, regex_1.regEx)(/^(?<base>https:\/\/[^/]+)\/(?<path>.+)$/).exec(registryHost)
?.groups ?? {};
if (base && path) {
registryHost = base;
dockerRepository = `${(0, url_2.trimTrailingSlash)(path)}/${dockerRepository}`;
}
registryHost = registryHost
.replace('https://docker.io', 'https://index.docker.io')
.replace('https://registry-1.docker.io', 'https://index.docker.io');
const opts = hostRules.find({
hostType: exports.dockerDatasourceId,
url: registryHost,
});
if (opts?.insecureRegistry) {
registryHost = registryHost.replace('https', 'http');
}
if (registryHost.endsWith('.docker.io') && !dockerRepository.includes('/')) {
dockerRepository = 'library/' + dockerRepository;
}
return {
registryHost,
dockerRepository,
};
}
function extractDigestFromResponseBody(manifestResponse) {
return 'sha256:' + (0, hash_1.toSha256)(manifestResponse.body);
}
function findLatestStable(tags) {
let stable = null;
for (const tag of tags) {
if (!docker_1.api.isValid(tag) || !docker_1.api.isStable(tag)) {
continue;
}
if (!stable || docker_1.api.isGreaterThan(tag, stable)) {
stable = tag;
}
}
return stable;
}
const chartRepo = (0, regex_1.regEx)(/charts?|helm|helm-charts?/i);
function isPossibleChartRepo(url) {
if ((0, common_1.detectPlatform)(url) === null) {
return false;
}
const parsed = (0, url_1.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;
}
}
// fallback
return release.sources[0];
}
//# sourceMappingURL=common.js.map