github.com/Datadog/cnab-go@v0.3.3-beta1.0.20191007143216-bba4b7e723d0/brigade.js (about) 1 const { events, Job } = require("brigadier"); 2 3 const projectOrg = "deislabs"; 4 const projectName = "cnab-go"; 5 6 const goImg = "golang:1.11"; 7 const gopath = "/go"; 8 const localPath = gopath + `/src/github.com/${projectOrg}/${projectName}`; 9 10 const releaseTagRegex = /^refs\/tags\/(v[0-9]+(?:\.[0-9]+)*(?:\-.+)?)$/; 11 12 // ********************************************** 13 // Event Handlers 14 // ********************************************** 15 16 events.on("exec", (e, p) => { 17 return test(e, p).run(); 18 }) 19 20 events.on("push", (e, p) => { 21 let matchStr = e.revision.ref.match(releaseTagRegex); 22 23 if (matchStr) { 24 // This is an official release with a semantically versioned tag 25 let matchTokens = Array.from(matchStr); 26 let version = matchTokens[1]; 27 return test(e, p).run() 28 .then(() => { 29 githubRelease(p, version).run(); 30 }); 31 } 32 }) 33 34 events.on("check_suite:requested", runSuite); 35 events.on("check_suite:rerequested", runSuite); 36 events.on("check_run:rerequested", checkRequested); 37 events.on("issue_comment:created", handleIssueComment); 38 events.on("issue_comment:edited", handleIssueComment); 39 40 // ********************************************** 41 // Actions 42 // ********************************************** 43 44 function test(e, project) { 45 var test = new Job("tests", goImg); 46 47 // Add a bit of set up for golang build 48 test.env = { 49 DEST_PATH: localPath, 50 GOPATH: gopath 51 }; 52 53 test.tasks = [ 54 // Need to move the source into GOPATH so vendor/ works as desired. 55 `mkdir -p ${localPath}`, 56 `cp -a /src/* ${localPath}`, 57 `cp -a /src/.git ${localPath}`, 58 `cd ${localPath}`, 59 "make bootstrap", 60 "make build", 61 "make test", 62 "make lint", 63 ]; 64 65 return test; 66 } 67 68 // Here we can add additional Check Runs, which will run in parallel and 69 // report their results independently to GitHub 70 function runSuite(e, p) { 71 // For now, this is the one-stop shop running build, lint and test targets 72 return runTests(e, p); 73 } 74 75 // runTests is a Check Run that is ran as part of a Checks Suite 76 function runTests(e, p) { 77 console.log("Check requested"); 78 79 // Create Notification object (which is just a Job to update GH using the Checks API) 80 var note = new Notification(`tests`, e, p); 81 note.conclusion = ""; 82 note.title = "Run Tests"; 83 note.summary = "Running the test targets for " + e.revision.commit; 84 note.text = "This test will ensure build, linting and tests all pass." 85 86 // Send notification, then run, then send pass/fail notification 87 return notificationWrap(test(e, p), note); 88 } 89 90 // handleIssueComment handles an issue_comment event, parsing the comment 91 // text and determining whether or not to trigger a corresponding action 92 function handleIssueComment(e, p) { 93 if (e.payload) { 94 payload = JSON.parse(e.payload); 95 96 // Extract the comment body and trim whitespace 97 comment = payload.body.comment.body.trim(); 98 99 // Here we determine if a comment should provoke an action 100 switch(comment) { 101 case "/brig run": 102 return runTests(e, p); 103 default: 104 console.log(`No applicable action found for comment: ${comment}`); 105 } 106 } 107 } 108 109 // checkRequested is the default function invoked on a check_run:* event 110 // 111 // It determines which check is being requested (from the payload body) 112 // and runs this particular check, or else throws an error if the check 113 // is not found 114 function checkRequested(e, p) { 115 if (e.payload) { 116 payload = JSON.parse(e.payload); 117 118 // Extract the check name 119 name = payload.body.check_run.name; 120 121 // Determine which check to run 122 switch(name) { 123 case "tests": 124 return runTests(e, p); 125 default: 126 throw new Error(`No check found with name: ${name}`); 127 } 128 } 129 } 130 131 // githubRelease creates a new release on GitHub, named by the provided tag 132 function githubRelease(p, tag) { 133 if (!p.secrets.ghToken) { 134 throw new Error("Project must have 'secrets.ghToken' set"); 135 } 136 137 var job = new Job("release", goImg); 138 job.mountPath = localPath; 139 parts = p.repo.name.split("/", 2); 140 141 job.env = { 142 "GITHUB_USER": parts[0], 143 "GITHUB_REPO": parts[1], 144 "GITHUB_TOKEN": p.secrets.ghToken, 145 }; 146 147 job.tasks = [ 148 "go get github.com/aktau/github-release", 149 `cd ${localPath}`, 150 `last_tag=$(git describe --tags ${tag}^ --abbrev=0 --always)`, 151 `github-release release \ 152 -t ${tag} \ 153 -n "${parts[1]} ${tag}" \ 154 -d "$(git log --no-merges --pretty=format:'- %s %H (%aN)' HEAD ^$last_tag)" \ 155 || echo "release ${tag} exists"` 156 ]; 157 158 console.log(job.tasks); 159 console.log(`release at https://github.com/${p.repo.name}/releases/tag/${tag}`); 160 161 return job; 162 } 163 164 165 // ********************************************** 166 // Classes/Helpers 167 // ********************************************** 168 169 // A GitHub Check Suite notification 170 class Notification { 171 constructor(name, e, p) { 172 this.proj = p; 173 this.payload = e.payload; 174 this.name = name; 175 this.externalID = e.buildID; 176 this.detailsURL = `https://brigadecore.github.io/kashti/builds/${ e.buildID }`; 177 this.title = "running check"; 178 this.text = ""; 179 this.summary = ""; 180 181 // count allows us to send the notification multiple times, with a distinct pod name 182 // each time. 183 this.count = 0; 184 185 // One of: "success", "failure", "neutral", "cancelled", or "timed_out". 186 this.conclusion = "neutral"; 187 } 188 189 // Send a new notification, and return a Promise<result>. 190 run() { 191 this.count++; 192 var job = new Job(`${ this.name }-notification-${ this.count }`, "brigadecore/brigade-github-check-run:v0.1.0"); 193 job.imageForcePull = true; 194 job.env = { 195 "CHECK_CONCLUSION": this.conclusion, 196 "CHECK_NAME": this.name, 197 "CHECK_TITLE": this.title, 198 "CHECK_PAYLOAD": this.payload, 199 "CHECK_SUMMARY": this.summary, 200 "CHECK_TEXT": this.text, 201 "CHECK_DETAILS_URL": this.detailsURL, 202 "CHECK_EXTERNAL_ID": this.externalID 203 }; 204 return job.run(); 205 } 206 } 207 208 // Helper to wrap a job execution between two notifications. 209 async function notificationWrap(job, note) { 210 await note.run(); 211 try { 212 let res = await job.run(); 213 const logs = await job.logs(); 214 note.conclusion = "success"; 215 note.summary = `Task "${ job.name }" passed`; 216 note.text = "```" + res.toString() + "```\nTest Complete"; 217 return await note.run(); 218 } catch (e) { 219 const logs = await job.logs(); 220 note.conclusion = "failure"; 221 note.summary = `Task "${ job.name }" failed for ${ e.buildID }`; 222 note.text = "```" + logs + "```\nFailed with error: " + e.toString(); 223 try { 224 await note.run(); 225 } catch (e2) { 226 console.error("failed to send notification: " + e2.toString()); 227 console.error("original error: " + e.toString()); 228 } 229 throw e; 230 } 231 }