renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
776 lines • 36.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DockerDatasource = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const global_1 = require("../../../config/global");
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 decorator_1 = require("../../../util/cache/package/decorator");
const env_1 = require("../../../util/env");
const http_1 = require("../../../util/http");
const memory_http_cache_provider_1 = require("../../../util/http/cache/memory-http-cache-provider");
const object_1 = require("../../../util/object");
const result_1 = require("../../../util/result");
const string_match_1 = require("../../../util/string-match");
const timestamp_1 = require("../../../util/timestamp");
const url_1 = require("../../../util/url");
const docker_1 = require("../../versioning/docker");
const datasource_1 = require("../datasource");
const util_1 = require("../util");
const common_1 = require("./common");
const dockerhub_cache_1 = require("./dockerhub-cache");
const ecr_1 = require("./ecr");
const schema_1 = require("./schema");
const defaultConfig = {
commitMessageTopic: '{{{depName}}} Docker tag',
commitMessageExtra: 'to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{{prettyNewMajor}}}{{else}}{{{prettyNewVersion}}}{{/if}}{{/if}}',
digest: {
branchTopic: '{{{depNameSanitized}}}-{{{currentValue}}}',
commitMessageExtra: 'to {{newDigestShort}}',
commitMessageTopic: '{{{depName}}}{{#if currentValue}}:{{{currentValue}}}{{/if}} Docker digest',
group: {
commitMessageTopic: '{{{groupName}}}',
commitMessageExtra: '',
},
},
pin: {
commitMessageExtra: '',
groupName: 'Docker digests',
group: {
commitMessageTopic: '{{{groupName}}}',
branchTopic: 'digests-pin',
},
},
};
class DockerDatasource extends datasource_1.Datasource {
static id = common_1.dockerDatasourceId;
defaultVersioning = docker_1.id;
defaultRegistryUrls = [common_1.DOCKER_HUB];
defaultConfig = defaultConfig;
releaseTimestampSupport = true;
releaseTimestampNote = 'The release timestamp is determined from the `tag_last_pushed` field in the results.';
sourceUrlSupport = 'package';
sourceUrlNote = 'The source URL is determined from the `org.opencontainers.image.source` and `org.label-schema.vcs-url` labels present in the metadata of the **latest stable** image found on the Docker registry.';
constructor() {
super(DockerDatasource.id);
}
// TODO: debug why quay throws errors (#9612)
async getManifestResponse(registryHost, dockerRepository, tag, mode = 'getText') {
logger_1.logger.debug(`getManifestResponse(${registryHost}, ${dockerRepository}, ${tag}, ${mode})`);
try {
const headers = await (0, common_1.getAuthHeaders)(this.http, registryHost, dockerRepository);
if (!headers) {
logger_1.logger.warn('No docker auth found - returning');
return null;
}
headers.accept = [
'application/vnd.docker.distribution.manifest.list.v2+json',
'application/vnd.docker.distribution.manifest.v2+json',
'application/vnd.oci.image.manifest.v1+json',
'application/vnd.oci.image.index.v1+json',
].join(', ');
const url = `${registryHost}/v2/${dockerRepository}/manifests/${tag}`;
const manifestResponse = await this.http[mode](url, {
headers,
noAuth: true,
cacheProvider: memory_http_cache_provider_1.memCacheProvider,
});
return manifestResponse;
}
catch (err) /* istanbul ignore next */ {
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
if (err.statusCode === 401) {
logger_1.logger.debug({ registryHost, dockerRepository }, 'Unauthorized docker lookup');
logger_1.logger.debug({ err });
return null;
}
if (err.statusCode === 404) {
logger_1.logger.debug({
err,
registryHost,
dockerRepository,
tag,
}, 'Docker Manifest is unknown');
return null;
}
if (err.statusCode === 429 && (0, common_1.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.code === 'ETIMEDOUT') {
logger_1.logger.debug({ registryHost }, 'Timeout when attempting to connect to docker registry');
logger_1.logger.debug({ err });
return null;
}
logger_1.logger.debug({
err,
registryHost,
dockerRepository,
tag,
}, 'Unknown Error looking up docker manifest');
return null;
}
}
async getImageConfig(registryHost, dockerRepository, configDigest) {
logger_1.logger.trace(`getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`);
const headers = await (0, common_1.getAuthHeaders)(this.http, registryHost, dockerRepository);
/* v8 ignore next 4 -- should never happen */
if (!headers) {
logger_1.logger.warn('No docker auth found - returning');
return undefined;
}
const url = (0, url_1.joinUrlParts)(registryHost, 'v2', dockerRepository, 'blobs', configDigest);
return await this.http.getJson(url, {
headers,
noAuth: true,
}, schema_1.OciImageConfig);
}
async getHelmConfig(registryHost, dockerRepository, configDigest) {
logger_1.logger.trace(`getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`);
const headers = await (0, common_1.getAuthHeaders)(this.http, registryHost, dockerRepository);
/* v8 ignore next 4 -- should never happen */
if (!headers) {
logger_1.logger.warn('No docker auth found - returning');
return undefined;
}
const url = (0, url_1.joinUrlParts)(registryHost, 'v2', dockerRepository, 'blobs', configDigest);
return await this.http.getJson(url, {
headers,
noAuth: true,
}, schema_1.OciHelmConfig);
}
async getConfigDigest(registry, dockerRepository, tag) {
return ((await this.getManifest(registry, dockerRepository, tag))?.config
?.digest ?? null);
}
async getManifest(registry, dockerRepository, tag) {
const manifestResponse = await this.getManifestResponse(registry, dockerRepository, tag);
// If getting the manifest fails here, then abort
// This means that the latest tag doesn't have a manifest, which shouldn't
// be possible
/* v8 ignore next 3 -- should never happen */
if (!manifestResponse) {
return null;
}
// Softfail on invalid manifests
const parsed = schema_1.ManifestJson.safeParse(manifestResponse.body);
if (!parsed.success) {
logger_1.logger.debug({
registry,
dockerRepository,
tag,
body: manifestResponse.body,
headers: manifestResponse.headers,
err: parsed.error,
}, 'Invalid manifest response');
return null;
}
const manifest = parsed.data;
switch (manifest.mediaType) {
case 'application/vnd.docker.distribution.manifest.v2+json':
case 'application/vnd.oci.image.manifest.v1+json':
return manifest;
case 'application/vnd.docker.distribution.manifest.list.v2+json':
case 'application/vnd.oci.image.index.v1+json':
if (!manifest.manifests.length) {
logger_1.logger.debug({ manifest }, 'Invalid manifest list with no manifests - returning');
return null;
}
logger_1.logger.trace({ registry, dockerRepository, tag }, 'Found manifest list, using first image');
return this.getManifest(registry, dockerRepository, manifest.manifests[0].digest);
// istanbul ignore next: can't happen
default:
return null;
}
}
async getImageArchitecture(registryHost, dockerRepository, currentDigest) {
try {
let manifestResponse;
try {
manifestResponse = await this.getManifestResponse(registryHost, dockerRepository, currentDigest, 'head');
}
catch (_err) {
const err = _err instanceof external_host_error_1.ExternalHostError
? _err.err
: /* istanbul ignore next: can never happen */ _err;
if (typeof err.statusCode === 'number' &&
err.statusCode >= 500 &&
err.statusCode < 600) {
// querying the digest manifest for a non existent image leads to a 500 statusCode
return null;
}
/* istanbul ignore next */
throw _err;
}
if (manifestResponse?.headers['content-type'] !==
'application/vnd.docker.distribution.manifest.v2+json' &&
manifestResponse?.headers['content-type'] !==
'application/vnd.oci.image.manifest.v1+json') {
return null;
}
const configDigest = await this.getConfigDigest(registryHost, dockerRepository, currentDigest);
if (!configDigest) {
return null;
}
const configResponse = await this.getImageConfig(registryHost, dockerRepository, configDigest);
// TODO: fix me, architecture is required in spec
if (configResponse &&
('config' in configResponse.body ||
'architecture' in configResponse.body)) {
const architecture = configResponse.body.architecture ?? null;
logger_1.logger.debug(`Current digest ${currentDigest} relates to architecture ${architecture ?? 'null'}`);
return architecture;
}
}
catch (err) /* istanbul ignore next */ {
if (err.statusCode !== 404 || err.message === error_messages_1.PAGE_NOT_FOUND_ERROR) {
throw err;
}
logger_1.logger.debug({ registryHost, dockerRepository, currentDigest, err }, 'Unknown error getting image architecture');
}
return undefined;
}
/*
* docker.getLabels
*
* This function will:
* - Return the labels for the requested image
*/
async getLabels(registryHost, dockerRepository, tag) {
logger_1.logger.debug(`getLabels(${registryHost}, ${dockerRepository}, ${tag})`);
// Skip Docker Hub image if RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP is set
if ((0, env_1.getEnv)().RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP &&
registryHost === 'https://index.docker.io') {
logger_1.logger.debug('Docker Hub image - skipping label lookup due to RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP');
return {};
}
// Docker Hub library images don't have labels we need
if (registryHost === 'https://index.docker.io' &&
dockerRepository.startsWith('library/')) {
logger_1.logger.debug('Docker Hub library image - skipping label lookup');
return {};
}
try {
let labels = {};
const manifest = await this.getManifest(registryHost, dockerRepository, tag);
if (!manifest) {
logger_1.logger.debug({ registryHost, dockerRepository, tag }, 'No manifest found');
return undefined;
}
if ('annotations' in manifest && manifest.annotations) {
labels = manifest.annotations;
}
switch (manifest.config.mediaType) {
case 'application/vnd.cncf.helm.config.v1+json': {
if (labels[common_1.sourceLabel]) {
// we already have the source url, so no need to pull the config
return labels;
}
const configResponse = await this.getHelmConfig(registryHost, dockerRepository, manifest.config.digest);
if (configResponse) {
// Helm chart
const url = (0, common_1.findHelmSourceUrl)(configResponse.body);
if (url) {
labels[common_1.sourceLabel] = url;
}
}
break;
}
case 'application/vnd.oci.image.config.v1+json':
case 'application/vnd.docker.container.image.v1+json': {
if (labels[common_1.sourceLabel] && labels[common_1.gitRefLabel]) {
// we already have the source url, so no need to pull the config
return labels;
}
const configResponse = await this.getImageConfig(registryHost, dockerRepository, manifest.config.digest);
/* v8 ignore next 3 -- should never happen */
if (!configResponse) {
return labels;
}
const body = configResponse.body;
if (body.config) {
labels = { ...labels, ...body.config.Labels };
}
else {
logger_1.logger.debug({ headers: configResponse.headers, body }, `manifest blob response body missing the "config" property`);
}
break;
}
}
if (labels) {
logger_1.logger.debug({
labels,
}, 'found labels in manifest');
}
return labels;
}
catch (err) /* istanbul ignore next: should be tested in future */ {
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
if (err.statusCode === 400 || err.statusCode === 401) {
logger_1.logger.debug({ registryHost, dockerRepository, err }, 'Unauthorized docker lookup');
}
else if (err.statusCode === 404) {
logger_1.logger.warn({
err,
registryHost,
dockerRepository,
tag,
}, 'Config Manifest is unknown');
}
else if (err.statusCode === 429 && (0, common_1.isDockerHost)(registryHost)) {
logger_1.logger.warn({ err }, 'docker registry failure: too many requests');
}
else if (err.statusCode >= 500 && err.statusCode < 600) {
logger_1.logger.debug({
err,
registryHost,
dockerRepository,
tag,
}, 'docker registry failure: internal error');
}
else if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID' ||
err.code === 'ETIMEDOUT') {
logger_1.logger.debug({ registryHost, err }, 'Error connecting to docker registry');
}
else if (registryHost === 'https://quay.io') {
// istanbul ignore next
logger_1.logger.debug('Ignoring quay.io errors until they fully support v2 schema');
}
else {
logger_1.logger.info({ registryHost, dockerRepository, tag, err }, 'Unknown error getting Docker labels');
}
return {};
}
}
async getTagsQuayRegistry(registry, repository) {
let tags = [];
const limit = 100;
const pageUrl = (page) => `${registry}/api/v1/repository/${repository}/tag/?limit=${limit}&page=${page}&onlyActiveTags=true`;
let page = 1;
let url = pageUrl(page);
while (url && page <= 20) {
// typescript issue :-/
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const res = (await this.http.getJsonUnchecked(url));
const pageTags = res.body.tags.map((tag) => tag.name);
tags = tags.concat(pageTags);
page += 1;
url = res.body.has_additional ? pageUrl(page) : null;
}
return tags;
}
async getDockerApiTags(registryHost, dockerRepository) {
let tags = [];
// AWS ECR limits the maximum number of results to 1000
// See https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html#ECR-DescribeRepositories-request-maxResults
// See https://docs.aws.amazon.com/AmazonECRPublic/latest/APIReference/API_DescribeRepositories.html#ecrpublic-DescribeRepositories-request-maxResults
const limit = ecr_1.ecrRegex.test(registryHost) || ecr_1.ecrPublicRegex.test(registryHost)
? 1000
: 10000;
let url = `${registryHost}/${dockerRepository}/tags/list?n=${limit}`;
url = (0, url_1.ensurePathPrefix)(url, '/v2');
const headers = await (0, common_1.getAuthHeaders)(this.http, registryHost, dockerRepository, url);
if (!headers) {
logger_1.logger.debug('Failed to get authHeaders for getTags lookup');
return null;
}
let page = 0;
const hostsNeedingAllPages = [
'https://ghcr.io', // GHCR sorts from oldest to newest, so we need to get all pages
'https://quay.io', // Quay sorts from oldest to newest, so we need to get all pages
];
const pages = hostsNeedingAllPages.includes(registryHost)
? 1000
: global_1.GlobalConfig.get('dockerMaxPages', 20);
logger_1.logger.trace({ registryHost, dockerRepository, pages }, 'docker.getTags');
let foundMaxResultsError = false;
do {
let res;
try {
res = await this.http.getJsonUnchecked(url, {
headers,
noAuth: true,
});
}
catch (err) {
if (!foundMaxResultsError &&
err instanceof http_1.HttpError &&
(0, ecr_1.isECRMaxResultsError)(err)) {
const maxResults = 1000;
url = `${registryHost}/${dockerRepository}/tags/list?n=${maxResults}`;
url = (0, url_1.ensurePathPrefix)(url, '/v2');
foundMaxResultsError = true;
continue;
}
throw err;
}
tags = tags.concat(res.body.tags);
const linkHeader = (0, url_1.parseLinkHeader)(res.headers.link);
if ((0, util_1.isArtifactoryServer)(res)) {
// Artifactory bug: next link comes back without virtual-repo prefix (RTFACT-18971)
if (linkHeader?.next?.last) {
// parse the current URL, strip any old "last" param, then set the new one
const parsed = new URL(url);
parsed.searchParams.delete('last');
parsed.searchParams.set('last', linkHeader.next.last);
url = parsed.href;
}
else {
url = null;
}
}
else if (linkHeader?.next?.url) {
// for the normal case we can still use URL to resolve relative-next
url = new URL(linkHeader.next.url, url).href;
}
else {
url = null;
}
page += 1;
} while (url && page < pages);
return tags;
}
async getTags(registryHost, dockerRepository) {
try {
const isQuay = registryHost === 'https://quay.io';
let tags;
if (isQuay) {
try {
// Due to pagination and sorting limits on Quay Docker v2 API implementation we try the Quay v1 API first
tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);
}
catch (err) {
// If we get a 401 Unauthorized error (v1 API requires separate auth for private images), fall back to Docker v2 API
if (err.statusCode === 401) {
logger_1.logger.debug({ registryHost, dockerRepository }, 'Quay v1 API unauthorized, falling back to Docker v2 API');
tags = await this.getDockerApiTags(registryHost, dockerRepository);
}
else {
throw err;
}
}
}
else {
tags = await this.getDockerApiTags(registryHost, dockerRepository);
}
return tags;
}
catch (_err) /* istanbul ignore next */ {
const err = _err instanceof external_host_error_1.ExternalHostError ? _err.err : _err;
if ((err.statusCode === 404 || err.message === error_messages_1.PAGE_NOT_FOUND_ERROR) &&
!dockerRepository.includes('/')) {
logger_1.logger.debug(`Retrying Tags for ${registryHost}/${dockerRepository} using library/ prefix`);
return this.getTags(registryHost, 'library/' + dockerRepository);
}
// JFrog Artifactory - Retry handling when resolving Docker Official Images
// These follow the format of {{registryHost}}{{jFrogRepository}}/library/{{dockerRepository}}
if ((err.statusCode === 404 || err.message === error_messages_1.PAGE_NOT_FOUND_ERROR) &&
(0, util_1.isArtifactoryServer)(err.response) &&
dockerRepository.split('/').length === 2) {
logger_1.logger.debug(`JFrog Artifactory: Retrying Tags for ${registryHost}/${dockerRepository} using library/ path between JFrog virtual repository and image`);
const dockerRepositoryParts = dockerRepository.split('/');
const jfrogRepository = dockerRepositoryParts[0];
const dockerImage = dockerRepositoryParts[1];
return this.getTags(registryHost, jfrogRepository + '/library/' + dockerImage);
}
if (err.statusCode === 429 && (0, common_1.isDockerHost)(registryHost)) {
logger_1.logger.warn({ registryHost, dockerRepository, err }, 'docker registry failure: too many requests');
throw new external_host_error_1.ExternalHostError(err);
}
if (err.statusCode >= 500 && err.statusCode < 600) {
logger_1.logger.warn({ registryHost, dockerRepository, err }, 'docker registry failure: internal error');
throw new external_host_error_1.ExternalHostError(err);
}
const errorCodes = ['ECONNRESET', 'ETIMEDOUT'];
if (errorCodes.includes(err.code)) {
logger_1.logger.warn({ registryHost, dockerRepository, err }, 'docker registry connection failure');
throw new external_host_error_1.ExternalHostError(err);
}
if ((0, common_1.isDockerHost)(registryHost)) {
logger_1.logger.info({ err }, 'Docker Hub lookup failure');
}
throw _err;
}
}
/**
* docker.getDigest
*
* The `newValue` supplied here should be a valid tag for the docker image.
*
* This function will:
* - Look up a sha256 digest for a tag on its registry
* - Return the digest as a string
*/
async getDigest({ registryUrl, lookupName, packageName, currentDigest }, newValue) {
let registryHost;
let dockerRepository;
if (registryUrl && lookupName) {
// Reuse the resolved values from getReleases()
registryHost = registryUrl;
dockerRepository = lookupName;
}
else {
// Resolve values independently
({ registryHost, dockerRepository } = (0, common_1.getRegistryRepository)(packageName, registryUrl));
}
logger_1.logger.debug(
// TODO: types (#22198)
`getDigest(${registryHost}, ${dockerRepository}, ${newValue})`);
const newTag = is_1.default.nonEmptyString(newValue) ? newValue : 'latest';
let digest = null;
try {
let architecture = null;
if (currentDigest && (0, string_match_1.isDockerDigest)(currentDigest)) {
architecture = await this.getImageArchitecture(registryHost, dockerRepository, currentDigest);
}
let manifestResponse = null;
if (!architecture) {
manifestResponse = await this.getManifestResponse(registryHost, dockerRepository, newTag, 'head');
if (manifestResponse &&
(0, object_1.hasKey)('docker-content-digest', manifestResponse.headers)) {
digest =
manifestResponse.headers['docker-content-digest'] ||
null;
}
}
if (is_1.default.string(architecture) ||
(manifestResponse &&
!(0, object_1.hasKey)('docker-content-digest', manifestResponse.headers))) {
logger_1.logger.debug({ registryHost, dockerRepository }, 'Architecture-specific digest or missing docker-content-digest header - pulling full manifest');
manifestResponse = await this.getManifestResponse(registryHost, dockerRepository, newTag);
if (architecture && manifestResponse) {
const parsed = schema_1.ManifestJson.safeParse(manifestResponse.body);
/* istanbul ignore else: hard to test */
if (parsed.success) {
const manifestList = parsed.data;
if (manifestList.mediaType ===
'application/vnd.docker.distribution.manifest.list.v2+json' ||
manifestList.mediaType ===
'application/vnd.oci.image.index.v1+json') {
for (const manifest of manifestList.manifests) {
if (manifest.platform?.architecture === architecture) {
digest = manifest.digest;
break;
}
}
// TODO: return null if no matching architecture digest found
// https://github.com/renovatebot/renovate/discussions/22639
}
else if ((0, object_1.hasKey)('docker-content-digest', manifestResponse.headers)) {
// TODO: return null if no matching architecture, requires to fetch the config manifest
// https://github.com/renovatebot/renovate/discussions/22639
digest = manifestResponse.headers['docker-content-digest'];
}
}
else {
logger_1.logger.debug({
registryHost,
dockerRepository,
newTag,
body: manifestResponse.body,
headers: manifestResponse.headers,
err: parsed.error,
}, 'Failed to parse manifest response');
}
}
if (!digest) {
logger_1.logger.debug({ registryHost, dockerRepository, newTag }, 'Extraction digest from manifest response body is deprecated');
digest = (0, common_1.extractDigestFromResponseBody)(manifestResponse);
}
}
if (!manifestResponse &&
!dockerRepository.includes('/') &&
!packageName.includes('/')) {
logger_1.logger.debug(`Retrying Digest for ${registryHost}/${dockerRepository} using library/ prefix`);
return this.getDigest({
registryUrl,
packageName: 'library/' + packageName,
currentDigest,
}, newValue);
}
if (manifestResponse) {
// TODO: fix types (#22198)
logger_1.logger.debug(`Got docker digest ${digest}`);
}
}
catch (err) /* istanbul ignore next */ {
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
logger_1.logger.debug({
err,
packageName,
newTag,
}, 'Unknown Error looking up docker image digest');
}
return digest;
}
async getDockerHubTags(dockerRepository) {
let url = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=1000&ordering=last_updated`;
const cache = await dockerhub_cache_1.DockerHubCache.init(dockerRepository);
const maxPages = global_1.GlobalConfig.get('dockerMaxPages', 20);
let page = 0, needNextPage = true;
while (needNextPage && page < maxPages) {
const { val, err } = await this.http
.getJsonSafe(url, schema_1.DockerHubTagsPage)
.unwrap();
if (err) {
logger_1.logger.debug({ err }, `Docker: error fetching data from DockerHub`);
return null;
}
page++;
const { results, next, count } = val;
needNextPage = cache.reconcile(results, count);
if (!next) {
break;
}
url = next;
}
await cache.save();
const items = cache.getItems();
return items.map(({ name: version, tag_last_pushed, digest: newDigest }) => {
const release = { version };
const releaseTimestamp = (0, timestamp_1.asTimestamp)(tag_last_pushed);
if (releaseTimestamp) {
release.releaseTimestamp = releaseTimestamp;
}
if (newDigest) {
release.newDigest = newDigest;
}
return release;
});
}
/**
* docker.getReleases
*
* A docker image usually looks something like this: somehost.io/owner/repo:8.1.0-alpine
* In the above:
* - 'somehost.io' is the registry
* - 'owner/repo' is the package name
* - '8.1.0-alpine' is the tag
*
* This function will filter only tags that contain a semver version
*/
async getReleases({ packageName, registryUrl, }) {
const { registryHost, dockerRepository } = (0, common_1.getRegistryRepository)(packageName, registryUrl);
const getTags = () => result_1.Result.wrapNullable(this.getTags(registryHost, dockerRepository), 'tags-error').transform((tags) => tags.map((version) => ({ version })));
const getDockerHubTags = () => result_1.Result.wrapNullable(this.getDockerHubTags(dockerRepository), 'dockerhub-error').catch(getTags);
const tagsResult = registryHost === 'https://index.docker.io' &&
!(0, env_1.getEnv)().RENOVATE_X_DOCKER_HUB_TAGS_DISABLE
? getDockerHubTags()
: getTags();
const { val: releases, err } = await tagsResult.unwrap();
if (err instanceof Error) {
throw err;
}
else if (err) {
return null;
}
const ret = {
registryUrl: registryHost,
releases,
};
if (dockerRepository !== packageName) {
// This will be reused later if a getDigest() call is made
ret.lookupName = dockerRepository;
}
const tags = releases.map((release) => release.version);
const latestTag = tags.includes('latest')
? 'latest'
: ((0, common_1.findLatestStable)(tags) ?? tags[tags.length - 1]);
/* v8 ignore next 3 -- TODO: add test */
if (!latestTag) {
return ret;
}
const labels = await this.getLabels(registryHost, dockerRepository, latestTag);
if (labels) {
if (is_1.default.nonEmptyString(labels[common_1.gitRefLabel])) {
ret.gitRef = labels[common_1.gitRefLabel];
}
for (const label of common_1.sourceLabels) {
if (is_1.default.nonEmptyString(labels[label])) {
ret.sourceUrl = labels[label];
break;
}
}
if (is_1.default.nonEmptyString(labels[common_1.imageUrlLabel])) {
ret.homepage = labels[common_1.imageUrlLabel];
}
}
return ret;
}
}
exports.DockerDatasource = DockerDatasource;
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-imageconfig',
key: (registryHost, dockerRepository, configDigest) => `${registryHost}:${dockerRepository}@${configDigest}`,
ttlMinutes: 1440 * 28,
})
], DockerDatasource.prototype, "getImageConfig", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-imageconfig',
key: (registryHost, dockerRepository, configDigest) => `${registryHost}:${dockerRepository}@${configDigest}`,
ttlMinutes: 1440 * 28,
})
], DockerDatasource.prototype, "getHelmConfig", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-architecture',
key: (registryHost, dockerRepository, currentDigest) => `${registryHost}:${dockerRepository}@${currentDigest}`,
ttlMinutes: 1440 * 28,
})
], DockerDatasource.prototype, "getImageArchitecture", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-labels',
key: (registryHost, dockerRepository, tag) => `${registryHost}:${dockerRepository}:${tag}`,
ttlMinutes: 24 * 60,
})
], DockerDatasource.prototype, "getLabels", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-tags',
key: (registryHost, dockerRepository) => `${registryHost}:${dockerRepository}`,
})
], DockerDatasource.prototype, "getTags", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-digest',
key: ({ registryUrl, packageName, currentDigest }, newValue) => {
const newTag = newValue ?? 'latest';
const { registryHost, dockerRepository } = (0, common_1.getRegistryRepository)(packageName, registryUrl);
const digest = currentDigest ? `@${currentDigest}` : '';
return `${registryHost}:${dockerRepository}:${newTag}${digest}`;
},
})
], DockerDatasource.prototype, "getDigest", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-hub-tags',
key: (dockerRepository) => `${dockerRepository}`,
})
], DockerDatasource.prototype, "getDockerHubTags", null);
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: 'datasource-docker-releases-v2',
key: ({ registryUrl, packageName }) => {
const { registryHost, dockerRepository } = (0, common_1.getRegistryRepository)(packageName, registryUrl);
return `${registryHost}:${dockerRepository}`;
},
cacheable: ({ registryUrl, packageName }) => {
const { registryHost } = (0, common_1.getRegistryRepository)(packageName, registryUrl);
return registryHost === 'https://index.docker.io';
},
})
], DockerDatasource.prototype, "getReleases", null);
//# sourceMappingURL=index.js.map