feat(docker): use Docker Hub tags api (#23876)

This commit is contained in:
Rhys Arkins 2023-08-15 15:21:17 +02:00 committed by GitHub
parent da7fc430ed
commit a1f79bcf39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 16 deletions

View file

@ -21,6 +21,7 @@ const ecrMock = mockClient(ECRClient);
const baseUrl = 'https://index.docker.io/v2'; const baseUrl = 'https://index.docker.io/v2';
const authUrl = 'https://auth.docker.io'; const authUrl = 'https://auth.docker.io';
const amazonUrl = 'https://123456789.dkr.ecr.us-east-1.amazonaws.com/v2'; const amazonUrl = 'https://123456789.dkr.ecr.us-east-1.amazonaws.com/v2';
const dockerHubUrl = 'https://hub.docker.com/v2/repositories';
function mockEcrAuthResolve( function mockEcrAuthResolve(
res: Partial<GetAuthorizationTokenCommandOutput> = {} res: Partial<GetAuthorizationTokenCommandOutput> = {}
@ -41,6 +42,7 @@ describe('modules/datasource/docker/index', () => {
}); });
hostRules.hosts.mockReturnValue([]); hostRules.hosts.mockReturnValue([]);
delete process.env.RENOVATE_X_DOCKER_MAX_PAGES; delete process.env.RENOVATE_X_DOCKER_MAX_PAGES;
delete process.env.RENOVATE_X_DOCKER_HUB_TAGS;
}); });
describe('getDigest', () => { describe('getDigest', () => {
@ -1520,7 +1522,12 @@ describe('modules/datasource/docker/index', () => {
}); });
it('adds library/ prefix for Docker Hub (implicit)', async () => { it('adds library/ prefix for Docker Hub (implicit)', async () => {
process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true';
const tags = ['1.0.0']; const tags = ['1.0.0'];
httpMock
.scope(dockerHubUrl)
.get('/library/node/tags?page_size=100')
.reply(404);
httpMock httpMock
.scope(baseUrl) .scope(baseUrl)
.get('/library/node/tags/list?n=10000') .get('/library/node/tags/list?n=10000')
@ -1548,31 +1555,29 @@ describe('modules/datasource/docker/index', () => {
}); });
it('adds library/ prefix for Docker Hub (explicit)', async () => { it('adds library/ prefix for Docker Hub (explicit)', async () => {
const tags = ['1.0.0']; process.env.RENOVATE_X_DOCKER_HUB_TAGS = 'true';
httpMock
.scope(dockerHubUrl)
.get('/library/node/tags?page_size=100')
.reply(200, {
next: `${dockerHubUrl}/library/node/tags?page=2&page_size=100`,
results: [{ name: '1.0.0' }],
})
.get('/library/node/tags?page=2&page_size=100')
.reply(200, {
results: [{ name: '0.9.0' }],
});
httpMock httpMock
.scope(baseUrl) .scope(baseUrl)
.get('/library/node/tags/list?n=10000')
.reply(401, '', {
'www-authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/node:pull"',
})
.get('/library/node/tags/list?n=10000')
.reply(200, { tags }, {})
.get('/') .get('/')
.reply(200) .reply(200)
.get('/library/node/manifests/1.0.0') .get('/library/node/manifests/1.0.0')
.reply(200); .reply(200);
httpMock
.scope(authUrl)
.get(
'/token?service=registry.docker.io&scope=repository:library/node:pull'
)
.reply(200, { token: 'test' });
const res = await getPkgReleases({ const res = await getPkgReleases({
datasource: DockerDatasource.id, datasource: DockerDatasource.id,
packageName: 'docker.io/node', packageName: 'docker.io/node',
}); });
expect(res?.releases).toHaveLength(1); expect(res?.releases).toHaveLength(2);
}); });
it('adds no library/ prefix for other registries', async () => { it('adds no library/ prefix for other registries', async () => {

View file

@ -30,6 +30,7 @@ import {
} from './common'; } from './common';
import { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr'; import { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr';
import type { Manifest, OciImageConfig } from './schema'; import type { Manifest, OciImageConfig } from './schema';
import type { DockerHubTags } from './types';
const defaultConfig = { const defaultConfig = {
commitMessageTopic: '{{{depName}}} Docker tag', commitMessageTopic: '{{{depName}}} Docker tag',
@ -819,6 +820,36 @@ export class DockerDatasource extends Datasource {
return digest; return digest;
} }
async getDockerHubTags(dockerRepository: string): Promise<string[] | null> {
if (!process.env.RENOVATE_X_DOCKER_HUB_TAGS) {
return null;
}
try {
let index = 0;
let tags: string[] = [];
let url:
| string
| undefined = `https://hub.docker.com/v2/repositories/${dockerRepository}/tags?page_size=100`;
do {
const res: DockerHubTags = (await this.http.getJson<DockerHubTags>(url))
.body;
tags = tags.concat(res.results.map((tag) => tag.name));
url = res.next;
index += 1;
} while (url && index < 100);
logger.debug(
`getDockerHubTags(${dockerRepository}): found ${tags.length} tags`
);
return tags;
} catch (err) {
logger.debug(
{ dockerRepository, errMessage: err.message },
`No Docker Hub tags result - falling back to docker.io api`
);
}
return null;
}
/** /**
* docker.getReleases * docker.getReleases
* *
@ -838,7 +869,11 @@ export class DockerDatasource extends Datasource {
packageName, packageName,
registryUrl! registryUrl!
); );
const tags = await this.getTags(registryHost, dockerRepository); let tags: string[] | null = null;
if (registryHost === 'https://index.docker.io') {
tags = await this.getDockerHubTags(dockerRepository);
}
tags ??= await this.getTags(registryHost, dockerRepository);
if (!tags) { if (!tags) {
return null; return null;
} }

View file

@ -2,3 +2,8 @@ export interface RegistryRepository {
registryHost: string; registryHost: string;
dockerRepository: string; dockerRepository: string;
} }
export interface DockerHubTags {
next?: string;
results: { name: string }[];
}