diff --git a/lib/modules/datasource/docker/common.ts b/lib/modules/datasource/docker/common.ts index 0aac62a42c3306285d4172be605eb05f61ade9ae..adec543111a96004dfb8761058ac6c60e10af27d 100644 --- a/lib/modules/datasource/docker/common.ts +++ b/lib/modules/datasource/docker/common.ts @@ -1,6 +1,15 @@ +import is from '@sindresorhus/is'; +import type { HttpResponse } from '../../../util/http/types'; + export const sourceLabels: string[] = [ 'org.opencontainers.image.source', 'org.label-schema.vcs-url', ]; export const gitRefLabel = 'org.opencontainers.image.revision'; + +const JFROG_ARTIFACTORY_RES_HEADER = 'x-jfrog-version'; + +export function isArtifactoryServer(res: HttpResponse): boolean { + return is.string(res.headers[JFROG_ARTIFACTORY_RES_HEADER]); +} diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts index 76332d86c8acc2aaada1077b9b1041dbcccec4e1..368967e4b2e1b0e25aa1ce038afd7827beab1950 100644 --- a/lib/modules/datasource/docker/index.spec.ts +++ b/lib/modules/datasource/docker/index.spec.ts @@ -1170,6 +1170,29 @@ describe('modules/datasource/docker/index', () => { await expect(getPkgReleases(config)).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('jfrog artifactory - retry tags for official images by injecting `/library` after repository and before image', async () => { + const tags = ['18.0.0']; + httpMock + .scope('https://org.jfrog.io/v2') + .get('/virtual-mirror/node/tags/list?n=10000') + .reply(200, '', {}) + .get('/virtual-mirror/node/tags/list?n=10000') + .reply(404, '', { 'x-jfrog-version': 'Artifactory/7.42.2 74202900' }) + .get('/virtual-mirror/library/node/tags/list?n=10000') + .reply(200, '', {}) + .get('/virtual-mirror/library/node/tags/list?n=10000') + .reply(200, { tags }, {}) + .get('/') + .reply(200, '', {}) + .get('/virtual-mirror/node/manifests/18.0.0') + .reply(200, '', {}); + const res = await getPkgReleases({ + datasource: DockerDatasource.id, + depName: 'org.jfrog.io/virtual-mirror/node', + }); + expect(res?.releases).toHaveLength(1); + }); + it('uses lower tag limit for ECR deps', async () => { httpMock .scope(amazonUrl) diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts index d9c5fc7c311c25d3ff67a2f0c9f95376eca8c3ed..97a62280c216392450e9f43672ef4ea079247213 100644 --- a/lib/modules/datasource/docker/index.ts +++ b/lib/modules/datasource/docker/index.ts @@ -35,7 +35,7 @@ import { } from '../../versioning/docker'; import { Datasource } from '../datasource'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import { gitRefLabel, sourceLabels } from './common'; +import { gitRefLabel, isArtifactoryServer, sourceLabels } from './common'; import { Image, ImageConfig, @@ -902,6 +902,26 @@ export class DockerDatasource extends Datasource { ); 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 === PAGE_NOT_FOUND_ERROR) && + isArtifactoryServer(err.response) && + dockerRepository.split('/').length === 2 + ) { + 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 + ); + } // prettier-ignore if (err.statusCode === 429 && isDockerHost(registryHost)) { logger.warn(