forked from aws-actions/aws-codebuild-run-build
-
Notifications
You must be signed in to change notification settings - Fork 0
/
code-build.js
195 lines (164 loc) · 5.4 KB
/
code-build.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
const core = require("@actions/core");
const github = require("@actions/github");
const aws = require("aws-sdk");
const assert = require("assert");
module.exports = {
runBuild,
build,
waitForBuildEndTime,
inputs2Parameters,
githubInputs,
buildSdk,
logName
};
function runBuild() {
// get a codeBuild instance from the SDK
const sdk = buildSdk();
// Get input options for startBuild
const params = inputs2Parameters(githubInputs());
return build(sdk, params);
}
async function build(sdk, params) {
// Start the build
const start = await sdk.codeBuild.startBuild(params).promise();
// Wait for the build to "complete"
return waitForBuildEndTime(sdk, start.build);
}
async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
const {
codeBuild,
cloudWatchLogs,
wait = 1000 * 30,
backOff = 1000 * 15
} = sdk;
// Get the CloudWatchLog info
const startFromHead = true;
const { cloudWatchLogsArn } = logs;
const { logGroupName, logStreamName } = logName(cloudWatchLogsArn);
let errObject = false;
// Check the state
const [batch, cloudWatch = {}] = await Promise.all([
codeBuild.batchGetBuilds({ ids: [id] }).promise(),
// The CloudWatchLog _may_ not be set up, only make the call if we have a logGroupName
logGroupName &&
cloudWatchLogs
.getLogEvents({ logGroupName, logStreamName, startFromHead, nextToken })
.promise()
]).catch(err => {
errObject = err;
/* Returning [] here so that the assignment above
* does not throw `TypeError: undefined is not iterable`.
* The error is handled below,
* since it might be a rate limit.
*/
return [];
});
if (errObject) {
//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
if (errObject.message && errObject.message.search("Rate exceeded") !== -1) {
//We were rate-limited, so add `backOff` seconds to the wait time
let newWait = wait + backOff;
//Sleep before trying again
await new Promise(resolve => setTimeout(resolve, newWait));
// Try again from the same token position
return waitForBuildEndTime(
{ ...sdk, wait: newWait },
{ id, logs },
nextToken
);
} else {
//The error returned from the API wasn't about rate limiting, so throw it as an actual error and fail the job
throw errObject;
}
}
// Pluck off the relevant state
const [current] = batch.builds;
const { nextForwardToken, events = [] } = cloudWatch;
// stdout the CloudWatchLog (everyone likes progress...)
// CloudWatchLogs have line endings.
// I trim and then log each line
// to ensure that the line ending is OS specific.
events.forEach(({ message }) => console.log(message.trimEnd()));
// We did it! We can stop looking!
if (current.endTime && !events.length) return current;
// More to do: Sleep for a few seconds to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, wait));
// Try again
return waitForBuildEndTime(sdk, current, nextForwardToken);
}
function githubInputs() {
const projectName = core.getInput("project-name", { required: true });
const { owner, repo } = github.context.repo;
// The github.context.sha is evaluated on import.
// This makes it hard to test.
// So I use the raw ENV
const sourceVersion = process.env[`GITHUB_SHA`];
const buildspecOverride =
core.getInput("buildspec-override", { required: false }) || undefined;
const envPassthrough = core
.getInput("env-vars-for-codebuild", { required: false })
.split(",")
.map(i => i.trim())
.filter(i => i !== "");
return {
projectName,
owner,
repo,
sourceVersion,
buildspecOverride,
envPassthrough
};
}
function inputs2Parameters(inputs) {
const {
projectName,
owner,
repo,
sourceVersion,
buildspecOverride,
envPassthrough = []
} = inputs;
const sourceTypeOverride = "GITHUB";
const sourceLocationOverride = `https://github.com/${owner}/${repo}.git`;
const environmentVariablesOverride = Object.entries(process.env)
.filter(
([key]) => key.startsWith("GITHUB_") || envPassthrough.includes(key)
)
.map(([name, value]) => ({ name, value, type: "PLAINTEXT" }));
// The idempotencyToken is intentionally not set.
// This way the GitHub events can manage the builds.
return {
projectName,
sourceVersion,
sourceTypeOverride,
sourceLocationOverride,
buildspecOverride,
environmentVariablesOverride
};
}
function buildSdk() {
const codeBuild = new aws.CodeBuild({
customUserAgent: "aws-actions/aws-codebuild-run-build"
});
const cloudWatchLogs = new aws.CloudWatchLogs({
customUserAgent: "aws-actions/aws-codebuild-run-build"
});
assert(
codeBuild.config.credentials && cloudWatchLogs.config.credentials,
"No credentials. Try adding @aws-actions/configure-aws-credentials earlier in your job to set up AWS credentials."
);
return { codeBuild, cloudWatchLogs };
}
function logName(Arn) {
const [logGroupName, logStreamName] = Arn.split(":log-group:")
.pop()
.split(":log-stream:");
if (logGroupName === "null" || logStreamName === "null")
return {
logGroupName: undefined,
logStreamName: undefined
};
return { logGroupName, logStreamName };
}