diff --git a/lib/datasource/docker.js b/lib/datasource/docker.js index 9a171e6a020b3b72e6e363c537abb4a4bd83e3a0..41816e7dc9083214a1198650f6babaddc2e02244 100644 --- a/lib/datasource/docker.js +++ b/lib/datasource/docker.js @@ -102,52 +102,28 @@ function extractDigestFromResponse(manifestResponse) { return manifestResponse.headers['docker-content-digest']; } -/* - * docker.getDigest - * - * The `newValue` supplied here should be a valid tag for the docker image. - * - * This function will: - * - Look up a sha256 digest for a tag on its registry - * - Return the digest as a string - */ - -async function getDigest(config, newValue) { - const { dockerRegistry, depName } = config; - logger.debug(`getDigest(${dockerRegistry}, ${depName}, ${newValue})`); - const massagedRegistry = massageRegistry(config, dockerRegistry); - const repository = getRepository(depName, dockerRegistry); - const newTag = newValue || 'latest'; +async function getManifestResponse(registry, repository, tag) { + logger.debug(`getManifestResponse(${registry}, ${repository}, ${tag})`); try { - const cacheNamespace = 'datasource-docker-digest'; - const cacheKey = `${massagedRegistry}:${repository}:${newTag}`; - const cachedResult = await renovateCache.get(cacheNamespace, cacheKey); - if (cachedResult) { - return cachedResult; - } - const headers = await getAuthHeaders(massagedRegistry, repository); + const headers = await getAuthHeaders(registry, repository); if (!headers) { logger.info('No docker auth found - returning'); return null; } headers.accept = 'application/vnd.docker.distribution.manifest.v2+json'; - const url = `${massagedRegistry}/v2/${repository}/manifests/${newTag}`; + const url = `${registry}/v2/${repository}/manifests/${tag}`; const manifestResponse = await got(url, { headers, timeout: 10000, }); - const digest = extractDigestFromResponse(manifestResponse); - logger.debug({ digest }, 'Got docker digest'); - const cacheMinutes = 30; - await renovateCache.set(cacheNamespace, cacheKey, digest, cacheMinutes); - return digest; + return manifestResponse; } catch (err) /* istanbul ignore next */ { if (err.message === 'registry-failure') { throw err; } if (err.statusCode === 401) { logger.info( - { dockerRegistry, dockerRepository: repository }, + { registry, dockerRepository: repository }, 'Unauthorized docker lookup' ); logger.debug({ err }); @@ -157,14 +133,15 @@ async function getDigest(config, newValue) { logger.info( { err, - depName, - newTag, + registry, + repository, + tag, }, 'Docker Manifest is unknown' ); return null; } - if (err.statusCode === 429 && massagedRegistry.endsWith('docker.io')) { + if (err.statusCode === 429 && registry.endsWith('docker.io')) { logger.warn({ err }, 'docker registry failure: too many requests'); throw new Error('registry-failure'); } @@ -172,8 +149,9 @@ async function getDigest(config, newValue) { logger.warn( { err, - depName, - newTag, + registry, + repository, + tag, }, 'docker registry failure: internal error' ); @@ -181,12 +159,61 @@ async function getDigest(config, newValue) { } if (err.code === 'ETIMEDOUT') { logger.info( - { massagedRegistry }, + { registry }, 'Timeout when attempting to connect to docker registry' ); logger.debug({ err }); return null; } + logger.info( + { + err, + registry, + repository, + tag, + }, + 'Unknown Error looking up docker manifest' + ); + return null; + } +} + +/* + * docker.getDigest + * + * The `newValue` supplied here should be a valid tag for the docker image. + * + * This function will: + * - Look up a sha256 digest for a tag on its registry + * - Return the digest as a string + */ + +async function getDigest(config, newValue) { + const { dockerRegistry, depName } = config; + logger.debug(`getDigest(${dockerRegistry}, ${depName}, ${newValue})`); + const massagedRegistry = massageRegistry(config, dockerRegistry); + const repository = getRepository(depName, dockerRegistry); + const newTag = newValue || 'latest'; + try { + const cacheNamespace = 'datasource-docker-digest'; + const cacheKey = `${massagedRegistry}:${repository}:${newTag}`; + const cachedResult = await renovateCache.get(cacheNamespace, cacheKey); + if (cachedResult) { + return cachedResult; + } + const manifestResponse = await getManifestResponse(massagedRegistry, repository, newTag); + if (!manifestResponse) { + return null; + } + const digest = extractDigestFromResponse(manifestResponse); + logger.debug({ digest }, 'Got docker digest'); + const cacheMinutes = 30; + await renovateCache.set(cacheNamespace, cacheKey, digest, cacheMinutes); + return digest; + } catch (err) /* istanbul ignore next */ { + if (err.message === 'registry-failure') { + throw err; + } logger.info( { err, diff --git a/test/datasource/docker.spec.js b/test/datasource/docker.spec.js index bb0f20bd4d30a3e362332a651f56b5f09a9759d1..8dac0ecc6771de876ae3c898c026c05a323662e2 100644 --- a/test/datasource/docker.spec.js +++ b/test/datasource/docker.spec.js @@ -141,6 +141,26 @@ describe('api/docker', () => { ); expect(res).toBe('some-digest'); }); + it('should throw error for 429', async () => { + got.mockRejectedValueOnce({statusCode: 429}); + let e; + try { + await docker.getDigest({ depName: 'some-dep' }, 'latest'); + } catch (err) { + e = err; + } + expect(e.message).toBe('registry-failure'); + }); + it('should throw error for 5xx', async () => { + got.mockRejectedValueOnce({statusCode: 503}); + let e; + try { + await docker.getDigest({ depName: 'some-dep' }, 'latest'); + } catch (err) { + e = err; + } + expect(e.message).toBe('registry-failure'); + }); }); describe('getPkgReleases', () => { beforeEach(() => {