diff --git a/lib/config/definitions.js b/lib/config/definitions.js index feddb01d631aa553fba96a2a7e8b44a962f26699..5d455fed0e0cba21fafee8046bdae545043341b3 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1268,6 +1268,19 @@ const options = [ mergeable: true, cli: false, }, + { + name: 'github-actions', + description: + 'Configuration object for GitHub Actions workflow renovation. Also inherits settings from `docker` object.', + stage: 'package', + type: 'json', + default: { + fileMatch: ['^\\.github/main.workflow$'], + pinDigests: true, + }, + mergeable: true, + cli: false, + }, { name: 'composer', description: 'Configuration object for composer.json files', diff --git a/lib/manager/github-actions/extract.js b/lib/manager/github-actions/extract.js new file mode 100644 index 0000000000000000000000000000000000000000..f4f1f6bc4da447c14bcaa3c58d719da907f7ec1b --- /dev/null +++ b/lib/manager/github-actions/extract.js @@ -0,0 +1,34 @@ +const { getDep } = require('../dockerfile/extract'); + +module.exports = { + extractPackageFile, +}; + +function extractPackageFile(content) { + logger.debug('github-actions.extractPackageFile()'); + const deps = []; + let lineNumber = 0; + for (const line of content.split('\n')) { + const match = line.match(/^\s+uses = "docker:\/\/([^"]+)"\s*$/); + if (match) { + const currentFrom = match[1]; + const dep = getDep(currentFrom); + logger.debug( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'Docker image inside GitHub Actions' + ); + dep.lineNumber = lineNumber; + dep.versionScheme = 'docker'; + deps.push(dep); + } + lineNumber += 1; + } + if (!deps.length) { + return null; + } + return { deps }; +} diff --git a/lib/manager/github-actions/index.js b/lib/manager/github-actions/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f98aa074d98cb6dc1534aeb4c1aa1250bf5fba71 --- /dev/null +++ b/lib/manager/github-actions/index.js @@ -0,0 +1,10 @@ +const { extractPackageFile } = require('./extract'); +const { updateDependency } = require('./update'); + +const language = 'docker'; + +module.exports = { + extractPackageFile, + language, + updateDependency, +}; diff --git a/lib/manager/github-actions/update.js b/lib/manager/github-actions/update.js new file mode 100644 index 0000000000000000000000000000000000000000..00dcb6b9ca85debb4fccc9885056fbbcee8b1c12 --- /dev/null +++ b/lib/manager/github-actions/update.js @@ -0,0 +1,29 @@ +const { getNewFrom } = require('../dockerfile/update'); + +module.exports = { + updateDependency, +}; + +function updateDependency(fileContent, upgrade) { + try { + const newFrom = getNewFrom(upgrade); + logger.debug(`github-actions.updateDependency(): ${newFrom}`); + const lines = fileContent.split('\n'); + const lineToChange = lines[upgrade.lineNumber]; + const imageLine = new RegExp(/^(\s+uses = "docker:\/\/)[^"]+("\s*)$/); + if (!lineToChange.match(imageLine)) { + logger.debug('No image line found'); + return null; + } + const newLine = lineToChange.replace(imageLine, `$1${newFrom}$2`); + if (newLine === lineToChange) { + logger.debug('No changes necessary'); + return fileContent; + } + lines[upgrade.lineNumber] = newLine; + return lines.join('\n'); + } catch (err) { + logger.info({ err }, 'Error setting new github-actions value'); + return null; + } +} diff --git a/lib/manager/index.js b/lib/manager/index.js index aa8feb497dcadfa8c79dc4bf51bf0d3378aba1b0..f1e4f17926b73d6068e6aa80ab8551d5c4701f26 100644 --- a/lib/manager/index.js +++ b/lib/manager/index.js @@ -8,6 +8,7 @@ const managerList = [ 'composer', 'docker-compose', 'dockerfile', + 'github-actions', 'gitlabci', 'gomod', 'gradle', diff --git a/test/_fixtures/github-actions/main.workflow.1 b/test/_fixtures/github-actions/main.workflow.1 new file mode 100644 index 0000000000000000000000000000000000000000..09738d3c1c3304f81d8b43989fdf1523866a4a36 --- /dev/null +++ b/test/_fixtures/github-actions/main.workflow.1 @@ -0,0 +1,50 @@ +workflow "Build and Publish" { + on = "push" + resolves = "Docker Publish" +} + +action "Shell Lint" { + uses = "actions/bin/shellcheck@master" + args = "entrypoint.sh" +} + +action "Docker Lint" { + uses = "docker://replicated/dockerfilelint" + args = ["Dockerfile"] +} + +action "Build" { + needs = ["Shell Lint", "Docker Lint"] + uses = "actions/docker/cli@master" + args = "build -t conventional-commits ." +} + +action "Docker Tag" { + needs = ["Build"] + uses = "actions/docker/tag@master" + args = "conventional-commits bcoe/conventional-commits --no-latest" +} + +action "Publish Filter" { + needs = ["Build"] + uses = "actions/bin/filter@master" + args = "branch master" +} + +action "Node_6_Test" { + needs = "Node_6_Install" + runs = "yarn test" + uses = "docker://node:6@sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896" +} + +action "Docker Login" { + needs = ["Publish Filter"] + uses = "actions/docker/login@master" + secrets = ["DOCKER_USERNAME", "DOCKER_PASSWORD"] +} + +action "Docker Publish" { + needs = ["Docker Tag", "Docker Login"] + uses = "actions/docker/cli@master" + args = "push bcoe/conventional-commits" +} diff --git a/test/manager/__snapshots__/manager-docs.spec.js.snap b/test/manager/__snapshots__/manager-docs.spec.js.snap index 42f49a6a650e22de7b229dc1cdb3dc4854e78a1a..e67b226338dc5e78bca664bc8e1ebf32325f0b68 100644 --- a/test/manager/__snapshots__/manager-docs.spec.js.snap +++ b/test/manager/__snapshots__/manager-docs.spec.js.snap @@ -11,6 +11,7 @@ Array [ "composer", "docker-compose", "dockerfile", + "github-actions", "gitlabci", "gomod", "gradle", diff --git a/test/manager/github-actions/__snapshots__/extract.spec.js.snap b/test/manager/github-actions/__snapshots__/extract.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..5bf8cc6dc9237d1a8e9cbb1fa2a7af7bde9cbe3a --- /dev/null +++ b/test/manager/github-actions/__snapshots__/extract.spec.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/manager/github-actions/extract extractPackageFile() extracts multiple image lines from docker_container 1`] = ` +Array [ + Object { + "currentDepTag": "replicated/dockerfilelint", + "currentDigest": undefined, + "currentFrom": "replicated/dockerfilelint", + "currentValue": undefined, + "datasource": "docker", + "depName": "replicated/dockerfilelint", + "lineNumber": 11, + "versionScheme": "docker", + }, + Object { + "commitMessageTopic": "Node.js", + "currentDepTag": "node:6", + "currentDigest": "sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896", + "currentFrom": "node:6@sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896", + "currentValue": "6", + "datasource": "docker", + "depName": "node", + "lineNumber": 36, + "versionScheme": "docker", + }, +] +`; diff --git a/test/manager/github-actions/extract.spec.js b/test/manager/github-actions/extract.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..86513b2ea7899ced96b1607525a3005bef90e6a6 --- /dev/null +++ b/test/manager/github-actions/extract.spec.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const { + extractPackageFile, +} = require('../../../lib/manager/github-actions/extract'); + +const workflow1 = fs.readFileSync( + 'test/_fixtures/github-actions/main.workflow.1', + 'utf8' +); + +describe('lib/manager/github-actions/extract', () => { + describe('extractPackageFile()', () => { + let config; + beforeEach(() => { + config = {}; + }); + it('returns null for empty', () => { + expect(extractPackageFile('nothing here', config)).toBe(null); + }); + it('extracts multiple image lines from docker_container', () => { + const res = extractPackageFile(workflow1, config); + expect(res.deps).toMatchSnapshot(); + expect(res.deps).toHaveLength(2); + }); + }); +}); diff --git a/test/manager/github-actions/update.spec.js b/test/manager/github-actions/update.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..334a55955a8b1600393d34d9f437060113a47441 --- /dev/null +++ b/test/manager/github-actions/update.spec.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +const dcUpdate = require('../../../lib/manager/github-actions/update'); + +const workflow1 = fs.readFileSync( + 'test/_fixtures/github-actions/main.workflow.1', + 'utf8' +); + +describe('manager/github-actions/update', () => { + describe('updateDependency', () => { + it('replaces existing uses value', () => { + const upgrade = { + lineNumber: 11, + depName: 'replicated/dockerfilelint', + newDigest: 'sha256:abcdefghijklmnop', + }; + const res = dcUpdate.updateDependency(workflow1, upgrade); + expect(res).not.toEqual(workflow1); + expect(res.includes(upgrade.newDigest)).toBe(true); + }); + it('returns same', () => { + const upgrade = { + lineNumber: 11, + depName: 'replicated/dockerfilelint', + }; + const res = dcUpdate.updateDependency(workflow1, upgrade); + expect(res).toEqual(workflow1); + }); + it('returns null if mismatch', () => { + const upgrade = { + lineNumber: 12, + newFrom: 'registry:2.6.2@sha256:abcdefghijklmnop', + }; + const res = dcUpdate.updateDependency(workflow1, upgrade); + expect(res).toBe(null); + }); + it('returns null if error', () => { + const res = dcUpdate.updateDependency(null, null); + expect(res).toBe(null); + }); + }); +}); diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap index 31bda27d591c18a9cdd182723342580425996164..9ca0e9b27b8372d0ab02edda8839611b2d433a88 100644 --- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap @@ -29,6 +29,9 @@ Object { "dockerfile": Array [ Object {}, ], + "github-actions": Array [ + Object {}, + ], "gitlabci": Array [ Object {}, ], diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index ccfb2ea5466abbfd4c6557767ae1263f7e75107f..672988d933737d56bcc8484cb1e22bebb4a047a6 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -231,6 +231,10 @@ See https://renovatebot.com/docs/config-presets for details. The primary use case for this option is if you are following a pre-release tag of a certain dependency, e.g. `typescript` "insiders" build. When it's configured, Renovate bypasses its normal major/minor/patch logic and stable/unstable logic and simply raises a PR if the tag does not match your current version. +## github-actions + +Add to this configuration setting if you need to override any of the GitHub Actions default settings. Use the `docker` config object instead if you wish for configuration to apply across all Docker-related package managers. + ## gitlabci Add to this configuration setting if you need to override any of the GitLab CI default settings. Use the `docker` config object instead if you wish for configuration to apply across all Docker-related package managers.