diff --git a/lib/datasource/docker/__snapshots__/index.spec.ts.snap b/lib/datasource/docker/__snapshots__/index.spec.ts.snap index 78708a7a56e6c784294bb671dbd998a6a7ff4566..856978c4b17728511f953d72d9a674a06d1dcd87 100644 --- a/lib/datasource/docker/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/docker/__snapshots__/index.spec.ts.snap @@ -565,6 +565,116 @@ Array [ ] `; +exports[`datasource/docker/index getReleases ignores unsupported manifest 1`] = ` +Object { + "dockerRegistry": "https://registry.company.com", + "dockerRepository": "node", + "releases": Array [], +} +`; + +exports[`datasource/docker/index getReleases ignores unsupported manifest 2`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/tags/list?n=10000", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/", + }, + Object { + "headers": Object { + "accept": "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/manifests/latest", + }, +] +`; + +exports[`datasource/docker/index getReleases ignores unsupported schema version 1`] = ` +Object { + "dockerRegistry": "https://registry.company.com", + "dockerRepository": "node", + "releases": Array [], +} +`; + +exports[`datasource/docker/index getReleases ignores unsupported schema version 2`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/tags/list?n=10000", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/", + }, + Object { + "headers": Object { + "accept": "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/manifests/latest", + }, +] +`; + exports[`datasource/docker/index getReleases returns null if no auth 1`] = ` Array [ Object { @@ -779,6 +889,27 @@ Array [ "method": "GET", "url": "https://registry.company.com/v2/", }, + Object { + "headers": Object { + "accept": "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/manifests/some-image-digest", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/", + }, Object { "headers": Object { "accept-encoding": "gzip, deflate", diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts index 0b54f5929099ca1e3e25ee580962838a03434258..90a144ff61361106ec3e75aede00fc1eaed82755 100644 --- a/lib/datasource/docker/index.spec.ts +++ b/lib/datasource/docker/index.spec.ts @@ -570,6 +570,7 @@ describe(getName(__filename), () => { .get('/node/manifests/latest') .reply(200, { schemaVersion: 2, + mediaType: MediaType.manifestV2, config: { digest: 'some-config-digest' }, }) .get('/node/blobs/some-config-digest') @@ -594,7 +595,7 @@ describe(getName(__filename), () => { httpMock .scope('https://registry.company.com/v2') .get('/') - .times(3) + .times(4) .reply(200) .get('/node/tags/list?n=10000') .reply(200, { tags: ['latest'] }) @@ -602,7 +603,13 @@ describe(getName(__filename), () => { .reply(200, { schemaVersion: 2, mediaType: MediaType.manifestListV2, - manifests: [{ digest: 'some-config-digest' }], + manifests: [{ digest: 'some-image-digest' }], + }) + .get('/node/manifests/some-image-digest') + .reply(200, { + schemaVersion: 2, + mediaType: MediaType.manifestV2, + config: { digest: 'some-config-digest' }, }) .get('/node/blobs/some-config-digest') .reply(200, { @@ -622,6 +629,47 @@ describe(getName(__filename), () => { expect(trace).toMatchSnapshot(); }); + it('ignores unsupported manifest', async () => { + httpMock + .scope('https://registry.company.com/v2') + .get('/') + .times(2) + .reply(200) + .get('/node/tags/list?n=10000') + .reply(200, { tags: ['latest'] }) + .get('/node/manifests/latest') + .reply(200, { + schemaVersion: 2, + mediaType: MediaType.manifestV1, + }); + const res = await getPkgReleases({ + datasource: docker.id, + depName: 'registry.company.com/node', + }); + const trace = httpMock.getTrace(); + expect(res).toMatchSnapshot(); + expect(trace).toMatchSnapshot(); + }); + + it('ignores unsupported schema version', async () => { + httpMock + .scope('https://registry.company.com/v2') + .get('/') + .times(2) + .reply(200) + .get('/node/tags/list?n=10000') + .reply(200, { tags: ['latest'] }) + .get('/node/manifests/latest') + .reply(200, {}); + const res = await getPkgReleases({ + datasource: docker.id, + depName: 'registry.company.com/node', + }); + const trace = httpMock.getTrace(); + expect(res).toMatchSnapshot(); + expect(trace).toMatchSnapshot(); + }); + it('supports redirect', async () => { httpMock .scope('https://registry.company.com/v2') @@ -633,6 +681,7 @@ describe(getName(__filename), () => { .get('/node/manifests/latest') .reply(200, { schemaVersion: 2, + mediaType: MediaType.manifestV2, config: { digest: 'some-config-digest' }, }) .get('/node/blobs/some-config-digest') diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts index 6d1e3ece6ea42ee7dd135e59a7dcc243b8fd828a..1427e795ac4737b8de73a04c819c268217327904 100644 --- a/lib/datasource/docker/index.ts +++ b/lib/datasource/docker/index.ts @@ -328,6 +328,44 @@ async function getManifestResponse( } } +async function getConfigDigest( + registry: string, + repository: string, + tag: string +): Promise<string> { + const manifestResponse = await getManifestResponse(registry, repository, 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; + } + const manifest = JSON.parse(manifestResponse.body) as ImageList | Image; + if (manifest.schemaVersion !== 2) { + logger.debug( + { registry, dockerRepository: repository, tag }, + 'Manifest schema version is not 2' + ); + return null; + } + + if (manifest.mediaType === MediaType.manifestListV2) { + logger.trace( + { registry, dockerRepository: repository, tag }, + 'Found manifest list, using first image' + ); + return getConfigDigest(registry, repository, manifest.manifests[0].digest); + } + + if (manifest.mediaType === MediaType.manifestV2) { + return manifest.config.digest; + } + + logger.debug({ manifest }, 'Invalid manifest - returning'); + return null; +} + /** * docker.getDigest * @@ -475,43 +513,12 @@ async function getLabels( return cachedResult; } try { - const manifestResponse = await getManifestResponse( - registry, - repository, - 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) { - logger.debug( - { - registry, - dockerRepository: repository, - tag, - }, - 'docker registry failure: failed to get manifest for tag' - ); - return {}; - } - const manifest = JSON.parse(manifestResponse.body) as ImageList | Image; - // istanbul ignore if - if (manifest.schemaVersion !== 2) { - logger.debug( - { registry, dockerRepository: repository, tag }, - 'Manifest schema version is not 2' - ); + let labels: Record<string, string> = {}; + const configDigest = await getConfigDigest(registry, repository, tag); + if (!configDigest) { return {}; } - let labels: Record<string, string> = {}; - let configDigest: string; - if (manifest.mediaType === MediaType.manifestListV2) { - configDigest = manifest.manifests[0].digest; - } else { - configDigest = manifest.config.digest; - } const headers = await getAuthHeaders(registry, repository); // istanbul ignore if: Should never be happen if (!headers) {