-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Adam Duncan
committed
Feb 9, 2019
0 parents
commit be486c9
Showing
9 changed files
with
810 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM node:10.15.1-alpine | ||
|
||
WORKDIR /app | ||
COPY . . | ||
|
||
RUN yarn | ||
|
||
CMD /usr/local/bin/node /app/plugin.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Drone Config Plugin - Changeset Conditional | ||
|
||
This implements the ability to have steps / pipelines only execute when certain files have changed, using the following additional YAML syntax: | ||
|
||
``` | ||
pipeline: | ||
frontend: | ||
image: node | ||
commands: | ||
- cd app | ||
- npm run test | ||
+ when: | ||
+ changeset: | ||
+ includes: [ **/**.js, **/**.css, **/**.html ] | ||
backend: | ||
image: golang | ||
commands: | ||
- go build | ||
- go test -v | ||
+ when: | ||
+ changeset: | ||
+ includes: [ **/**.go ] | ||
+changeset: | ||
+ includes: [ **/**.go ] | ||
``` | ||
|
||
## Installation | ||
|
||
PLEASE NOTE: At the moment it supports only github.com installations. | ||
|
||
Generate a GitHub access token with repo permission. This token is used to fetch the `.drone.yml` file and details of the files changed. | ||
|
||
Generate a shared secret key. This key is used to secure communication between the server and agents. The secret should be 32 bytes. | ||
``` | ||
$ openssl rand -hex 16 | ||
558f3eacbfd5928157cbfe34823ab921 | ||
``` | ||
|
||
Run the container somewhere where the drone server can reach it: | ||
|
||
``` | ||
docker run \ | ||
-p ${PLUGIN_PORT}:3000 \ | ||
-e PLUGIN_SECRET=558f3eacbfd5928157cbfe34823ab921 \ | ||
-e GITHUB_TOKEN=GITHUB8168c98304b \ | ||
--name drone-changeset-conditional \ | ||
microadam/drone-config-plugin-changeset-conditional | ||
``` | ||
|
||
Update your drone server with information about the plugin: | ||
|
||
``` | ||
-e DRONE_YAML_ENDPOINT=http://${PLUGIN_HOST}:${PLUGIN_PORT} | ||
-e DRONE_YAML_SECRET=558f3eacbfd5928157cbfe34823ab921 | ||
``` | ||
|
||
See [the official docs](https://docs.drone.io/extend/config) for extra information on installing a Configuration Provider Plugin. | ||
|
||
## Pattern Matching | ||
|
||
This uses the [Glob](https://www.npmjs.com/package/glob) module under the hood, so supports all pattern matching syntaxes of this module. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const { promisify } = require('util') | ||
const getCommit = gh => async data => { | ||
let commitData = null | ||
const options = { | ||
user: data.repo.namespace, | ||
repo: data.repo.name, | ||
base: data.build.before, | ||
head: data.build.after | ||
} | ||
const comparison = await promisify(gh.repos.compareCommits)(options) | ||
return comparison.files.map(f => f.filename) | ||
} | ||
|
||
module.exports = getCommit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const { promisify } = require('util') | ||
const yaml = require('yamljs') | ||
const getParsedYaml = gh => async data => { | ||
let file = null | ||
const options = { | ||
user: data.repo.namespace, | ||
repo: data.repo.name, | ||
ref: data.build.ref, | ||
path: data.repo.config_path | ||
} | ||
file = await promisify(gh.repos.getContent)(options) | ||
const contents = Buffer.from(file.content, 'base64').toString() | ||
const parsed = yaml.parse(contents) | ||
return parsed | ||
} | ||
|
||
module.exports = getParsedYaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const httpSignature = require('http-signature') | ||
const isValidSig = (req, hmac) => { | ||
req.headers.signature = 'Signature ' + req.headers.signature | ||
const parsedSig = httpSignature.parseRequest(req, { authorizationHeaderName: 'signature' }) | ||
return httpSignature.verifyHMAC(parsedSig, hmac) | ||
} | ||
|
||
module.exports = isValidSig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "drone-config-changeset-conditional", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "plugin.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"body-parser": "^1.18.3", | ||
"express": "^4.16.4", | ||
"github4": "^1.1.1", | ||
"globule": "^1.2.1", | ||
"http-signature": "^1.2.0", | ||
"yamljs": "^0.3.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const express = require('express') | ||
const bodyParser = require('body-parser') | ||
const GhApi = require('github4') | ||
const yaml = require('yamljs') | ||
const glob = require('globule') | ||
const createFilesChangedDeterminer = require('./lib/files-changed-determiner') | ||
const createParsedYamlRetriever = require('./lib/parsed-yaml-retriever') | ||
const isValidSig = require('./lib/signature-validator') | ||
|
||
const githubToken = process.env.GITHUB_TOKEN | ||
const sharedKey = process.env.PLUGIN_SECRET | ||
|
||
const gh = new GhApi({ version: '3.0.0' }) | ||
gh.authenticate({ type: 'oauth', token: githubToken }) | ||
|
||
const determineFilesChanged = createFilesChangedDeterminer(gh) | ||
const getParsedYaml = createParsedYamlRetriever(gh) | ||
|
||
const nullYaml = 'kind: pipeline\nname: default\ntrigger:\n event:\n exclude: [ "*" ]' | ||
|
||
const app = express() | ||
app.post('/', bodyParser.json(), async (req, res) => { | ||
console.log('Processing...') | ||
if (!req.headers.signature) return res.status(400).send('Missing signature') | ||
if (!isValidSig(req, sharedKey)) return res.status(400).send('Invalid signature') | ||
if (!req.body) return res.sendStatus(400) | ||
const data = req.body | ||
|
||
let filesChanged = [] | ||
try { | ||
filesChanged = await determineFilesChanged(data) | ||
} catch (e) { | ||
console.log('ERROR:', e) | ||
return res.sendStatus(500) | ||
} | ||
|
||
console.log('Files changed:', filesChanged) | ||
|
||
let parsedYaml = null | ||
try { | ||
parsedYaml = await getParsedYaml(data) | ||
} catch (e) { | ||
if (e.code === 404) return res.sendStatus(204) | ||
console.log('ERROR:', e) | ||
return res.sendStatus(500) | ||
} | ||
|
||
if (parsedYaml.trigger && parsedYaml.trigger.changeset && parsedYaml.trigger.changeset.includes) { | ||
const requiredFiles = parsedYaml.trigger.changeset.includes | ||
const matchedFiles = glob.match(requiredFiles, filesChanged, { dot: true }) | ||
console.log('Matched files for pipeline:', matchedFiles.length, 'Allowed matches:', requiredFiles) | ||
if (!matchedFiles.length) return res.json({ Data: nullYaml }) | ||
} | ||
|
||
const trimmedSteps = parsedYaml.steps.filter(s => { | ||
if (!s.when || !s.when.changeset || !s.when.changeset.includes) return true | ||
const requiredFiles = s.when.changeset.includes | ||
const matchedFiles = glob.match(requiredFiles, filesChanged, { dot: true }) | ||
console.log('Matched files for step:', matchedFiles.length, 'Allowed matches:', requiredFiles) | ||
return matchedFiles.length | ||
}) | ||
|
||
const returnYaml = trimmedSteps.length ? yaml.stringify({ ...parsedYaml, steps: trimmedSteps }) : nullYaml | ||
|
||
res.json({ Data: returnYaml }) | ||
}) | ||
|
||
app.listen(3000) |
Oops, something went wrong.