diff --git a/lib/datasource/docker/__snapshots__/index.spec.ts.snap b/lib/datasource/docker/__snapshots__/index.spec.ts.snap index be528e281181c5fa1e281bb5ae4faf78e06b041e..21c6a14bd9010be1624fb6678330026c89a1533e 100644 --- a/lib/datasource/docker/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/docker/__snapshots__/index.spec.ts.snap @@ -773,7 +773,26 @@ Array [ exports[`datasource/docker/index getReleases supports labels 1`] = ` Object { "registryUrl": "https://index.docker.io", - "releases": Array [], + "releases": Array [ + Object { + "version": "1.0.0", + }, + Object { + "version": "1.2.3-alpine", + }, + Object { + "version": "1.2.3", + }, + Object { + "version": "1-alpine", + }, + Object { + "version": "2.0.0", + }, + Object { + "version": "2-alpine", + }, + ], "sourceUrl": "https://github.com/renovatebot/renovate", } `; @@ -820,7 +839,7 @@ Array [ "user-agent": "https://github.com/renovatebot/renovate", }, "method": "GET", - "url": "https://registry.company.com/v2/node/manifests/latest", + "url": "https://registry.company.com/v2/node/manifests/2-alpine", }, Object { "headers": Object { @@ -895,7 +914,7 @@ Array [ "user-agent": "https://github.com/renovatebot/renovate", }, "method": "GET", - "url": "https://registry.company.com/v2/node/manifests/latest", + "url": "https://registry.company.com/v2/node/manifests/abc", }, Object { "headers": Object { diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts index 99b397c594be8bf87a024013f0ae2519ce945468..7be603c7cb522e30108636bf31fce85826ba422d 100644 --- a/lib/datasource/docker/index.spec.ts +++ b/lib/datasource/docker/index.spec.ts @@ -623,8 +623,18 @@ describe(getName(), () => { .times(3) .reply(200) .get('/node/tags/list?n=10000') - .reply(200, { tags: ['latest'] }) - .get('/node/manifests/latest') + .reply(200, { + tags: [ + '2.0.0', + '2-alpine', + '1-alpine', + '1.0.0', + '1.2.3', + '1.2.3-alpine', + 'abc', + ], + }) + .get('/node/manifests/2-alpine') .reply(200, { schemaVersion: 2, mediaType: MediaType.manifestV2, @@ -655,8 +665,8 @@ describe(getName(), () => { .times(4) .reply(200) .get('/node/tags/list?n=10000') - .reply(200, { tags: ['latest'] }) - .get('/node/manifests/latest') + .reply(200, { tags: ['abc'] }) + .get('/node/manifests/abc') .reply(200, { schemaVersion: 2, mediaType: MediaType.manifestListV2, diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts index b4aec34b47dfb07ce473e08c6af066cb8d3f36e2..843f058ece48022374c7ff79f9a696d9bee9c3b9 100644 --- a/lib/datasource/docker/index.ts +++ b/lib/datasource/docker/index.ts @@ -3,7 +3,10 @@ import parseLinkHeader from 'parse-link-header'; import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as packageCache from '../../util/cache/package'; -import * as dockerVersioning from '../../versioning/docker'; +import { + api as dockerVersioning, + id as dockerVersioningId, +} from '../../versioning/docker'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import { defaultRegistryUrls, @@ -24,7 +27,7 @@ import { getTagsQuayRegistry } from './quay'; export { id }; export const customRegistrySupport = true; export { defaultRegistryUrls }; -export const defaultVersioning = dockerVersioning.id; +export const defaultVersioning = dockerVersioningId; export const registryStrategy = 'first'; export const defaultConfig = { @@ -142,6 +145,14 @@ async function getTags( } } +function findLatestStable(tags: string[]): string { + const versions = tags + .filter((v) => dockerVersioning.isValid(v) && dockerVersioning.isStable(v)) + .sort((a, b) => dockerVersioning.sortVersions(a, b)); + + return versions.pop() ?? tags.slice(-1).pop(); +} + /** * docker.getDigest * @@ -228,7 +239,7 @@ export async function getReleases({ releases, }; - const latestTag = tags.includes('latest') ? 'latest' : tags[tags.length - 1]; + const latestTag = tags.includes('latest') ? 'latest' : findLatestStable(tags); const labels = await getLabels(registryHost, dockerRepository, latestTag); if (labels && 'org.opencontainers.image.source' in labels) { ret.sourceUrl = labels['org.opencontainers.image.source'];