From 52a8da89fa2d4db6c1ae6208f65af1b15d61b7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ga=C3=9F?= <mxey@mxey.net> Date: Sat, 11 Aug 2018 11:27:18 +0200 Subject: [PATCH] feat(docker): Add support for COPY --from lines (#2368) COPY --from= can specify external images. Add support to renovate them. --- lib/manager/dockerfile/extract.js | 27 +++++++++++- lib/manager/dockerfile/update.js | 10 +++-- .../__snapshots__/extract.spec.js.snap | 43 +++++++++++++++++++ .../__snapshots__/update.spec.js.snap | 6 +++ test/manager/dockerfile/extract.spec.js | 15 +++++++ test/manager/dockerfile/update.spec.js | 14 ++++++ 6 files changed, 110 insertions(+), 5 deletions(-) diff --git a/lib/manager/dockerfile/extract.js b/lib/manager/dockerfile/extract.js index 7dcd7b91a1..fb6a226ec9 100644 --- a/lib/manager/dockerfile/extract.js +++ b/lib/manager/dockerfile/extract.js @@ -68,8 +68,8 @@ function extractDependencies(content) { const stageNames = []; let lineNumber = 0; for (const fromLine of content.split('\n')) { - const match = fromLine.match(/^FROM /i); - if (match) { + const fromMatch = fromLine.match(/^FROM /i); + if (fromMatch) { logger.debug({ lineNumber, fromLine }, 'FROM line'); const [fromPrefix, currentFrom, ...fromRest] = fromLine.match(/\S+/g); if (fromRest.length === 2 && fromRest[0].toLowerCase() === 'as') { @@ -97,6 +97,29 @@ function extractDependencies(content) { deps.push(dep); } } + + const copyFromMatch = fromLine.match(/^(COPY --from=)([^\s]+)\s+(.*)$/i); + if (copyFromMatch) { + const [fromPrefix, currentFrom, fromSuffix] = copyFromMatch.slice(1); + logger.debug({ lineNumber, fromLine }, 'COPY --from line'); + if (stageNames.includes(currentFrom)) { + logger.debug({ currentFrom }, 'Skipping alias COPY --from'); + } else { + const dep = getDep(currentFrom); + logger.info( + { + depName: dep.depName, + currentTag: dep.currentTag, + currentDigest: dep.currentDigest, + }, + 'Dockerfile COPY --from' + ); + dep.lineNumber = lineNumber; + dep.fromPrefix = fromPrefix; + dep.fromSuffix = fromSuffix; + deps.push(dep); + } + } lineNumber += 1; } if (!deps.length) { diff --git a/lib/manager/dockerfile/update.js b/lib/manager/dockerfile/update.js index 70dcad73ec..7bdd89866a 100644 --- a/lib/manager/dockerfile/update.js +++ b/lib/manager/dockerfile/update.js @@ -21,17 +21,21 @@ function getNewFrom(upgrade) { function updateDependency(fileContent, upgrade) { try { - const { lineNumber, fromPrefix, fromSuffix } = upgrade; + const { lineNumber, fromSuffix } = upgrade; + let { fromPrefix } = upgrade; const newFrom = getNewFrom(upgrade); logger.debug(`docker.updateDependency(): ${newFrom}`); const lines = fileContent.split('\n'); const lineToChange = lines[lineNumber]; - const imageLine = new RegExp(/^FROM /i); + const imageLine = new RegExp(/^(FROM |COPY --from=)/i); if (!lineToChange.match(imageLine)) { logger.debug('No image line found'); return null; } - const newLine = `${fromPrefix} ${newFrom} ${fromSuffix}`.trim(); + if (!fromPrefix.endsWith('=')) { + fromPrefix += ' '; + } + const newLine = `${fromPrefix}${newFrom} ${fromSuffix}`.trim(); if (newLine === lineToChange) { logger.debug('No changes necessary'); return fileContent; diff --git a/test/manager/dockerfile/__snapshots__/extract.spec.js.snap b/test/manager/dockerfile/__snapshots__/extract.spec.js.snap index e4c22b5fa5..da952dea32 100644 --- a/test/manager/dockerfile/__snapshots__/extract.spec.js.snap +++ b/test/manager/dockerfile/__snapshots__/extract.spec.js.snap @@ -76,6 +76,27 @@ Array [ ] `; +exports[`lib/manager/dockerfile/extract extractDependencies() handles COPY --from 1`] = ` +Array [ + Object { + "currentDepTag": "k8s-skaffold/skaffold:v0.11.0", + "currentDepTagDigest": "k8s-skaffold/skaffold:v0.11.0", + "currentDigest": undefined, + "currentFrom": "gcr.io/k8s-skaffold/skaffold:v0.11.0", + "currentTag": "v0.11.0", + "currentValue": "v0.11.0", + "depName": "k8s-skaffold/skaffold", + "dockerRegistry": "gcr.io", + "fromPrefix": "COPY --from=", + "fromSuffix": "/usr/bin/skaffold /usr/bin/skaffold", + "lineNumber": 1, + "purl": "pkg:docker/k8s-skaffold/skaffold?registry=gcr.io", + "tagSuffix": undefined, + "versionScheme": "docker", + }, +] +`; + exports[`lib/manager/dockerfile/extract extractDependencies() handles abnoral spacing 1`] = ` Array [ Object { @@ -362,6 +383,28 @@ Array [ ] `; +exports[`lib/manager/dockerfile/extract extractDependencies() skips named multistage COPY --from tags 1`] = ` +Array [ + Object { + "commitMessageTopic": "Node.js", + "currentDepTag": "node:6.12.3", + "currentDepTagDigest": "node:6.12.3", + "currentDigest": undefined, + "currentFrom": "node:6.12.3", + "currentTag": "6.12.3", + "currentValue": "6.12.3", + "depName": "node", + "dockerRegistry": undefined, + "fromPrefix": "FROM", + "fromSuffix": "as frontend", + "lineNumber": 0, + "purl": "pkg:docker/node", + "tagSuffix": undefined, + "versionScheme": "docker", + }, +] +`; + exports[`lib/manager/dockerfile/extract extractDependencies() skips named multistage FROM tags 1`] = ` Array [ Object { diff --git a/test/manager/dockerfile/__snapshots__/update.spec.js.snap b/test/manager/dockerfile/__snapshots__/update.spec.js.snap index 8e9dd8c5b3..ef595744b5 100644 --- a/test/manager/dockerfile/__snapshots__/update.spec.js.snap +++ b/test/manager/dockerfile/__snapshots__/update.spec.js.snap @@ -14,6 +14,12 @@ RUN something " `; +exports[`manager/dockerfile/update updateDependency replaces COPY --from 1`] = ` +"FROM scratch +COPY --from=gcr.io/k8s-skaffold/skaffold:v0.12.0 /usr/bin/skaffold /usr/bin/skaffold +" +`; + exports[`manager/dockerfile/update updateDependency replaces existing value 1`] = ` "# comment FROM node:8 FROM node:8-alpine@sha256:abcdefghijklmnop diff --git a/test/manager/dockerfile/extract.spec.js b/test/manager/dockerfile/extract.spec.js index 48340cf147..6d929adc91 100644 --- a/test/manager/dockerfile/extract.spec.js +++ b/test/manager/dockerfile/extract.spec.js @@ -120,6 +120,21 @@ describe('lib/manager/dockerfile/extract', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(1); }); + it('handles COPY --from', () => { + const res = extractDependencies( + 'FROM scratch\nCOPY --from=gcr.io/k8s-skaffold/skaffold:v0.11.0 /usr/bin/skaffold /usr/bin/skaffold\n', + config + ).deps; + expect(res).toMatchSnapshot(); + }); + it('skips named multistage COPY --from tags', () => { + const res = extractDependencies( + 'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nCOPY --from=frontend /usr/bin/node /usr/bin/node\n', + config + ).deps; + expect(res).toMatchSnapshot(); + expect(res).toHaveLength(1); + }); it('extracts images on adjacent lines', () => { const res = extractDependencies(d1, config).deps; expect(res).toMatchSnapshot(); diff --git a/test/manager/dockerfile/update.spec.js b/test/manager/dockerfile/update.spec.js index b24c7654c3..3a1c6b881f 100644 --- a/test/manager/dockerfile/update.spec.js +++ b/test/manager/dockerfile/update.spec.js @@ -108,5 +108,19 @@ describe('manager/dockerfile/update', () => { expect(res).toMatchSnapshot(); expect(res.includes('as stage-1')).toBe(true); }); + it('replaces COPY --from', () => { + const fileContent = + 'FROM scratch\nCOPY --from=gcr.io/k8s-skaffold/skaffold:v0.11.0 /usr/bin/skaffold /usr/bin/skaffold\n'; + const upgrade = { + lineNumber: 1, + depName: 'k8s-skaffold/skaffold', + newValue: 'v0.12.0', + fromPrefix: 'COPY --from=', + fromSuffix: '/usr/bin/skaffold /usr/bin/skaffold', + dockerRegistry: 'gcr.io', + }; + const res = dockerfile.updateDependency(fileContent, upgrade); + expect(res).toMatchSnapshot(); + }); }); }); -- GitLab