From b23f1827cb3fa4cb4e90e93659e0916e1991631b Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Tue, 12 Feb 2019 09:43:37 -0800 Subject: [PATCH] feat: Add CircleCI workflow rerun when a specific label is added to a PR. --- functions/src/default.ts | 15 +++- functions/src/plugins/rerun-circleci.ts | 95 +++++++++++++++++++++++++ functions/src/util.ts | 4 ++ test/fixtures/angular-robot.yml | 7 ++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 functions/src/plugins/rerun-circleci.ts diff --git a/functions/src/default.ts b/functions/src/default.ts index 048b4ce..f4c3cc4 100644 --- a/functions/src/default.ts +++ b/functions/src/default.ts @@ -113,7 +113,14 @@ If you can't get the PR to a green state due to flakes or broken master, please l1TriageLabels: [["comp: *"]], // arrays of labels that determine if a PR has been fully triaged l2TriageLabels: [["type: *", "effort*", "risk*", "comp: *"]] - } + }, + + rerunCircleCI: { + // set to true to disable + disabled: true, + // the label which when added triggers a rerun of the default CircleCI workflow. + triggerRerunLabel: 'Trigger CircleCI Rerun'; + }, }; export interface AppConfig { @@ -121,6 +128,7 @@ export interface AppConfig { triage: TriageConfig; triagePR: TriagePRConfig; size: SizeConfig; + rerunCircleCI: RerunCircleCIConfig; } export interface MergeConfig { @@ -187,3 +195,8 @@ export interface SizeConfig { export interface AdminConfig { allowInit: boolean; } + +export interface RerunCircleCIConfig { + disabled: boolean; + triggerRerunLabel: string; +} diff --git a/functions/src/plugins/rerun-circleci.ts b/functions/src/plugins/rerun-circleci.ts new file mode 100644 index 0000000..03aec00 --- /dev/null +++ b/functions/src/plugins/rerun-circleci.ts @@ -0,0 +1,95 @@ +import {config} from 'firebase-functions'; +import {Application, Context} from "probot"; +import {Task} from "./task"; +import {RerunCircleCIConfig} from "../default"; +import Github from '@octokit/rest'; +import fetch from "node-fetch"; + +let circleCIConfig = config().circleCI.token; + +// Check if we are in Firebase or in development +if(!circleCIConfig) { + // Use dev config + circleCIConfig = require('../../private/circle-ci.json'); +} + +const CIRCLE_CI_TOKEN = circleCIConfig.token; + +export class RerunCircleCITask extends Task { + constructor(robot: Application, db: FirebaseFirestore.Firestore) { + super(robot, db); + + // Dispatch when a label is added to a pull request. + this.dispatch([ + 'pull_request.labeled', + ], this.checkRerunCircleCI.bind(this)); + } + + /** Determines if a circle rerun should occur. */ + async checkRerunCircleCI(context: Context): Promise { + const config = await this.getConfig(context); + if (config.disabled) { + return; + } + + if (context.payload.label) { + const label: Github.IssuesGetLabelResponse = context.payload.label; + if (label.name === config.triggerRerunLabel) { + await this.triggerCircleCIRerun(context); + } + } + } + + /** Triggers a rerun of the default CircleCI workflow and then removed the triggering label. */ + async triggerCircleCIRerun(context: Context) { + const config = await this.getConfig(context); + if (config.disabled) { + return; + } + + const pullRequest: Github.PullRequestsGetResponse = context.payload.pull_request; + const sender: Github.PullRequestsGetResponseUser = context.payload.sender; + const {owner, repo} = context.repo(); + const circleCiUrl = `https://circleci.com/api/v1.1/project/github/${owner}/${repo}/build?circle-token=${CIRCLE_CI_TOKEN}`; + try { + await fetch(circleCiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + revision: pullRequest.head.sha, + branch: `pull/${pullRequest.number}`, + }) + }); + } catch (err) { + const error: TypeError = err; + context.github.issues.createComment({ + body: `@${sender.login} the CircleCI rerun you requested failed. See details below: + +\`\`\` +${error.message} +\`\`\``, + number: pullRequest.number, + owner: owner, + repo: repo, + }) + + } + await context.github.issues.removeLabel({ + name: config.triggerRerunLabel, + number: pullRequest.number, + owner: owner, + repo: repo + }); + } + + /** + * Gets the config for the merge plugin from Github or uses default if necessary + */ + async getConfig(context: Context): Promise { + const repositoryConfig = await this.getAppConfig(context); + const config = repositoryConfig.rerunCircleCI; + return config; + } +} diff --git a/functions/src/util.ts b/functions/src/util.ts index 09d5603..96fc334 100644 --- a/functions/src/util.ts +++ b/functions/src/util.ts @@ -4,6 +4,8 @@ import {MergeTask} from "./plugins/merge"; import {TriageTask} from "./plugins/triage"; import {SizeTask} from "./plugins/size"; import {TriagePRTask} from "./plugins/triagePR"; +import {RerunCircleCITask} from "./plugins/rerun-circleci"; + class Stream { constructor(private store: FirebaseFirestore.Firestore) { @@ -96,6 +98,7 @@ export interface Tasks { triageTask: TriageTask; triagePRTask: TriagePRTask; sizeTask: SizeTask; + rerunCircleCiTask: RerunCircleCITask; } export function registerTasks(robot: Application, store: FirebaseFirestore.Firestore): Tasks { @@ -106,6 +109,7 @@ export function registerTasks(robot: Application, store: FirebaseFirestore.Fires triageTask: new TriageTask(robot, store), triagePRTask: new TriagePRTask(robot, store), sizeTask: new SizeTask(robot, store), + rerunCircleCiTask: new RerunCircleCITask(robot, store), }; } diff --git a/test/fixtures/angular-robot.yml b/test/fixtures/angular-robot.yml index e2670fc..5feba70 100644 --- a/test/fixtures/angular-robot.yml +++ b/test/fixtures/angular-robot.yml @@ -167,3 +167,10 @@ triagePR: - "effort*" - "risk*" - "comp: *" + +# options for the rerun circleCI plugin +rerunCircleCI: + # set to true to disable + disabled: true + # the label which when added triggers a rerun of the default CircleCI workflow. + triggerRerunLabel: 'Trigger CircleCI Rerun'