UNPKG

renovate

Version:

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

275 lines • 13 kB
"use strict"; 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