diff --git a/lib/datasource/docker/__snapshots__/index.spec.ts.snap b/lib/datasource/docker/__snapshots__/index.spec.ts.snap index c2017225d3e206e96604fee9b7f429c143b6955d..701d6a7a2047b2b3969a5858413eac074649f07b 100644 --- a/lib/datasource/docker/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/docker/__snapshots__/index.spec.ts.snap @@ -14,7 +14,6 @@ Array [ }, Object { "headers": Object { - "accept": "application/json", "accept-encoding": "gzip, deflate", "host": "123456789.dkr.ecr.us-east-1.amazonaws.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -30,7 +29,6 @@ Array [ Object { "headers": Object { "accept-encoding": "gzip, deflate", - "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", "host": "123456789.dkr.ecr.us-east-1.amazonaws.com", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -39,7 +37,6 @@ Array [ }, Object { "headers": Object { - "accept": "application/json", "accept-encoding": "gzip, deflate", "host": "123456789.dkr.ecr.us-east-1.amazonaws.com", "user-agent": "https://github.com/renovatebot/renovate", @@ -118,7 +115,6 @@ Array [ Object { "headers": Object { "accept-encoding": "gzip, deflate", - "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", "host": "index.docker.io", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -129,7 +125,6 @@ Array [ "headers": Object { "accept": "application/json", "accept-encoding": "gzip, deflate", - "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", "host": "auth.docker.io", "user-agent": "https://github.com/renovatebot/renovate", }, @@ -164,7 +159,6 @@ Array [ }, Object { "headers": Object { - "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", "host": "index.docker.io", @@ -176,6 +170,32 @@ Array [ ] `; +exports[`datasource/docker/index getDigest returns null if empty header 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "index.docker.io", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://index.docker.io/v2/", + }, + Object { + "headers": Object { + "accept": "application/vnd.docker.distribution.manifest.v2+json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "index.docker.io", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://index.docker.io/v2/library/some-dep/manifests/some-new-value", + }, +] +`; + exports[`datasource/docker/index getDigest returns null if errored 1`] = ` Array [ Object { @@ -242,7 +262,6 @@ Array [ }, Object { "headers": Object { - "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "Basic abcdef", "host": "123456789.dkr.ecr.us-east-1.amazonaws.com", @@ -279,7 +298,6 @@ Array [ }, Object { "headers": Object { - "accept": "application/json", "accept-encoding": "gzip, deflate", "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", "host": "index.docker.io", @@ -547,6 +565,29 @@ Array [ ] `; +exports[`datasource/docker/index getReleases returns null if no auth 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "index.docker.io", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://index.docker.io/v2/", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "index.docker.io", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://index.docker.io/v2/", + }, +] +`; + exports[`datasource/docker/index getReleases returns null if no token 1`] = ` Array [ Object { @@ -599,6 +640,166 @@ Array [ ] `; +exports[`datasource/docker/index getReleases supports labels 1`] = ` +Object { + "dockerRegistry": "https://registry.company.com", + "dockerRepository": "node", + "releases": Array [], + "sourceUrl": "https://github.com/renovatebot/renovate", +} +`; + +exports[`datasource/docker/index getReleases supports labels 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.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", + }, + 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", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/blobs/some-config-digest", + }, +] +`; + +exports[`datasource/docker/index getReleases supports redirect 1`] = ` +Object { + "dockerRegistry": "https://registry.company.com", + "dockerRepository": "node", + "releases": Array [], +} +`; + +exports[`datasource/docker/index getReleases supports redirect 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.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", + }, + 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", + "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", + "host": "registry.company.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://registry.company.com/v2/node/blobs/some-config-digest", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "abc.s3.amazon.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://abc.s3.amazon.com/some-config-digest?X-Amz-Algorithm=xxxx", + }, +] +`; + exports[`datasource/docker/index getReleases uses custom registry in depName 1`] = ` Array [ Object { diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts index e837fd28a5032333bcea3a6fbe3be7c12ec274fd..b6f5b38d80284a5d9d51b56013657f74406343c2 100644 --- a/lib/datasource/docker/index.spec.ts +++ b/lib/datasource/docker/index.spec.ts @@ -75,6 +75,20 @@ describe(getName(__filename), () => { expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns null if empty header', async () => { + httpMock + .scope(baseUrl) + .get('/') + .reply(200, { token: 'some-token' }) + .get('/library/some-dep/manifests/some-new-value') + .reply(200, undefined, { 'docker-content-digest': '' }); + const res = await getDigest( + { datasource: 'docker', depName: 'some-dep' }, + 'some-new-value' + ); + expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('returns digest', async () => { httpMock .scope(baseUrl) @@ -91,6 +105,8 @@ describe(getName(__filename), () => { '/token?service=registry.docker.io&scope=repository:library/some-dep:pull' ) .reply(200, { token: 'some-token' }); + + hostRules.find.mockReturnValue({}); const res = await getDigest({ datasource: 'docker', depName: 'some-dep', @@ -262,6 +278,7 @@ describe(getName(__filename), () => { AWSMock.restore('ECR'); }); it('continues without token if ECR authentication fails', async () => { + hostRules.find.mockReturnValue({}); httpMock .scope(amazonUrl) .get('/') @@ -320,7 +337,7 @@ describe(getName(__filename), () => { .get( '/token?service=registry.docker.io&scope=repository:library/some-other-dep:pull' ) - .reply(200, { token: 'some-token' }); + .reply(200, { access_token: 'some-token' }); const res = await getDigest( { datasource: 'docker', depName: 'some-other-dep' }, '8.0.0-alpine' @@ -522,5 +539,92 @@ describe(getName(__filename), () => { expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('returns null if no auth', async () => { + hostRules.find.mockReturnValue({}); + httpMock + .scope(baseUrl) + .get('/') + .reply(200, undefined, { + 'www-authenticate': 'Basic realm="My Private Docker Registry Server"', + }) + .get('/') + .reply(403); + const res = await getPkgReleases({ + datasource: docker.id, + depName: 'node', + }); + expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('supports labels', async () => { + httpMock + .scope('https://registry.company.com/v2') + .get('/') + .times(3) + .reply(200) + .get('/node/tags/list?n=10000') + .reply(200, { tags: ['latest'] }) + .get('/node/manifests/latest') + .reply(200, { + schemaVersion: 2, + config: { digest: 'some-config-digest' }, + }) + .get('/node/blobs/some-config-digest') + .reply(200, { + config: { + Labels: { + 'org.opencontainers.image.source': + 'https://github.com/renovatebot/renovate', + }, + }, + }); + 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') + .get('/') + .times(3) + .reply(200) + .get('/node/tags/list?n=10000') + .reply(200, { tags: ['latest'] }) + .get('/node/manifests/latest') + .reply(200, { + schemaVersion: 2, + config: { digest: 'some-config-digest' }, + }) + .get('/node/blobs/some-config-digest') + .reply(302, undefined, { + location: + 'https://abc.s3.amazon.com/some-config-digest?X-Amz-Algorithm=xxxx', + }); + httpMock + .scope('https://abc.s3.amazon.com') + .get('/some-config-digest') + .query({ 'X-Amz-Algorithm': 'xxxx' }) + .reply(200, { + config: {}, + }); + const res = await getPkgReleases({ + datasource: docker.id, + depName: 'registry.company.com/node', + }); + const trace = httpMock.getTrace(); + expect(res).toMatchSnapshot(); + expect(trace).toMatchSnapshot(); + expect(trace[1].headers.authorization).toBe( + 'Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk' + ); + expect(trace[trace.length - 1].headers.authorization).toBeUndefined(); + }); }); }); diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts index bb7d57448347109be34a94ecc6ffa2099f97c9d9..6fd9874dc7b1bd172b4db37a12e5c4197d4edd3f 100644 --- a/lib/datasource/docker/index.ts +++ b/lib/datasource/docker/index.ts @@ -155,7 +155,6 @@ async function getAuthHeaders( const opts: HostRule & { headers?: Record<string, string>; } = hostRules.find({ hostType: id, url: apiCheckUrl }); - opts.json = true; if (ecrRegex.test(registry)) { const [, region] = ecrRegex.exec(registry); const auth = await getECRAuthToken(region, opts); @@ -449,7 +448,6 @@ async function getTags( * - Return the labels for the requested image */ -// istanbul ignore next async function getLabels( registry: string, repository: string, @@ -475,6 +473,7 @@ async function getLabels( // 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( { @@ -498,6 +497,7 @@ async function getLabels( let labels: Record<string, string> = {}; const configDigest = manifest.config.digest; const headers = await getAuthHeaders(registry, repository); + // istanbul ignore if: Should never be happen if (!headers) { logger.debug('No docker auth found - returning'); return {}; @@ -519,7 +519,7 @@ async function getLabels( const cacheMinutes = 60; await packageCache.set(cacheNamespace, cacheKey, labels, cacheMinutes); return labels; - } catch (err) { + } catch (err) /* istanbul ignore next: should be tested in future */ { if (err instanceof ExternalHostError) { throw err; } @@ -605,7 +605,6 @@ export async function getReleases({ const latestTag = tags.includes('latest') ? 'latest' : tags[tags.length - 1]; const labels = await getLabels(registry, repository, latestTag); - // istanbul ignore if if (labels && 'org.opencontainers.image.source' in labels) { ret.sourceUrl = labels['org.opencontainers.image.source']; } diff --git a/lib/datasource/terraform-module/index.spec.ts b/lib/datasource/terraform-module/index.spec.ts index a5d7a0f1331d2297c576564473c954d6d07b292e..931520dc74b03fb9b8acd071f88d930e6bd0a368 100644 --- a/lib/datasource/terraform-module/index.spec.ts +++ b/lib/datasource/terraform-module/index.spec.ts @@ -139,7 +139,7 @@ describe('datasource/terraform-module', () => { .reply(200, serviceDiscoveryCustomResult); const res = await getPkgReleases({ datasource, - registryUrls: ['terraform.foo.bar'], + registryUrls: ['https://terraform.foo.bar'], depName: 'hashicorp/consul/aws', }); expect(res).toMatchSnapshot();