Skip to content

Commit

Permalink
feat(aws-codebuild): add enableBatchBuilds() to Project (#12531)
Browse files Browse the repository at this point in the history
In order for a CodeBuild to run in batch mode, a batch service role is needed, as described [here in the docs](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build.html).

>Batch builds introduce a new security role in the batch configuration. This new role is required as CodeBuild must be able to call the StartBuild, StopBuild, and RetryBuild actions on your behalf to run builds as part of a batch. Customers should use a new role, and not the same role they use in their build...

At first I thought lets add this by default, but then I realised when `BatchConfiguration` is set to something, in the aws console the default 'start build' button behaviour changes to start a batch build by default instead :/

So now this adds a new `supportBatchBuildType` option, which when `true` adds minimum amount of `BatchConfiguration` needed for batch builds to run.

I also updated the doc blocks for the webhook option and CodePipeline action option, because users of those also need to set this option. It would be nice to auto-enable this if a webhook or CodeBuild action is configured, but that sounds pretty complicated.

I'm not sure why anyone would need to customise this role, given it appears to only be used internally to do those 3 things, so this PR does not make it configurable. My thinking is that this could be added later if needed, but this PR just gets batch builds working.

In the future if people want control of the other `BatchConfiguration` options I was thinking these could be added and would require `supportBatchBuildType` to be `true`.

related: aws-cloudformation/cloudformation-coverage-roadmap#621
  • Loading branch information
tjenkinson authored Jan 26, 2021
1 parent d7e028a commit 0568390
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 19 deletions.
17 changes: 17 additions & 0 deletions packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,20 @@ new codebuild.Project(stack, 'MyProject', {
Here's a CodeBuild project with a simple example that creates a project mounted on AWS EFS:

[Minimal Example](./test/integ.project-file-system-location.ts)

## Batch builds

To enable batch builds you should call `enableBatchBuilds()` on the project instance.

It returns an object containing the batch service role that was created,
or `undefined` if batch builds could not be enabled, for example if the project was imported.

```ts
import * as codebuild from '@aws-cdk/aws-codebuild';

const project = new codebuild.Project(this, 'MyProject', { ... });

if (project.enableBatchBuilds()) {
console.log('Batch builds were enabled');
}
```
58 changes: 54 additions & 4 deletions packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-typ
// eslint-disable-next-line
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* The type returned from {@link IProject#enableBatchBuilds}.
*/
export interface BatchBuildConfig {
/** The IAM batch service Role of this Project. */
readonly role: iam.IRole;
}

export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable {
/**
* The ARN of this Project.
Expand All @@ -44,6 +52,14 @@ export interface IProject extends IResource, iam.IGrantable, ec2.IConnectable {
/** The IAM service Role of this Project. Undefined for imported Projects. */
readonly role?: iam.IRole;

/**
* Enable batch builds.
*
* Returns an object contining the batch service role if batch builds
* could be enabled.
*/
enableBatchBuilds(): BatchBuildConfig | undefined;

addToRolePolicy(policyStatement: iam.PolicyStatement): void;

/**
Expand Down Expand Up @@ -196,6 +212,10 @@ abstract class ProjectBase extends Resource implements IProject {
return this._connections;
}

public enableBatchBuilds(): BatchBuildConfig | undefined {
return undefined;
}

/**
* Add a permission only if there's a policy attached.
* @param statement The permissions statement to add
Expand Down Expand Up @@ -729,6 +749,7 @@ export class Project extends ProjectBase {
private readonly _secondaryArtifacts: CfnProject.ArtifactsProperty[];
private _encryptionKey?: kms.IKey;
private readonly _fileSystemLocations: CfnProject.ProjectFileSystemLocationProperty[];
private _batchServiceRole?: iam.Role;

constructor(scope: Construct, id: string, props: ProjectProps) {
super(scope, id, {
Expand Down Expand Up @@ -813,6 +834,14 @@ export class Project extends ProjectBase {
sourceVersion: sourceConfig.sourceVersion,
vpcConfig: this.configureVpc(props),
logsConfig: this.renderLoggingConfiguration(props.logging),
buildBatchConfig: Lazy.any({
produce: () => {
const config: CfnProject.ProjectBuildBatchConfigProperty | undefined = this._batchServiceRole ? {
serviceRole: this._batchServiceRole.roleArn,
} : undefined;
return config;
},
}),
});

this.addVpcRequiredPermissions(props, resource);
Expand Down Expand Up @@ -853,6 +882,27 @@ export class Project extends ProjectBase {
}
}

public enableBatchBuilds(): BatchBuildConfig | undefined {
if (!this._batchServiceRole) {
this._batchServiceRole = new iam.Role(this, 'BatchServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
});
this._batchServiceRole.addToPrincipalPolicy(new iam.PolicyStatement({
resources: [Lazy.string({
produce: () => this.projectArn,
})],
actions: [
'codebuild:StartBuild',
'codebuild:StopBuild',
'codebuild:RetryBuild',
],
}));
}
return {
role: this._batchServiceRole,
};
}

/**
* Adds a secondary source to the Project.
*
Expand Down Expand Up @@ -1139,8 +1189,8 @@ export class Project extends ProjectBase {
return undefined;
}

let s3Config: CfnProject.S3LogsConfigProperty|undefined = undefined;
let cloudwatchConfig: CfnProject.CloudWatchLogsConfigProperty|undefined = undefined;
let s3Config: CfnProject.S3LogsConfigProperty | undefined = undefined;
let cloudwatchConfig: CfnProject.CloudWatchLogsConfigProperty | undefined = undefined;

if (props.s3) {
const s3Logs = props.s3;
Expand Down Expand Up @@ -1350,10 +1400,10 @@ export interface IBuildImage {
}

/** Optional arguments to {@link IBuildImage.binder} - currently empty. */
export interface BuildImageBindOptions {}
export interface BuildImageBindOptions { }

/** The return type from {@link IBuildImage.binder} - currently empty. */
export interface BuildImageConfig {}
export interface BuildImageConfig { }

// @deprecated(not in tsdoc on purpose): add bind() to IBuildImage
// and get rid of IBindableBuildImage
Expand Down
11 changes: 9 additions & 2 deletions packages/@aws-cdk/aws-codebuild/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ interface ThirdPartyGitSourceProps extends GitSourceProps {
/**
* Trigger a batch build from a webhook instead of a standard one.
*
* Enabling this will enable batch builds on the CodeBuild project.
*
* @default false
*/
readonly webhookTriggersBatchBuild?: boolean;
Expand Down Expand Up @@ -518,7 +520,7 @@ abstract class ThirdPartyGitSource extends GitSource {
this.webhookTriggersBatchBuild = props.webhookTriggersBatchBuild;
}

public bind(_scope: CoreConstruct, _project: IProject): SourceConfig {
public bind(_scope: CoreConstruct, project: IProject): SourceConfig {
const anyFilterGroupsProvided = this.webhookFilters.length > 0;
const webhook = this.webhook === undefined ? (anyFilterGroupsProvided ? true : undefined) : this.webhook;

Expand All @@ -530,7 +532,12 @@ abstract class ThirdPartyGitSource extends GitSource {
throw new Error('`webhookTriggersBatchBuild` cannot be used when `webhook` is `false`');
}

const superConfig = super.bind(_scope, _project);
const superConfig = super.bind(_scope, project);

if (this.webhookTriggersBatchBuild) {
project.enableBatchBuilds();
}

return {
sourceProperty: {
...superConfig.sourceProperty,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
{
"Resources": {
"MyProjectRole9BBE5233": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"MyProjectRoleDefaultPolicyB19B7C29": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow",
"Resource": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":logs:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":log-group:/aws/codebuild/",
{
"Ref": "MyProject39F7B0AE"
}
]
]
},
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":logs:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":log-group:/aws/codebuild/",
{
"Ref": "MyProject39F7B0AE"
},
":*"
]
]
}
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyProjectRoleDefaultPolicyB19B7C29",
"Roles": [
{
"Ref": "MyProjectRole9BBE5233"
}
]
}
},
"MyProjectBatchServiceRole6B35CF0E": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"MyProjectBatchServiceRoleDefaultPolicy7A0E5721": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"codebuild:StartBuild",
"codebuild:StopBuild",
"codebuild:RetryBuild"
],
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"MyProject39F7B0AE",
"Arn"
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyProjectBatchServiceRoleDefaultPolicy7A0E5721",
"Roles": [
{
"Ref": "MyProjectBatchServiceRole6B35CF0E"
}
]
}
},
"MyProject39F7B0AE": {
"Type": "AWS::CodeBuild::Project",
"Properties": {
"Artifacts": {
"Type": "NO_ARTIFACTS"
},
"Environment": {
"ComputeType": "BUILD_GENERAL1_SMALL",
"Image": "aws/codebuild/standard:1.0",
"ImagePullCredentialsType": "CODEBUILD",
"PrivilegedMode": false,
"Type": "LINUX_CONTAINER"
},
"ServiceRole": {
"Fn::GetAtt": [
"MyProjectRole9BBE5233",
"Arn"
]
},
"Source": {
"Location": "https://github.com/aws/aws-cdk.git",
"ReportBuildStatus": false,
"Type": "GITHUB"
},
"BuildBatchConfig": {
"ServiceRole": {
"Fn::GetAtt": [
"MyProjectBatchServiceRole6B35CF0E",
"Arn"
]
}
},
"EncryptionKey": "alias/aws/s3",
"Triggers": {
"BuildType": "BUILD_BATCH",
"FilterGroups": [
[
{
"Pattern": "PUSH",
"Type": "EVENT"
}
]
],
"Webhook": true
}
}
}
}
}
29 changes: 29 additions & 0 deletions packages/@aws-cdk/aws-codebuild/test/integ.github-webhook-batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as cdk from '@aws-cdk/core';
import * as codebuild from '../lib';

class TestStack extends cdk.Stack {
constructor(scope: cdk.App, id: string) {
super(scope, id);

const source = codebuild.Source.gitHub({
owner: 'aws',
repo: 'aws-cdk',
reportBuildStatus: false,
webhook: true,
webhookTriggersBatchBuild: true,
webhookFilters: [
codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH),
],
});
new codebuild.Project(this, 'MyProject', {
source,
grantReportGroupPermissions: false,
});
}
}

const app = new cdk.App();

new TestStack(app, 'test-codebuild-github-webhook-batch');

app.synth();
Loading

0 comments on commit 0568390

Please sign in to comment.