Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add batch support #59

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ In order to use this tool,
you must first `git checkout` the commit that you want to test.

```
npx @aws-actions/codebuild-run-build -p ProjectName -r remoteName
npx https://github.com/aws-actions/aws-codebuild-run-build.git -p ProjectName -r remoteName

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional change? If so, why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the package has not been published to npm

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understandable. This can be shortened however:

Suggested change
npx https://github.com/aws-actions/aws-codebuild-run-build.git -p ProjectName -r remoteName
npx aws-actions/aws-codebuild-run-build -p ProjectName -r remoteName

```

This will use whatever commit you have checked out
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ inputs:
buildspec-override:
description: 'Buildspec Override'
required: false
batch:
description: 'Run as an AWS CodeBuild batch'
required: false
env-vars-for-codebuild:
description: 'Comma separated list of environment variables to send to CodeBuild'
required: false
Expand Down
116 changes: 115 additions & 1 deletion code-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const assert = require("assert");
module.exports = {
runBuild,
build,
buildBatch,
waitForBuildEndTime,
waitForBatchBuildEndTime,
inputs2Parameters,
githubInputs,
buildSdk,
Expand All @@ -22,8 +24,13 @@ function runBuild() {

// Get input options for startBuild
const params = inputs2Parameters(githubInputs());
const isBatch = core.getInput("batch").toUpper() === "TRUE";

return build(sdk, params);
if (isBatch) {
return buildBatch(sdk, params);
} else {
return build(sdk, params);
}
}

async function build(sdk, params) {
Expand All @@ -34,6 +41,113 @@ async function build(sdk, params) {
return waitForBuildEndTime(sdk, start.build);
}

async function buildBatch(sdk, params) {
// Start the batch
const { buildBatch } = await sdk.codeBuild.startBuildBatch(params).promise();
const { id } = buildBatch;

// Wait for the batch to "complete"
return waitForBatchBuildEndTime(sdk, { id });
}

async function waitForBatchBuildEndTime(sdk, { id, observedBuilds = [] }) {
const { codeBuild, wait = 2000 } = sdk;

/* Batch builds take a long time,
* and there is a fare amount
* of eventual constancy involved.
* The first time I enter,
* I never expect this wait
* to impact performance.
* But for every recursive call,
* this wait makes a simple gate
* to keep from throttling myself.
*/
await new Promise((resolve) => setTimeout(resolve, wait));

const { buildBatches } = await codeBuild
.batchGetBuildBatches({ ids: [id] })
.promise();
const [current] = buildBatches;
const { buildGroups } = current;

/* Immediately after the batch is started,
* the build group will be empty.
* I have to wait for the first build,
* that will process the list/matrix
* that start all the builds
* that do the work.
*/
if (!buildGroups) return waitForBatchBuildEndTime(sdk, { id });

// The build ids I have not yet waited for.
const ids = buildGroups
.map(({ currentBuildSummary }) => currentBuildSummary.arn)
.filter((arn) => !observedBuilds.includes(arn));

/* Don't try and get the status of 0 builds.
* It would be nice to not have an if here,
* but it is nicer to not make the remote call.
*/
if (ids.length) {
// Get the information for the builds to wait for
const { builds } = await codeBuild.batchGetBuilds({ ids }).promise();

for (const build of builds) {
console.log(`=========== START: ${build.id} =============`);
await waitForBuildEndTime(sdk, build)
/* Just because this build failed,
* I still need to stream the other results.
* waitForBuildEndTime is supposed to handle
* all retirable errors.
* It may be better to
* gather up these errors
* and throw when the batch has completed.
*/
.catch((e) => {
console.log(`Error in build ${build.id}: ${e.stack} `);
});
console.log(`============================================`);
}

/* Update the observed builds
* since they have now been observed.
* The `currentBuildSummary` does not have the id.
* It only has the arn,
* so while the id is nice to pass around,
* the arn is what I want to keep here.
* Otherwise each build will need to be streamed twice.
*/
observedBuilds = observedBuilds.concat(builds.map(({ arn }) => arn));
}

/* Just because I have processed
* all the builds in the buildGroup
* this does not mean
* that the batch is complete.
* This is especially true with the first build.
* The first build in the batch generally
* is how all the needed builds are calculated.
* So the first time I'm here,
* I expect to have 1 build.
* After that first build,
* I expect to recurse
* and then have the complete set.
* But why not just recurse until
* the batch is no longer in progress?
*/
if (current.buildBatchStatus === "IN_PROGRESS")
return waitForBatchBuildEndTime(sdk, {
id,
observedBuilds,
});

/* If the batch is not IN_PROGRESS
* this is as good as it gets.
*/
return current;
}

async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
const {
codeBuild,
Expand Down
10 changes: 8 additions & 2 deletions local.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const cb = require("./code-build");
const assert = require("assert");
const yargs = require("yargs");

const { projectName, buildspecOverride, envPassthrough, remote } = yargs
const { projectName, buildspecOverride, envPassthrough, remote, batch } = yargs
.option("project-name", {
alias: "p",
describe: "AWS CodeBuild Project Name",
Expand All @@ -30,6 +30,11 @@ const { projectName, buildspecOverride, envPassthrough, remote } = yargs
describe: "remote name to publish to",
default: "origin",
type: "string",
})
.option("batch", {
describe: "Run as AWS CodeBuild batch build",
type: "boolean",
default: false,
}).argv;

const BRANCH_NAME = uuid();
Expand All @@ -46,7 +51,8 @@ const sdk = cb.buildSdk();

pushBranch(remote, BRANCH_NAME);

cb.build(sdk, params)
// Need to select batch or build mode
(batch ? cb.buildBatch(sdk, params) : cb.build(sdk, params))
.then(() => deleteBranch(remote, BRANCH_NAME))
.catch((err) => {
deleteBranch(remote, BRANCH_NAME);
Expand Down
100 changes: 99 additions & 1 deletion test/code-build-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
githubInputs,
inputs2Parameters,
waitForBuildEndTime,
waitForBatchBuildEndTime,
} = require("../code-build");
const { expect } = require("chai");

Expand Down Expand Up @@ -418,7 +419,97 @@ describe("waitForBuildEndTime", () => {
});
});

function help(builds, logs) {
describe("waitForBatchBuildEndTime", () => {
it("basic usage", async () => {
const buildID = "buildID";
const cloudWatchLogsArn =
"arn:aws:logs:us-west-2:111122223333:log-group:/aws/codebuild/CloudWatchLogGroup:log-stream:1234abcd-12ab-34cd-56ef-1234567890ab";

const buildReplies = [
{
builds: [
{
id: buildID,
logs: { cloudWatchLogsArn },
endTime: "endTime",
arn: buildID,
},
],
},
{
builds: [
{
id: buildID,
logs: { cloudWatchLogsArn },
endTime: "endTime",
arn: buildID,
},
],
},
];
const logReplies = [{ events: [] }];
const batchReplies = [
{
buildBatches: [{}],
},
{
buildBatches: [
{
buildBatchStatus: "IN_PROGRESS",
buildGroups: [
{
currentBuildSummary: {
arn: buildID,
},
},
],
},
],
},
{
buildBatches: [
{
buildBatchStatus: "SUCCESS",
buildGroups: [
{
currentBuildSummary: {
arn: buildID,
},
},
],
},
],
},
];
let countBuild = 0;
let countLog = 0;
let countBatch = 0;
const sdk = help(
() => buildReplies[countBuild++],
() => logReplies[countLog++],
() => batchReplies[countBatch++]
);

const test = await waitForBatchBuildEndTime(sdk, {
id: buildID,
logs: { cloudWatchLogsArn },
});

expect(test).to.equal(batchReplies.pop().buildBatches[0]);

/* These counts extensively tests
* the underlying logic.
* Given the linearity
* of the function,
* I assert that this is adequate.
*/
expect(countBuild).to.equal(2);
expect(countLog).to.equal(1);
expect(countBatch).to.equal(3);
});
});

function help(builds, logs, batches) {
const codeBuild = {
batchGetBuilds() {
return {
Expand All @@ -427,6 +518,13 @@ function help(builds, logs) {
},
};
},
batchGetBuildBatches() {
return {
async promise() {
return ret(batches);
},
};
},
};

const cloudWatchLogs = {
Expand Down
12 changes: 12 additions & 0 deletions wallaby.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

module.exports = function () {
return {
files: ["./code-build.js"],
tests: ["test/code-build-test.js"],
testFramework: "mocha",
env: { type: "node" },
debug: true,
};
};