diff --git a/lib/config/definitions.js b/lib/config/definitions.js index c10b8a53d7bc55c144716ee410b3c7c179e6acde..e524139c62c47480e989d64313027b619c979952 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1462,6 +1462,18 @@ const options = [ mergeable: true, cli: false, }, + { + name: 'droneci', + description: + 'Configuration object for DroneCI yml renovation. Also inherits settings from `docker` object.', + stage: 'package', + type: 'object', + default: { + fileMatch: ['(^|/).drone.yml$'], + }, + mergeable: true, + cli: false, + }, { name: 'ansible', description: diff --git a/lib/manager/droneci/extract.ts b/lib/manager/droneci/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b54d1262d7aea895a2717d450cccf82b421f8a5 --- /dev/null +++ b/lib/manager/droneci/extract.ts @@ -0,0 +1,35 @@ +import { logger } from '../../logger'; +import { getDep } from '../dockerfile/extract'; +import { PackageFile, PackageDependency } from '../common'; + +export function extractPackageFile(content: string): PackageFile { + const deps: PackageDependency[] = []; + try { + const lines = content.split('\n'); + for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) { + const line = lines[lineNumber]; + const match = line.match(/^\s* image:\s*'?"?([^\s'"]+)'?"?\s*$/); + if (match) { + const currentFrom = match[1]; + const dep = getDep(currentFrom); + logger.debug( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'DroneCI docker image' + ); + dep.depType = 'docker'; + dep.managerData = { lineNumber }; + deps.push(dep); + } + } + } catch (err) /* istanbul ignore next */ { + logger.warn({ err }, 'Error extracting DroneCI images'); + } + if (!deps.length) { + return null; + } + return { deps }; +} diff --git a/lib/manager/droneci/index.ts b/lib/manager/droneci/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5705105fe4b3fc5c6ffd85e4a307850d0b81f0a3 --- /dev/null +++ b/lib/manager/droneci/index.ts @@ -0,0 +1,6 @@ +import { extractPackageFile } from './extract'; +import { updateDependency } from './update'; + +const language = 'docker'; + +export { extractPackageFile, language, updateDependency }; diff --git a/lib/manager/droneci/update.ts b/lib/manager/droneci/update.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9f20f840a32255119bca44b93320c28d8890674 --- /dev/null +++ b/lib/manager/droneci/update.ts @@ -0,0 +1,34 @@ +import { logger } from '../../logger'; +import { getNewFrom } from '../dockerfile/update'; +import { Upgrade } from '../common'; + +export function updateDependency( + fileContent: string, + upgrade: Upgrade +): string { + try { + const lines = fileContent.split('\n'); + const lineToChange = lines[upgrade.managerData.lineNumber]; + if (upgrade.depType === 'docker') { + const newFrom = getNewFrom(upgrade); + logger.debug(`droneci.updateDependency(): ${newFrom}`); + const imageLine = new RegExp(/^(\s* image:\s*'?"?)[^\s'"]+('?"?\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.managerData.lineNumber] = newLine; + return lines.join('\n'); + } + logger.error('Unknown DroneCI depType'); + return null; + } catch (err) { + logger.info({ err }, 'Error setting new DroneCI image value'); + return null; + } +} diff --git a/lib/manager/index.ts b/lib/manager/index.ts index 393d1b926c735a215adab585668b7d2f6a81ce4e..0467781cff4a4b65a50a4f0140345e2b67d9a93c 100644 --- a/lib/manager/index.ts +++ b/lib/manager/index.ts @@ -16,12 +16,14 @@ const managerList = [ 'deps-edn', 'docker-compose', 'dockerfile', + 'droneci', 'github-actions', 'gitlabci', 'gitlabci-include', 'gomod', 'gradle', 'gradle-wrapper', + 'homebrew', 'kubernetes', 'leiningen', 'maven', @@ -39,7 +41,6 @@ const managerList = [ 'terraform', 'travis', 'ruby-version', - 'homebrew', ]; const managers: Record<string, ManagerApi> = {}; diff --git a/renovate-schema.json b/renovate-schema.json index e26e7363caedd49a23409da08c8bdc041273875e..09c4387cc8baa4fe2814d2d076750fec72b1a819 100644 --- a/renovate-schema.json +++ b/renovate-schema.json @@ -1006,6 +1006,14 @@ }, "$ref": "#" }, + "droneci": { + "description": "Configuration object for DroneCI yml renovation. Also inherits settings from `docker` object.", + "type": "object", + "default": { + "fileMatch": ["(^|/).drone.yml$"] + }, + "$ref": "#" + }, "ansible": { "description": "Configuration object for Ansible yaml renovation. Also inherits settings from `docker` object.", "type": "object", diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap index 2865dfe627cdbe54689fe8170401baf2314db903..811d4ee9dcee8629e22556e191cd831af6b45f9c 100644 --- a/test/config/__snapshots__/validation.spec.js.snap +++ b/test/config/__snapshots__/validation.spec.js.snap @@ -87,7 +87,7 @@ Array [ "depName": "Configuration Error", "message": "packageRules: You have included an unsupported manager in a package rule. Your list: foo. - Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, deps-edn, docker-compose, dockerfile, github-actions, gitlabci, gitlabci-include, gomod, gradle, gradle-wrapper, kubernetes, leiningen, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, swift, terraform, travis, ruby-version, homebrew).", + Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, deps-edn, docker-compose, dockerfile, droneci, github-actions, gitlabci, gitlabci-include, gomod, gradle, gradle-wrapper, homebrew, kubernetes, leiningen, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, swift, terraform, travis, ruby-version).", }, ] `; diff --git a/test/manager/droneci/__snapshots__/extract.spec.ts.snap b/test/manager/droneci/__snapshots__/extract.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..cb6449d2a874acaa99b90247ea88eadaca64b3eb --- /dev/null +++ b/test/manager/droneci/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/manager/droneci/extract extractPackageFile() extracts multiple image lines 1`] = ` +Array [ + Object { + "currentDigest": undefined, + "currentValue": "1.8.1-alpine", + "datasource": "docker", + "depName": "elixir", + "depType": "docker", + "managerData": Object { + "lineNumber": 5, + }, + }, + Object { + "commitMessageTopic": "Node.js", + "currentDigest": "sha256:36adc17e9cceab32179d3314da9cb9c737ffb11f0de4e688f407ad6d9ca32201", + "currentValue": "10.0.0", + "datasource": "docker", + "depName": "amd64/node", + "depType": "docker", + "managerData": Object { + "lineNumber": 16, + }, + }, + Object { + "currentDigest": undefined, + "currentValue": "5.7.24", + "datasource": "docker", + "depName": "mysql", + "depType": "docker", + "managerData": Object { + "lineNumber": 23, + }, + }, + Object { + "currentDigest": undefined, + "currentValue": "alpine", + "datasource": "docker", + "depName": "redis", + "depType": "docker", + "managerData": Object { + "lineNumber": 28, + }, + }, +] +`; diff --git a/test/manager/droneci/_fixtures/.drone.yml b/test/manager/droneci/_fixtures/.drone.yml new file mode 100644 index 0000000000000000000000000000000000000000..54bbe221475732214f2acaaafd1411926dc1f18e --- /dev/null +++ b/test/manager/droneci/_fixtures/.drone.yml @@ -0,0 +1,31 @@ +kind: pipeline +name: Test + +steps: + - name: mix + image: elixir:1.8.1-alpine + environment: + DB_HOST: mysql + commands: + - apk add --no-cache gcc g++ git imagemagick libc-dev libmcrypt-dev make + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + - mix test + + - name: node + image: amd64/node:10.0.0@sha256:36adc17e9cceab32179d3314da9cb9c737ffb11f0de4e688f407ad6d9ca32201 + commands: + - npm install + - npm test + +services: + - name: mysql + image: mysql:5.7.24 + ports: + - 3306 + + - name: redis + image: redis:alpine + ports: + - 6379 diff --git a/test/manager/droneci/extract.spec.ts b/test/manager/droneci/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca2ea0a1f22b68802d7231c7ca0606c8d5001df2 --- /dev/null +++ b/test/manager/droneci/extract.spec.ts @@ -0,0 +1,23 @@ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const { extractPackageFile } = require('../../../lib/manager/droneci/extract'); + +const droneYAML = readFileSync( + resolve('test/manager/droneci/_fixtures/.drone.yml'), + 'utf8' +); + +describe('lib/manager/droneci/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for empty', () => { + expect(extractPackageFile('nothing here')).toBeNull(); + }); + + it('extracts multiple image lines', () => { + const res = extractPackageFile(droneYAML); + expect(res.deps).toMatchSnapshot(); + expect(res.deps).toHaveLength(4); + }); + }); +}); diff --git a/test/manager/droneci/update.spec.ts b/test/manager/droneci/update.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e2b0c3e2197b54f02d7da62cc10340a6c6c5ae4 --- /dev/null +++ b/test/manager/droneci/update.spec.ts @@ -0,0 +1,64 @@ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const { updateDependency } = require('../../../lib/manager/droneci/update'); + +const droneYAML = readFileSync( + resolve('test/manager/droneci/_fixtures/.drone.yml'), + 'utf8' +); + +describe('manager/droneci/update', () => { + describe('updateDependency', () => { + it('replaces existing value', () => { + const upgrade = { + managerData: { lineNumber: 16 }, + depType: 'docker', + depName: 'node', + newValue: '10.16.0', + newDigest: 'sha256:new-node-hash', + }; + const res = updateDependency(droneYAML, upgrade); + expect(res).not.toEqual(droneYAML); + expect(res.includes(upgrade.newDigest)).toBe(true); + }); + + it('returns same', () => { + const upgrade = { + managerData: { lineNumber: 28 }, + depType: 'docker', + depName: 'redis:alpine', + }; + const res = updateDependency(droneYAML, upgrade); + expect(res).toEqual(droneYAML); + }); + + it('returns null if mismatch', () => { + const upgrade = { + managerData: { lineNumber: 17 }, + depType: 'docker', + depName: 'postgres', + newValue: '9.6.8', + newDigest: 'sha256:abcdefghijklmnop', + }; + const res = updateDependency(droneYAML, upgrade); + expect(res).toBeNull(); + }); + + it('returns null if error', () => { + const res = updateDependency(null, null); + expect(res).toBeNull(); + }); + + it('returns null for unknown depType', () => { + const upgrade = { + currentValue: '4.1.0', + depName: 'release-workflows', + managerData: { lineNumber: 3 }, + newValue: '4.2.0', + }; + const res = updateDependency(droneYAML, upgrade); + expect(res).toBeNull(); + }); + }); +}); diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap index 49a17198f4144eb438ee280d23798277567e6d73..e5b3d0b45385086c17b155fbff1944e2f09e64b8 100644 --- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap @@ -32,6 +32,9 @@ Object { "dockerfile": Array [ Object {}, ], + "droneci": Array [ + Object {}, + ], "github-actions": Array [ Object {}, ], diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index ed8362451dcfe28ad1dd62d6dbb7adacd86fae37..bdae62b60abb55a904cf906f0edeba630b82135c 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -212,6 +212,8 @@ Add configuration here if you want to enable or disable something in particular ## dotnet +## droneci + ## enabled Renovate is enabled for all packages by default, but this setting allows you to disable Renovate for specific packages, dependency types, package files, or even for the whole repository.