├── .prettierignore ├── .gitignore ├── NOTICE ├── CODE_OF_CONDUCT.md ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci-unit-tests.yaml ├── .eslintrc.json ├── action.yml ├── index.js ├── package.json ├── local.js ├── CONTRIBUTING.md ├── code-build.js ├── LICENSE ├── README.md └── test └── code-build-test.js /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.DS_Store 3 | /.history 4 | .idea/ 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 4 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 5 | opensource-codeofconduct@amazon.com with any additional questions or comments. 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | 8 | # Check any applicable: 9 | - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. 10 | 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": ["eslint:recommended", "prettier"], 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018 15 | }, 16 | "ignorePatterns": ["dist/**"], 17 | "rules": {} 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/ci-unit-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | ci-unit-tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v1 11 | with: 12 | # This should match the using value in `actions.yaml` 13 | node-version: 12 14 | - run: npm ci 15 | - run: npm run lint 16 | - run: npm run test 17 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: '"AWS CodeBuild run build" Action For GitHub Actions' 2 | description: 'Execute CodeBuild::startBuild for the current repo.' 3 | branding: 4 | icon: 'cloud' 5 | color: 'orange' 6 | inputs: 7 | project-name: 8 | description: 'AWS CodeBuild Project Name' 9 | required: true 10 | buildspec-override: 11 | description: 'Buildspec Override' 12 | required: false 13 | env-vars-for-codebuild: 14 | description: 'Comma separated list of environment variables to send to CodeBuild' 15 | required: false 16 | outputs: 17 | aws-build-id: 18 | description: 'The AWS CodeBuild Build ID for this build.' 19 | runs: 20 | using: 'node12' 21 | main: 'dist/index.js' -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const core = require("@actions/core"); 5 | const { runBuild } = require("./code-build"); 6 | const assert = require("assert"); 7 | 8 | /* istanbul ignore next */ 9 | if (require.main === module) { 10 | run(); 11 | } 12 | 13 | module.exports = run; 14 | 15 | async function run() { 16 | console.log("*****STARTING CODEBUILD*****"); 17 | try { 18 | const build = await runBuild(); 19 | core.setOutput("aws-build-id", build.id); 20 | 21 | // Signal the outcome 22 | assert( 23 | build.buildStatus === "SUCCEEDED", 24 | `Build status: ${build.buildStatus}` 25 | ); 26 | } catch (error) { 27 | core.setFailed(error.message); 28 | } finally { 29 | console.log("*****CODEBUILD COMPLETE*****"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aws-actions/codebuild-run-build", 3 | "version": "1.0.3", 4 | "description": "Execute CodeBuild::startBuild for the current repo.", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "prettier -c *.js *.json *.md test/*.js; eslint **.js test/**.js", 8 | "format": "prettier --write -c *.js *.json *.md test/*.js; eslint --fix **.js test/**.js", 9 | "package": "ncc build index.js -o dist", 10 | "test": "mocha" 11 | }, 12 | "author": "aws-crypto-tools-team@amazon.com", 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "@actions/core": "^1.2.3", 16 | "@actions/exec": "^1.0.3", 17 | "@actions/github": "^2.1.1", 18 | "aws-sdk": "^2.654.0", 19 | "uuid": "^3.4.0", 20 | "yargs": "^15.3.1" 21 | }, 22 | "bin": "./local.js", 23 | "files": [ 24 | "*.js" 25 | ], 26 | "devDependencies": { 27 | "@zeit/ncc": "^0.21.1", 28 | "chai": "^4.2.0", 29 | "eslint": "^6.8.0", 30 | "eslint-config-prettier": "^6.10.1", 31 | "husky": "^4.2.3", 32 | "lint-staged": "^10.1.2", 33 | "mocha": "^7.1.1", 34 | "prettier": "^2.0.4" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "lint-staged" 39 | } 40 | }, 41 | "lint-staged": { 42 | "*.js": [ 43 | "prettier --write --ignore-path dist/**", 44 | "eslint --fix --ignore-pattern dist/**" 45 | ], 46 | "*.{json,md}": [ 47 | "prettier --write" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /local.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | const uuid = require("uuid/v4"); 6 | const cp = require("child_process"); 7 | const cb = require("./code-build"); 8 | const assert = require("assert"); 9 | const yargs = require("yargs"); 10 | 11 | const { projectName, buildspecOverride, envPassthrough, remote } = yargs 12 | .option("project-name", { 13 | alias: "p", 14 | describe: "AWS CodeBuild Project Name", 15 | demandOption: true, 16 | type: "string", 17 | }) 18 | .option("buildspec-override", { 19 | alias: "b", 20 | describe: "Path to buildspec file", 21 | type: "string", 22 | }) 23 | .option("env-vars-for-codebuild", { 24 | alias: "e", 25 | describe: "List of environment variables to send to CodeBuild", 26 | type: "array", 27 | }) 28 | .option("remote", { 29 | alias: "r", 30 | describe: "remote name to publish to", 31 | default: "origin", 32 | type: "string", 33 | }).argv; 34 | 35 | const BRANCH_NAME = uuid(); 36 | 37 | const params = cb.inputs2Parameters({ 38 | projectName, 39 | ...githubInfo(remote), 40 | sourceVersion: BRANCH_NAME, 41 | buildspecOverride, 42 | envPassthrough, 43 | }); 44 | 45 | const sdk = cb.buildSdk(); 46 | 47 | pushBranch(remote, BRANCH_NAME); 48 | 49 | cb.build(sdk, params) 50 | .then(() => deleteBranch(remote, BRANCH_NAME)) 51 | .catch((err) => { 52 | deleteBranch(remote, BRANCH_NAME); 53 | throw err; 54 | }); 55 | 56 | function pushBranch(remote, branchName) { 57 | cp.execSync(`git push ${remote} HEAD:${branchName}`); 58 | } 59 | 60 | function deleteBranch(remote, branchName) { 61 | cp.execSync(`git push ${remote} :${branchName}`); 62 | } 63 | 64 | function githubInfo(remote) { 65 | const gitHubSSH = "git@github.com:"; 66 | const gitHubHTTPS = "https://github.com/"; 67 | /* Expecting to match something like: 68 | * 'fork git@github.com:seebees/aws-codebuild-run-build.git (push)' 69 | * Which is the output of `git remote -v` 70 | */ 71 | const remoteMatch = new RegExp(`^${remote}.*\\(push\\)$`); 72 | /* Not doing a grep because then I have to pass user input to the shell. 73 | * This way I don't have to worry about sanitizing and injection and all that jazz. 74 | * Further, when I _do_ pass the remote into the shell to push to it, 75 | * given that I find it in the remote list, 76 | * I feel confident that there are no shinanaigans. 77 | */ 78 | const [gitRemote] = cp 79 | .execSync("git remote -v") 80 | .toString() 81 | .split("\n") 82 | .filter((line) => line.trim().match(remoteMatch)); 83 | assert(gitRemote, `No remote found named ${remote}`); 84 | const [, url] = gitRemote.split(/[\t ]/); 85 | if (url.startsWith(gitHubHTTPS)) { 86 | const [owner, repo] = url.slice(gitHubHTTPS.length, -4).split("/"); 87 | return { owner, repo }; 88 | } else if (url.startsWith(gitHubSSH)) { 89 | const [owner, repo] = url.slice(gitHubSSH.length, -4).split("/"); 90 | return { owner, repo }; 91 | } else { 92 | throw new Error(`Unsupported format: ${url}`); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | ## Reporting Bugs/Feature Requests 10 | 11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 12 | 13 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 15 | 16 | - A reproducible test case or series of steps 17 | - The version of our code being used 18 | - Any modifications you've made relevant to the bug 19 | - Anything unusual about your environment or deployment 20 | 21 | ## Contributing via Pull Requests 22 | 23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 24 | 25 | 1. You are working against the latest source on the _master_ branch. 26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 28 | 29 | To send us a pull request, please: 30 | 31 | 1. Fork the repository. 32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 33 | 3. Ensure local tests pass. 34 | 4. Commit to your fork using clear commit messages. 35 | 5. Send us a pull request, answering any default questions in the pull request interface. 36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 37 | 38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | ## Finding contributions to work on 42 | 43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 44 | 45 | ## Code of Conduct 46 | 47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 49 | opensource-codeofconduct@amazon.com with any additional questions or comments. 50 | 51 | ## Security issue notifications 52 | 53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 54 | 55 | ## Licensing 56 | 57 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 58 | 59 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 60 | -------------------------------------------------------------------------------- /code-build.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const core = require("@actions/core"); 5 | const github = require("@actions/github"); 6 | const aws = require("aws-sdk"); 7 | const assert = require("assert"); 8 | 9 | module.exports = { 10 | runBuild, 11 | build, 12 | waitForBuildEndTime, 13 | inputs2Parameters, 14 | githubInputs, 15 | buildSdk, 16 | logName, 17 | }; 18 | 19 | function runBuild() { 20 | // get a codeBuild instance from the SDK 21 | const sdk = buildSdk(); 22 | 23 | // Get input options for startBuild 24 | const params = inputs2Parameters(githubInputs()); 25 | 26 | return build(sdk, params); 27 | } 28 | 29 | async function build(sdk, params) { 30 | // Start the build 31 | const start = await sdk.codeBuild.startBuild(params).promise(); 32 | 33 | // Wait for the build to "complete" 34 | return waitForBuildEndTime(sdk, start.build); 35 | } 36 | 37 | async function waitForBuildEndTime(sdk, { id, logs }, nextToken) { 38 | const { 39 | codeBuild, 40 | cloudWatchLogs, 41 | wait = 1000 * 30, 42 | backOff = 1000 * 15, 43 | } = sdk; 44 | 45 | // Get the CloudWatchLog info 46 | const startFromHead = true; 47 | const { cloudWatchLogsArn } = logs; 48 | const { logGroupName, logStreamName } = logName(cloudWatchLogsArn); 49 | 50 | let errObject = false; 51 | 52 | // Check the state 53 | const [batch, cloudWatch = {}] = await Promise.all([ 54 | codeBuild.batchGetBuilds({ ids: [id] }).promise(), 55 | // The CloudWatchLog _may_ not be set up, only make the call if we have a logGroupName 56 | logGroupName && 57 | cloudWatchLogs 58 | .getLogEvents({ logGroupName, logStreamName, startFromHead, nextToken }) 59 | .promise(), 60 | ]).catch((err) => { 61 | errObject = err; 62 | /* Returning [] here so that the assignment above 63 | * does not throw `TypeError: undefined is not iterable`. 64 | * The error is handled below, 65 | * since it might be a rate limit. 66 | */ 67 | return []; 68 | }); 69 | 70 | if (errObject) { 71 | //We caught an error in trying to make the AWS api call, and are now checking to see if it was just a rate limiting error 72 | if (errObject.message && errObject.message.search("Rate exceeded") !== -1) { 73 | //We were rate-limited, so add `backOff` seconds to the wait time 74 | let newWait = wait + backOff; 75 | 76 | //Sleep before trying again 77 | await new Promise((resolve) => setTimeout(resolve, newWait)); 78 | 79 | // Try again from the same token position 80 | return waitForBuildEndTime( 81 | { ...sdk, wait: newWait }, 82 | { id, logs }, 83 | nextToken 84 | ); 85 | } else { 86 | //The error returned from the API wasn't about rate limiting, so throw it as an actual error and fail the job 87 | throw errObject; 88 | } 89 | } 90 | 91 | // Pluck off the relevant state 92 | const [current] = batch.builds; 93 | const { nextForwardToken, events = [] } = cloudWatch; 94 | 95 | // stdout the CloudWatchLog (everyone likes progress...) 96 | // CloudWatchLogs have line endings. 97 | // I trim and then log each line 98 | // to ensure that the line ending is OS specific. 99 | events.forEach(({ message }) => console.log(message.trimEnd())); 100 | 101 | // We did it! We can stop looking! 102 | if (current.endTime && !events.length) return current; 103 | 104 | // More to do: Sleep for a few seconds to avoid rate limiting 105 | await new Promise((resolve) => setTimeout(resolve, wait)); 106 | 107 | // Try again 108 | return waitForBuildEndTime(sdk, current, nextForwardToken); 109 | } 110 | 111 | function githubInputs() { 112 | const projectName = core.getInput("project-name", { required: true }); 113 | const { owner, repo } = github.context.repo; 114 | const { payload } = github.context; 115 | // The github.context.sha is evaluated on import. 116 | // This makes it hard to test. 117 | // So I use the raw ENV. 118 | // There is a complexity here because for pull request 119 | // the GITHUB_SHA value is NOT the correct value. 120 | // See: https://github.com/aws-actions/aws-codebuild-run-build/issues/36 121 | const sourceVersion = 122 | process.env[`GITHUB_EVENT_NAME`] === "pull_request" 123 | ? (((payload || {}).pull_request || {}).head || {}).sha 124 | : process.env[`GITHUB_SHA`]; 125 | 126 | assert(sourceVersion, "No source version could be evaluated."); 127 | const buildspecOverride = 128 | core.getInput("buildspec-override", { required: false }) || undefined; 129 | 130 | const envPassthrough = core 131 | .getInput("env-vars-for-codebuild", { required: false }) 132 | .split(",") 133 | .map((i) => i.trim()) 134 | .filter((i) => i !== ""); 135 | 136 | return { 137 | projectName, 138 | owner, 139 | repo, 140 | sourceVersion, 141 | buildspecOverride, 142 | envPassthrough, 143 | }; 144 | } 145 | 146 | function inputs2Parameters(inputs) { 147 | const { 148 | projectName, 149 | owner, 150 | repo, 151 | sourceVersion, 152 | buildspecOverride, 153 | envPassthrough = [], 154 | } = inputs; 155 | 156 | const sourceTypeOverride = "GITHUB"; 157 | const sourceLocationOverride = `https://github.com/${owner}/${repo}.git`; 158 | 159 | const environmentVariablesOverride = Object.entries(process.env) 160 | .filter( 161 | ([key]) => key.startsWith("GITHUB_") || envPassthrough.includes(key) 162 | ) 163 | .map(([name, value]) => ({ name, value, type: "PLAINTEXT" })); 164 | 165 | // The idempotencyToken is intentionally not set. 166 | // This way the GitHub events can manage the builds. 167 | return { 168 | projectName, 169 | sourceVersion, 170 | sourceTypeOverride, 171 | sourceLocationOverride, 172 | buildspecOverride, 173 | environmentVariablesOverride, 174 | }; 175 | } 176 | 177 | function buildSdk() { 178 | const codeBuild = new aws.CodeBuild({ 179 | customUserAgent: "aws-actions/aws-codebuild-run-build", 180 | }); 181 | 182 | const cloudWatchLogs = new aws.CloudWatchLogs({ 183 | customUserAgent: "aws-actions/aws-codebuild-run-build", 184 | }); 185 | 186 | assert( 187 | codeBuild.config.credentials && cloudWatchLogs.config.credentials, 188 | "No credentials. Try adding @aws-actions/configure-aws-credentials earlier in your job to set up AWS credentials." 189 | ); 190 | 191 | return { codeBuild, cloudWatchLogs }; 192 | } 193 | 194 | function logName(Arn) { 195 | const [logGroupName, logStreamName] = Arn.split(":log-group:") 196 | .pop() 197 | .split(":log-stream:"); 198 | if (logGroupName === "null" || logStreamName === "null") 199 | return { 200 | logGroupName: undefined, 201 | logStreamName: undefined, 202 | }; 203 | return { logGroupName, logStreamName }; 204 | } 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS CodeBuild Run Build for GitHub Actions 2 | 3 | This action runs a [AWS CodeBuild][codebuild] [project][codebuild project] 4 | as a step in a GitHub Actions workflow job. 5 | 6 | The action builds the CodeBuild project, collects the build logs, and prints them as they are written. 7 | The user experience is the same as it would be if the logic were executed 8 | in the GitHub Actions job runner. 9 | 10 | [Security issue notifications](./CONTRIBUTING.md#security-issue-notifications) 11 | 12 | ## Usage 13 | 14 | ### Inputs 15 | 16 | This action offers three inputs that you can use to configure its behavior. 17 | The only required input is `project-name`. 18 | 19 | 1. **project-name** (required) : The name of CodeBuild project you want to run. 20 | 1. **buildspec-override** (optional) : 21 | The location (in this repository) of the [buildspec file][codebuild buildspec] 22 | that CodeBuild requires. 23 | By default, the action uses the buildspec file location 24 | that you configured in the CodeBuild project. 25 | 1. **env-vars-for-codebuild** (optional) : 26 | A comma-separated list of the names of environment variables 27 | that the action passes from GitHub Actions to CodeBuild. 28 | 29 | The action passes these environment variables to CodeBuild 30 | along with any environment variables that have a `github` prefix. 31 | 32 | This list is often the same or a subset of the list of environment variables 33 | that you define for GitHub actions in the `env` property. 34 | 35 | Note: If you specify an environment variable 36 | with the same name as one defined in your CodeBuild project, 37 | the one defined here replaces the one in the CodeBuild project. 38 | For a list of CodeBuild environment variables, see 39 | 40 | ### Outputs 41 | 42 | 1. **aws-build-id** : The CodeBuild build ID of the build that the action ran. 43 | 44 | ## Purpose 45 | 46 | This action is designed to give you the power of GitHub Actions 47 | with options available in [AWS CodeBuild][codebuild] for more CPU and memory, 48 | and access to other resources. 49 | 50 | GitHub Actions provides a powerful system of event-based workflows, 51 | but the hosted job runners cannot exceed the defined computing and memory limits, 52 | and might prevent you from accessing resources that you need for your project. 53 | 54 | [AWS CodeBuild][codebuild] is a fully managed continuous integration service 55 | that can compile source code, run tests, and produce software packages that are ready to deploy. 56 | It supports more environment options than standard GitHub Actions, 57 | including a selection of powerful computing environments with additional memory. 58 | 59 | ### Resources and Architecture 60 | 61 | [GitHub Actions job runners][github actions job runners] have 2 x86_64 CPU cores and 7 GB RAM. 62 | 63 | This is enough for the most common activities, 64 | but some large or complex builds need more resources, 65 | and some builds need access to special CPU architectures or hardware. 66 | 67 | [CodeBuild compute types][codebuild compute types] offer options including: 68 | 69 | - up to 72 x86_64 vCPUs 70 | - up to 255 GB RAM 71 | - up to 8 ARM64 vCPUs 72 | - GPU hardware devices 73 | 74 | ### Access 75 | 76 | Your workflow might require access to assets, configuration, or resources 77 | that are impossible, difficult, or simply expensive 78 | to access from GitHub's hosted job runners 79 | but are easy or cheap to access from CodeBuild. 80 | 81 | ## Credentials and Permissions 82 | 83 | In order for the action to run your CodeBuild project, 84 | you need to provide AWS credentials. 85 | We recommend using [aws-actions/configure-aws-credentials] 86 | to configure your credentials for a job. 87 | 88 | **NOTE: 89 | GitHub Secrets are not passed to the runner when a workflow is triggered from a forked repository. 90 | This means that you cannot use this action directly in a workflow 91 | that is triggered by pull requests from a fork. 92 | See the [GitHub Secrets docs][github secrets access] for more information.** 93 | 94 | The credentials that you provide need to have the following permissions: 95 | 96 | - `codebuild:StartBuild` 97 | - `codebuild:BatchGetBuilds` 98 | - `logs:GetLogEvents` 99 | 100 | For example: 101 | 102 | ```json 103 | { 104 | "Version": "2012-10-17", 105 | "Statement": [ 106 | { 107 | "Effect": "Allow", 108 | "Action": ["codebuild:StartBuild", "codebuild:BatchGetBuilds"], 109 | "Resource": ["arn:aws:codebuild:REGION:ACCOUNT_ID:project/PROJECT_NAME"] 110 | }, 111 | { 112 | "Effect": "Allow", 113 | "Action": ["logs:GetLogEvents"], 114 | "Resource": [ 115 | "arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/codebuild/PROJECT_NAME:*" 116 | ] 117 | } 118 | ] 119 | } 120 | ``` 121 | 122 | ## Examples 123 | 124 | These examples show how you can define a step in a workflow job. 125 | For more information about GitHub Actions workflow syntax, 126 | see the [GitHub docs][github workflow syntax]. 127 | 128 | If your CodeBuild project is already configured the way you want it, 129 | the only CodeBuild Run input you need to provide is the project name. 130 | 131 | ```yaml 132 | - name: Configure AWS Credentials 133 | uses: aws-actions/configure-aws-credentials@v1 134 | with: 135 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 136 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 137 | aws-region: us-east-2 138 | - name: Run CodeBuild 139 | uses: aws-actions/aws-codebuild-run-project@v1.0.0 140 | with: 141 | project-name: CodeBuildProjectName 142 | ``` 143 | 144 | If you reuse a project in multiple jobs or repositories, 145 | you might want to provide a bit more configuration. 146 | For example, the following configuration 147 | specifies an alternate location for the buildspec file. 148 | It also tells AWS CodeBuild Run Build 149 | to send all of the environment variables defined in the `env:` list to CodeBuild. 150 | If any of these environment variables are defined in the CodeBuild project, 151 | this will overwrite them. 152 | 153 | ```yaml 154 | - name: Configure AWS Credentials 155 | uses: aws-actions/configure-aws-credentials@v1 156 | with: 157 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 158 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 159 | aws-region: us-east-2 160 | - name: Run CodeBuild 161 | uses: aws-actions/aws-codebuild-run-project@v1.0.0 162 | with: 163 | project-name: CodeBuildProjectName 164 | buildspec-override: path/to/buildspec.yaml 165 | env-vars-for-codebuild: | 166 | custom, 167 | requester, 168 | event-name 169 | env: 170 | custom: my environment variable 171 | requester: ${{ github.actor }} 172 | event-name: ${{ github.event_name }} 173 | ``` 174 | 175 | ### Running Locally 176 | 177 | It can be useful to run a build outside of CI. 178 | So, this action can also be installed locally 179 | to kick off a CodeBuild project from your git sandbox. 180 | You could push your changes to an open PR, 181 | but if you only want to test one project this may be faster. 182 | In order to use this tool, 183 | you must first `git checkout` the commit that you want to test. 184 | 185 | ``` 186 | npx @aws-actions/codebuild-run-build -p ProjectName -r remoteName 187 | ``` 188 | 189 | This will use whatever commit you have checked out 190 | and push to a temporary branch in the specified remote. 191 | Then kick off the build 192 | and delete the remote branch when complete. 193 | 194 | You can also install the project globally or locally 195 | and execute it that way. 196 | 197 | ## Implementation Notes 198 | 199 | ### What we did 200 | 201 | We call the [CodeBuild `StartBuild` API][codebuild startbuild], 202 | checking out the commit that triggered the workflow. 203 | 204 | The action waits for the build to complete while logging everything written to the build's 205 | [Amazon CloudWatch Logs][cloudwatch logs] [logstream][cloudwatch logs concepts]. 206 | If the `buildStatus` value in the StartBuild response is `SUCCEEDED`, the action succeeds. 207 | Otherwise, it fails. 208 | 209 | In the call to StartBuild, we pass in all 210 | `GITHUB_` [environment variables][github environment variables] in the GitHub Actions environment, 211 | plus any environment variables that you specified in the `evn-passthrough` input value. 212 | 213 | Regardless of the project configuration in CodeBuild or GitHub Actions, 214 | we always pass the following parameters and values to CodeBuild in the StartBuild API call. 215 | 216 | | CodeBuild value | GitHub value | 217 | | ------------------------ | -------------------------------------- | 218 | | `sourceVersion` | The commit that triggered the workflow | 219 | | `sourceTypeOverride` | The string `'GITHUB'` | 220 | | `sourceLocationOverride` | The `HTTPS` git url for `context.repo` | 221 | 222 | ### What we did not do 223 | 224 | This action intentionally does not let you specify every option 225 | in the [CodeBuild::StartBuild][codebuild startbuild] API. 226 | 227 | Because all GitHub Actions input values are passed through environment variables, 228 | they must be simple strings. 229 | This makes it difficult to pass complex structures as inputs. 230 | 231 | Also, providing an input for every parameter in the `StartBuild` API 232 | would have made it much more difficult to use and maintain this tool. 233 | We would have to add many more inputs or require string values, 234 | while hoping that all supported configurations 235 | conformed to the environment variable length limits. 236 | 237 | For this reason, and to simplify what we expect to be the most common use-cases, 238 | we chose to start with the simplest possible configuration. 239 | If you find that these options don't meet your needs, please open an issue to let us know. 240 | 241 | ## License 242 | 243 | This SDK is distributed under the 244 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), 245 | see LICENSE and NOTICE for more information. 246 | 247 | [codebuild]: https://docs.aws.amazon.com/codebuild/latest/userguide/welcome.html 248 | [codebuild project]: https://docs.aws.amazon.com/codebuild/latest/userguide/working-with-build-projects.html 249 | [codebuild startbuild]: https://docs.aws.amazon.com/codebuild/latest/APIReference/API_StartBuild.html 250 | [codebuild compute types]: https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html 251 | [codebuild buildspec]: https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html 252 | [cloudwatch logs]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html 253 | [cloudwatch logs concepts]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogsConcepts.html 254 | [github environment variables]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables 255 | [github actions job runners]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources 256 | [github workflow syntax]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions 257 | [github secrets access]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#using-encrypted-secrets-in-a-workflow 258 | [aws-actions/configure-aws-credentials]: https://github.com/aws-actions/configure-aws-credentials 259 | -------------------------------------------------------------------------------- /test/code-build-test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const { 5 | logName, 6 | githubInputs, 7 | inputs2Parameters, 8 | waitForBuildEndTime, 9 | } = require("../code-build"); 10 | const { expect } = require("chai"); 11 | 12 | describe("logName", () => { 13 | it("return the logGroupName and logStreamName from an ARN", () => { 14 | const arn = 15 | "arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab"; 16 | const test = logName(arn); 17 | expect(test) 18 | .to.haveOwnProperty("logGroupName") 19 | .and.to.equal("/aws/codebuild/CloudWatchLogGroup"); 20 | expect(test) 21 | .to.haveOwnProperty("logStreamName") 22 | .and.to.equal("1234abcd-12ab-34cd-56ef-1234567890ab"); 23 | }); 24 | 25 | it("return undefined when the group and stream are null", () => { 26 | const arn = 27 | "arn:aws:logs:us-west-2:111122223333:log-group:null:log-stream:null"; 28 | const test = logName(arn); 29 | expect(test).to.haveOwnProperty("logGroupName").and.to.equal(undefined); 30 | expect(test).to.haveOwnProperty("logStreamName").and.to.equal(undefined); 31 | }); 32 | }); 33 | 34 | describe("githubInputs", () => { 35 | const OLD_ENV = { ...process.env }; 36 | const { context: OLD_CONTEXT } = require("@actions/github"); 37 | const { payload: OLD_PAYLOAD, eventName: OLD_EVENT_NAME } = OLD_CONTEXT; 38 | afterEach(() => { 39 | process.env = { ...OLD_ENV }; 40 | const { context } = require("@actions/github"); 41 | context.eventName = OLD_EVENT_NAME; 42 | context.payload = OLD_PAYLOAD; 43 | }); 44 | 45 | const projectName = "project_name"; 46 | const repoInfo = "owner/repo"; 47 | const sha = "1234abcd-12ab-34cd-56ef-1234567890ab"; 48 | const pullRequestSha = "181600acb3cfb803f4570d0018928be5d730c00d"; 49 | 50 | it("build basic parameters for codeBuild.startBuild", () => { 51 | // This is how GITHUB injects its input values. 52 | // It would be nice if there was an easy way to test this... 53 | process.env[`INPUT_PROJECT-NAME`] = projectName; 54 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 55 | process.env[`GITHUB_SHA`] = sha; 56 | // These tests run in pull requests 57 | // so to tests things that are NOT pull request... 58 | process.env[`GITHUB_EVENT_NAME`] = "not_pull_request"; 59 | const test = githubInputs(); 60 | expect(test).to.haveOwnProperty("projectName").and.to.equal(projectName); 61 | expect(test).to.haveOwnProperty("sourceVersion").and.to.equal(sha); 62 | expect(test).to.haveOwnProperty("owner").and.to.equal(`owner`); 63 | expect(test).to.haveOwnProperty("repo").and.to.equal(`repo`); 64 | expect(test) 65 | .to.haveOwnProperty("buildspecOverride") 66 | .and.to.equal(undefined); 67 | expect(test).to.haveOwnProperty("envPassthrough").and.to.deep.equal([]); 68 | }); 69 | 70 | it("a project name is required.", () => { 71 | expect(() => githubInputs()).to.throw(); 72 | }); 73 | 74 | it("can process env-vars-for-codebuild", () => { 75 | // This is how GITHUB injects its input values. 76 | // It would be nice if there was an easy way to test this... 77 | process.env[`INPUT_PROJECT-NAME`] = projectName; 78 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 79 | process.env[`GITHUB_SHA`] = sha; 80 | 81 | process.env[`INPUT_ENV-VARS-FOR-CODEBUILD`] = `one, two 82 | , three, 83 | four `; 84 | 85 | process.env.one = "_one_"; 86 | process.env.two = "_two_"; 87 | process.env.three = "_three_"; 88 | process.env.four = "_four_"; 89 | 90 | const test = githubInputs(); 91 | 92 | expect(test) 93 | .to.haveOwnProperty("envPassthrough") 94 | .and.to.deep.equal(["one", "two", "three", "four"]); 95 | }); 96 | 97 | it("can handle pull requests", () => { 98 | // This is how GITHUB injects its input values. 99 | // It would be nice if there was an easy way to test this... 100 | process.env[`INPUT_PROJECT-NAME`] = projectName; 101 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 102 | process.env[`GITHUB_SHA`] = sha; 103 | process.env[`GITHUB_EVENT_NAME`] = "pull_request"; 104 | const { context } = require("@actions/github"); 105 | context.payload = { pull_request: { head: { sha: pullRequestSha } } }; 106 | const test = githubInputs(); 107 | expect(test).to.haveOwnProperty("projectName").and.to.equal(projectName); 108 | expect(test) 109 | .to.haveOwnProperty("sourceVersion") 110 | .and.to.equal(pullRequestSha); 111 | expect(test).to.haveOwnProperty("owner").and.to.equal(`owner`); 112 | expect(test).to.haveOwnProperty("repo").and.to.equal(`repo`); 113 | expect(test) 114 | .to.haveOwnProperty("buildspecOverride") 115 | .and.to.equal(undefined); 116 | expect(test).to.haveOwnProperty("envPassthrough").and.to.deep.equal([]); 117 | }); 118 | 119 | it("will not continue if there is no payload", () => { 120 | // This is how GITHUB injects its input values. 121 | // It would be nice if there was an easy way to test this... 122 | process.env[`INPUT_PROJECT-NAME`] = projectName; 123 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 124 | process.env[`GITHUB_SHA`] = sha; 125 | process.env[`GITHUB_EVENT_NAME`] = "pull_request"; 126 | // These tests run in pull requests 127 | // so to tests things that are NOT pull request... 128 | require("@actions/github").context.payload = {}; 129 | 130 | expect(() => githubInputs()).to.throw( 131 | "No source version could be evaluated." 132 | ); 133 | }); 134 | }); 135 | 136 | describe("inputs2Parameters", () => { 137 | const OLD_ENV = { ...process.env }; 138 | afterEach(() => { 139 | process.env = { ...OLD_ENV }; 140 | }); 141 | 142 | const projectName = "project_name"; 143 | const repoInfo = "owner/repo"; 144 | const sha = "1234abcd-12ab-34cd-56ef-1234567890ab"; 145 | 146 | it("build basic parameters for codeBuild.startBuild", () => { 147 | // This is how GITHUB injects its input values. 148 | // It would be nice if there was an easy way to test this... 149 | process.env[`INPUT_PROJECT-NAME`] = projectName; 150 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 151 | process.env[`GITHUB_SHA`] = sha; 152 | const test = inputs2Parameters({ 153 | projectName, 154 | sourceVersion: sha, 155 | owner: "owner", 156 | repo: "repo", 157 | }); 158 | expect(test).to.haveOwnProperty("projectName").and.to.equal(projectName); 159 | expect(test).to.haveOwnProperty("sourceVersion").and.to.equal(sha); 160 | expect(test) 161 | .to.haveOwnProperty("sourceTypeOverride") 162 | .and.to.equal("GITHUB"); 163 | expect(test) 164 | .to.haveOwnProperty("sourceLocationOverride") 165 | .and.to.equal(`https://github.com/owner/repo.git`); 166 | expect(test) 167 | .to.haveOwnProperty("buildspecOverride") 168 | .and.to.equal(undefined); 169 | 170 | // I send everything that starts 'GITHUB_' 171 | expect(test) 172 | .to.haveOwnProperty("environmentVariablesOverride") 173 | .and.to.have.lengthOf.greaterThan(1); 174 | 175 | const [repoEnv] = test.environmentVariablesOverride.filter( 176 | ({ name }) => name === "GITHUB_REPOSITORY" 177 | ); 178 | expect(repoEnv) 179 | .to.haveOwnProperty("name") 180 | .and.to.equal("GITHUB_REPOSITORY"); 181 | expect(repoEnv).to.haveOwnProperty("value").and.to.equal(repoInfo); 182 | expect(repoEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 183 | 184 | const [shaEnv] = test.environmentVariablesOverride.filter( 185 | ({ name }) => name === "GITHUB_SHA" 186 | ); 187 | expect(shaEnv).to.haveOwnProperty("name").and.to.equal("GITHUB_SHA"); 188 | expect(shaEnv).to.haveOwnProperty("value").and.to.equal(sha); 189 | expect(shaEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 190 | }); 191 | 192 | it("can process env-vars-for-codebuild", () => { 193 | // This is how GITHUB injects its input values. 194 | // It would be nice if there was an easy way to test this... 195 | process.env[`INPUT_PROJECT-NAME`] = projectName; 196 | process.env[`GITHUB_REPOSITORY`] = repoInfo; 197 | process.env[`GITHUB_SHA`] = sha; 198 | 199 | process.env[`INPUT_ENV-VARS-FOR-CODEBUILD`] = `one, two 200 | , three, 201 | four `; 202 | 203 | process.env.one = "_one_"; 204 | process.env.two = "_two_"; 205 | process.env.three = "_three_"; 206 | process.env.four = "_four_"; 207 | 208 | const test = inputs2Parameters({ 209 | projectName, 210 | sourceVersion: sha, 211 | owner: "owner", 212 | repo: "repo", 213 | envPassthrough: ["one", "two", "three", "four"], 214 | }); 215 | 216 | expect(test) 217 | .to.haveOwnProperty("environmentVariablesOverride") 218 | .and.to.have.lengthOf.greaterThan(5); 219 | 220 | const [oneEnv] = test.environmentVariablesOverride.filter( 221 | ({ name }) => name === "one" 222 | ); 223 | expect(oneEnv).to.haveOwnProperty("name").and.to.equal("one"); 224 | expect(oneEnv).to.haveOwnProperty("value").and.to.equal("_one_"); 225 | expect(oneEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 226 | 227 | const [twoEnv] = test.environmentVariablesOverride.filter( 228 | ({ name }) => name === "two" 229 | ); 230 | expect(twoEnv).to.haveOwnProperty("name").and.to.equal("two"); 231 | expect(twoEnv).to.haveOwnProperty("value").and.to.equal("_two_"); 232 | expect(twoEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 233 | 234 | const [threeEnv] = test.environmentVariablesOverride.filter( 235 | ({ name }) => name === "three" 236 | ); 237 | expect(threeEnv).to.haveOwnProperty("name").and.to.equal("three"); 238 | expect(threeEnv).to.haveOwnProperty("value").and.to.equal("_three_"); 239 | expect(threeEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 240 | 241 | const [fourEnv] = test.environmentVariablesOverride.filter( 242 | ({ name }) => name === "four" 243 | ); 244 | expect(fourEnv).to.haveOwnProperty("name").and.to.equal("four"); 245 | expect(fourEnv).to.haveOwnProperty("value").and.to.equal("_four_"); 246 | expect(fourEnv).to.haveOwnProperty("type").and.to.equal("PLAINTEXT"); 247 | }); 248 | }); 249 | 250 | describe("waitForBuildEndTime", () => { 251 | it("basic usages", async () => { 252 | let count = 0; 253 | const buildID = "buildID"; 254 | const cloudWatchLogsArn = 255 | "arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab"; 256 | 257 | const buildReplies = [ 258 | { 259 | builds: [ 260 | { id: buildID, logs: { cloudWatchLogsArn }, endTime: "endTime" }, 261 | ], 262 | }, 263 | ]; 264 | const logReplies = [{ events: [] }]; 265 | const sdk = help( 266 | () => buildReplies[count++], 267 | () => logReplies[count - 1] 268 | ); 269 | 270 | const test = await waitForBuildEndTime(sdk, { 271 | id: buildID, 272 | logs: { cloudWatchLogsArn }, 273 | }); 274 | 275 | expect(test).to.equal(buildReplies.pop().builds[0]); 276 | }); 277 | 278 | it("waits for a build endTime **and** no cloud watch log events", async function () { 279 | this.timeout(25000); 280 | let count = 0; 281 | const buildID = "buildID"; 282 | const nullArn = 283 | "arn:aws:logs:us-west-2:111122223333:log-group:null:log-stream:null"; 284 | const cloudWatchLogsArn = 285 | "arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab"; 286 | 287 | const buildReplies = [ 288 | { builds: [{ id: buildID, logs: { cloudWatchLogsArn } }] }, 289 | { 290 | builds: [ 291 | { id: buildID, logs: { cloudWatchLogsArn }, endTime: "endTime" }, 292 | ], 293 | }, 294 | { 295 | builds: [ 296 | { id: buildID, logs: { cloudWatchLogsArn }, endTime: "endTime" }, 297 | ], 298 | }, 299 | ]; 300 | const logReplies = [ 301 | undefined, 302 | { events: [{ message: "got one" }] }, 303 | { events: [] }, 304 | ]; 305 | const sdk = help( 306 | () => buildReplies[count++], 307 | () => logReplies[count - 1] 308 | ); 309 | 310 | const test = await waitForBuildEndTime(sdk, { 311 | id: buildID, 312 | logs: { cloudWatchLogsArn: nullArn }, 313 | }); 314 | expect(test).to.equal(buildReplies.pop().builds[0]); 315 | }); 316 | 317 | it("waits after being rate limited and tries again", async function () { 318 | const buildID = "buildID"; 319 | const nullArn = 320 | "arn:aws:logs:us-west-2:111122223333:log-group:null:log-stream:null"; 321 | const cloudWatchLogsArn = 322 | "arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab"; 323 | 324 | const buildReplies = [ 325 | () => { 326 | throw { message: "Rate exceeded" }; 327 | }, 328 | { builds: [{ id: buildID, logs: { cloudWatchLogsArn } }] }, 329 | { 330 | builds: [ 331 | { id: buildID, logs: { cloudWatchLogsArn }, endTime: "endTime" }, 332 | ], 333 | }, 334 | ]; 335 | 336 | const sdk = help( 337 | () => { 338 | //similar to the ret function in the helper, allows me to throw an error in a function or return a more standard reply 339 | let reply = buildReplies.shift(); 340 | 341 | if (typeof reply === "function") return reply(); 342 | return reply; 343 | }, 344 | () => { 345 | if (!buildReplies.length) { 346 | return { events: [] }; 347 | } 348 | 349 | return { events: [{ message: "got one" }] }; 350 | } 351 | ); 352 | 353 | const test = await waitForBuildEndTime( 354 | { ...sdk, wait: 1, backOff: 1 }, 355 | { 356 | id: buildID, 357 | logs: { cloudWatchLogsArn: nullArn }, 358 | } 359 | ); 360 | 361 | expect(test.id).to.equal(buildID); 362 | }); 363 | 364 | it("dies after getting an error from the aws sdk that isn't rate limiting", async function () { 365 | const buildID = "buildID"; 366 | const nullArn = 367 | "arn:aws:logs:us-west-2:111122223333:log-group:null:log-stream:null"; 368 | const cloudWatchLogsArn = 369 | "arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab"; 370 | 371 | const buildReplies = [ 372 | () => { 373 | throw { message: "Some AWS error" }; 374 | }, 375 | { builds: [{ id: buildID, logs: { cloudWatchLogsArn } }] }, 376 | { 377 | builds: [ 378 | { id: buildID, logs: { cloudWatchLogsArn }, endTime: "endTime" }, 379 | ], 380 | }, 381 | ]; 382 | 383 | const sdk = help( 384 | () => { 385 | //similar to the ret function in the helper 386 | //allows me to throw an error in a function or return a more standard reply 387 | let reply = buildReplies.shift(); 388 | 389 | if (typeof reply === "function") return reply(); 390 | return reply; 391 | }, 392 | () => { 393 | if (!buildReplies.length) { 394 | return { events: [] }; 395 | } 396 | 397 | return { events: [{ message: "got one" }] }; 398 | } 399 | ); 400 | 401 | //run the thing and it should fail 402 | let didFail = false; 403 | 404 | try { 405 | await waitForBuildEndTime( 406 | { ...sdk, wait: 1, backOff: 1 }, 407 | { 408 | id: buildID, 409 | logs: { cloudWatchLogsArn: nullArn }, 410 | } 411 | ); 412 | } catch (err) { 413 | didFail = true; 414 | expect(err.message).to.equal("Some AWS error"); 415 | } 416 | 417 | expect(didFail).to.equal(true); 418 | }); 419 | }); 420 | 421 | function help(builds, logs) { 422 | const codeBuild = { 423 | batchGetBuilds() { 424 | return { 425 | async promise() { 426 | return ret(builds); 427 | }, 428 | }; 429 | }, 430 | }; 431 | 432 | const cloudWatchLogs = { 433 | getLogEvents() { 434 | return { 435 | async promise() { 436 | return ret(logs); 437 | }, 438 | }; 439 | }, 440 | }; 441 | 442 | return { codeBuild, cloudWatchLogs, wait: 10 }; 443 | 444 | function ret(thing) { 445 | if (typeof thing === "function") return thing(); 446 | return thing; 447 | } 448 | } 449 | --------------------------------------------------------------------------------