"use strict";
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 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 http_1 = require("../../../util/http");
const object_1 = require("../../../util/object");
const regex_1 = require("../../../util/regex");
const result_1 = require("../../../util/result");
const string_match_1 = require("../../../util/string-match");
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 thre 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 = 'get') {
        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,
            });
            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);
        // istanbul ignore if: 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);
        // istanbul ignore if: 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
        // istanbul ignore if
        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 (process.env.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);
                    // istanbul ignore if: 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.getJson(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 pages = process.env.RENOVATE_X_DOCKER_MAX_PAGES
            ? parseInt(process.env.RENOVATE_X_DOCKER_MAX_PAGES, 10)
            : 20;
        let foundMaxResultsError = false;
        do {
            let res;
            try {
                res = await this.http.getJson(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 incorrectly returns a next link without the virtual repository name
                // this is due to a bug in Artifactory https://jfrog.atlassian.net/browse/RTFACT-18971
                url = linkHeader?.next?.last
                    ? `${url}&last=${linkHeader.next.last}`
                    : null;
            }
            else {
                url = linkHeader?.next ? new URL(linkHeader.next.url, url).href : null;
            }
            page += 1;
        } while (url && page < pages);
        return tags;
    }
    async getTags(registryHost, dockerRepository) {
        try {
            const isQuay = (0, regex_1.regEx)(/^https:\/\/quay\.io(?::[1-9][0-9]{0,4})?$/i).test(registryHost);
            let tags;
            if (isQuay) {
                tags = await this.getTagsQuayRegistry(registryHost, dockerRepository);
            }
            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 = 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);
        let needNextPage = true;
        while (needNextPage) {
            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;
            }
            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: releaseTimestamp, digest: newDigest, }) => {
            const release = { version };
            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' &&
            !process.env.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]);
        // istanbul ignore if: needs 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