Skip to content

Commit

Permalink
feat(codebuild): allow setting the Project's logging configuration (#…
Browse files Browse the repository at this point in the history
…11444)

Fixes: #3856

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
wulfmann committed Nov 19, 2020
1 parent f0de91f commit 6a4b22d
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 0 deletions.
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,38 @@ new codebuild.Project(this, 'Project', {
})
```

## Logs

CodeBuild lets you specify an S3 Bucket, CloudWatch Log Group or both to receive logs from your projects.

By default, logs will go to cloudwatch.

### CloudWatch Logs Example

```typescript
new codebuild.Project(this, 'Project', {
logging: {
cloudWatch: {
logGroup: new cloudwatch.LogGroup(this, `MyLogGroup`),
}
},
...
})
```

### S3 Logs Example

```typescript
new codebuild.Project(this, 'Project', {
logging: {
s3: {
bucket: new s3.Bucket(this, `LogBucket`)
}
},
...
})
```

## Credentials

CodeBuild allows you to store credentials used when communicating with various sources,
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codebuild/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './events';
export * from './pipeline-project';
export * from './project';
export * from './project-logs';
export * from './report-group';
export * from './source';
export * from './source-credentials';
Expand Down
78 changes: 78 additions & 0 deletions packages/@aws-cdk/aws-codebuild/lib/project-logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as logs from '@aws-cdk/aws-logs';
import * as s3 from '@aws-cdk/aws-s3';

/**
* Information about logs built to an S3 bucket for a build project.
*/
export interface S3LoggingOptions {
/**
* Encrypt the S3 build log output
*
* @default true
*/
readonly encrypted?: boolean;

/**
* The S3 Bucket to send logs to
*/
readonly bucket: s3.IBucket;

/**
* The path prefix for S3 logs
*
* @default - no prefix
*/
readonly prefix?: string;

/**
* The current status of the logs in Amazon CloudWatch Logs for a build project
*
* @default true
*/
readonly enabled?: boolean;
}

/**
* Information about logs built to a CloudWatch Log Group for a build project.
*/
export interface CloudWatchLoggingOptions {
/**
* The Log Group to send logs to
*
* @default - no log group specified
*/
readonly logGroup?: logs.ILogGroup;

/**
* The prefix of the stream name of the Amazon CloudWatch Logs
*
* @default - no prefix
*/
readonly prefix?: string;

/**
* The current status of the logs in Amazon CloudWatch Logs for a build project
*
* @default true
*/
readonly enabled?: boolean;
}

/**
* Information about logs for the build project. A project can create logs in Amazon CloudWatch Logs, an S3 bucket, or both.
*/
export interface LoggingOptions {
/**
* Information about logs built to an S3 bucket for a build project.
*
* @default - disabled
*/
readonly s3?: S3LoggingOptions;

/**
* Information about Amazon CloudWatch Logs for a build project.
*
* @default - enabled
*/
readonly cloudWatch?: CloudWatchLoggingOptions;
}
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IFileSystemLocation } from './file-location';
import { NoArtifacts } from './no-artifacts';
import { NoSource } from './no-source';
import { runScriptLinuxBuildSpec, S3_BUCKET_ENV, S3_KEY_ENV } from './private/run-script-linux-build-spec';
import { LoggingOptions } from './project-logs';
import { renderReportGroupArn } from './report-group-utils';
import { ISource } from './source';
import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-types';
Expand Down Expand Up @@ -538,6 +539,13 @@ export interface CommonProjectProps {
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/test-report-group-naming.html
*/
readonly grantReportGroupPermissions?: boolean;

/**
* Information about logs for the build project. A project can create logs in Amazon CloudWatch Logs, an S3 bucket, or both.
*
* @default - no log configuration is set
*/
readonly logging?: LoggingOptions;
}

export interface ProjectProps extends CommonProjectProps {
Expand Down Expand Up @@ -771,6 +779,7 @@ export class Project extends ProjectBase {
triggers: sourceConfig.buildTriggers,
sourceVersion: sourceConfig.sourceVersion,
vpcConfig: this.configureVpc(props),
logsConfig: this.renderLoggingConfiguration(props.logging),
});

this.addVpcRequiredPermissions(props, resource);
Expand Down Expand Up @@ -1036,6 +1045,44 @@ export class Project extends ProjectBase {
};
}

private renderLoggingConfiguration(props: LoggingOptions | undefined): CfnProject.LogsConfigProperty | undefined {
if (props === undefined) {
return undefined;
};

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

if (props.s3) {
const s3Logs = props.s3;
s3Config = {
status: (s3Logs.enabled ?? true) ? 'ENABLED' : 'DISABLED',
location: `${s3Logs.bucket.bucketName}/${s3Logs.prefix}`,
encryptionDisabled: s3Logs.encrypted,
};
}

if (props.cloudWatch) {
const cloudWatchLogs = props.cloudWatch;
const status = (cloudWatchLogs.enabled ?? true) ? 'ENABLED' : 'DISABLED';

if (status === 'ENABLED' && !(cloudWatchLogs.logGroup)) {
throw new Error('Specifying a LogGroup is required if CloudWatch logging for CodeBuild is enabled');
}

cloudwatchConfig = {
status,
groupName: cloudWatchLogs.logGroup?.logGroupName,
streamName: cloudWatchLogs.prefix,
};
}

return {
s3Logs: s3Config,
cloudWatchLogs: cloudwatchConfig,
};
}

private addVpcRequiredPermissions(props: ProjectProps, project: CfnProject): void {
if (!props.vpc || !this.role) {
return;
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-codebuild/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"@aws-cdk/aws-events": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-kms": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-secretsmanager": "0.0.0",
Expand All @@ -114,6 +115,7 @@
"@aws-cdk/aws-events": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-kms": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-secretsmanager": "0.0.0",
Expand Down
137 changes: 137 additions & 0 deletions packages/@aws-cdk/aws-codebuild/test/test.project.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { countResources, expect, haveResource, haveResourceLike, objectLike, not, ResourcePart } from '@aws-cdk/assert';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import * as s3 from '@aws-cdk/aws-s3';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
import * as cdk from '@aws-cdk/core';
Expand Down Expand Up @@ -600,5 +601,141 @@ export = {

test.done();
},

'logs config - cloudWatch'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const logGroup = logs.LogGroup.fromLogGroupName(stack, 'LogGroup', 'MyLogGroupName');

// WHEN
new codebuild.Project(stack, 'Project', {
source: codebuild.Source.s3({
bucket: new s3.Bucket(stack, 'Bucket'),
path: 'path',
}),
logging: {
cloudWatch: {
logGroup,
prefix: '/my-logs',
},
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', {
LogsConfig: objectLike({
CloudWatchLogs: {
GroupName: 'MyLogGroupName',
Status: 'ENABLED',
StreamName: '/my-logs',
},
}),
}));

test.done();
},

'logs config - cloudWatch disabled'(test: Test) {
// GIVEN
const stack = new cdk.Stack();

// WHEN
new codebuild.Project(stack, 'Project', {
source: codebuild.Source.s3({
bucket: new s3.Bucket(stack, 'Bucket'),
path: 'path',
}),
logging: {
cloudWatch: {
enabled: false,
},
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', {
LogsConfig: objectLike({
CloudWatchLogs: {
Status: 'DISABLED',
},
}),
}));

test.done();
},

'logs config - s3'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket', 'MyBucketName');

// WHEN
new codebuild.Project(stack, 'Project', {
source: codebuild.Source.s3({
bucket: new s3.Bucket(stack, 'Bucket'),
path: 'path',
}),
logging: {
s3: {
bucket,
prefix: 'my-logs',
},
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', {
LogsConfig: objectLike({
S3Logs: {
Location: 'MyBucketName/my-logs',
Status: 'ENABLED',
},
}),
}));

test.done();
},

'logs config - cloudWatch and s3'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = s3.Bucket.fromBucketName(stack, 'LogBucket2', 'MyBucketName');
const logGroup = logs.LogGroup.fromLogGroupName(stack, 'LogGroup2', 'MyLogGroupName');

// WHEN
new codebuild.Project(stack, 'Project', {
source: codebuild.Source.s3({
bucket: new s3.Bucket(stack, 'Bucket'),
path: 'path',
}),
logging: {
cloudWatch: {
logGroup,
prefix: '/my-logs',
},
s3: {
bucket,
prefix: 'my-logs',
},
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', {
LogsConfig: objectLike({
CloudWatchLogs: {
GroupName: 'MyLogGroupName',
Status: 'ENABLED',
StreamName: '/my-logs',
},
S3Logs: {
Location: 'MyBucketName/my-logs',
Status: 'ENABLED',
},
}),
}));

test.done();
},
},
};

0 comments on commit 6a4b22d

Please sign in to comment.