Skip to content

Commit fe197b4

Browse files
authored
Merge branch 'master' into fix-fargate-secret-key-env
2 parents fc13b43 + c498f60 commit fe197b4

37 files changed

+1152
-7
lines changed

packages/@aws-cdk/aws-codebuild/README.md

+62
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,68 @@ any attempt to save more than one will result in an error.
269269
You can use the [`list-source-credentials` AWS CLI operation](https://docs.aws.amazon.com/cli/latest/reference/codebuild/list-source-credentials.html)
270270
to inspect what credentials are stored in your account.
271271

272+
## Test reports
273+
274+
You can specify a test report in your buildspec:
275+
276+
```typescript
277+
const project = new codebuild.Project(this, 'Project', {
278+
buildSpec: codebuild.BuildSpec.fromObject({
279+
// ...
280+
reports: {
281+
myReport: {
282+
files: '**/*',
283+
'base-directory': 'build/test-results',
284+
},
285+
},
286+
}),
287+
});
288+
```
289+
290+
This will create a new test report group,
291+
with the name `<ProjectName>-myReport`.
292+
293+
The project's role in the CDK will always be granted permissions to create and use report groups
294+
with names starting with the project's name;
295+
if you'd rather not have those permissions added,
296+
you can opt out of it when creating the project:
297+
298+
```typescript
299+
const project = new codebuild.Project(this, 'Project', {
300+
// ...
301+
grantReportGroupPermissions: false,
302+
});
303+
```
304+
305+
Alternatively, you can specify an ARN of an existing resource group,
306+
instead of a simple name, in your buildspec:
307+
308+
```typescript
309+
// create a new ReportGroup
310+
const reportGroup = new codebuild.ReportGroup(this, 'ReportGroup');
311+
312+
const project = new codebuild.Project(this, 'Project', {
313+
buildSpec: codebuild.BuildSpec.fromObject({
314+
// ...
315+
reports: {
316+
[reportGroup.reportGroupArn]: {
317+
files: '**/*',
318+
'base-directory': 'build/test-results',
319+
},
320+
},
321+
}),
322+
});
323+
```
324+
325+
If you do that, you need to grant the project's role permissions to write reports to that report group:
326+
327+
```typescript
328+
reportGroup.grantWrite(project);
329+
```
330+
331+
For more information on the test reports feature,
332+
see the [AWS CodeBuild documentation](https://docs.aws.amazon.com/codebuild/latest/userguide/test-reporting.html).
333+
272334
## Events
273335

274336
CodeBuild projects can be used either as a source for events or be triggered

packages/@aws-cdk/aws-codebuild/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './events';
22
export * from './pipeline-project';
33
export * from './project';
4+
export * from './report-group';
45
export * from './source';
56
export * from './source-credentials';
67
export * from './artifacts';

packages/@aws-cdk/aws-codebuild/lib/project.ts

+30
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CodePipelineArtifacts } from './codepipeline-artifacts';
1616
import { IFileSystemLocation } from './file-location';
1717
import { NoArtifacts } from './no-artifacts';
1818
import { NoSource } from './no-source';
19+
import { renderReportGroupArn } from './report-group-utils';
1920
import { ISource } from './source';
2021
import { CODEPIPELINE_SOURCE_ARTIFACTS_TYPE, NO_SOURCE_TYPE } from './source-types';
2122

@@ -519,6 +520,21 @@ export interface CommonProjectProps {
519520
* @default - no file system locations
520521
*/
521522
readonly fileSystemLocations?: IFileSystemLocation[];
523+
524+
/**
525+
* Add permissions to this project's role to create and use test report groups with name starting with the name of this project.
526+
*
527+
* That is the standard report group that gets created when a simple name
528+
* (in contrast to an ARN)
529+
* is used in the 'reports' section of the buildspec of this project.
530+
* This is usually harmless, but you can turn these off if you don't plan on using test
531+
* reports in this project.
532+
*
533+
* @default true
534+
*
535+
* @see https://docs.aws.amazon.com/codebuild/latest/userguide/test-report-group-naming.html
536+
*/
537+
readonly grantReportGroupPermissions?: boolean;
522538
}
523539

524540
export interface ProjectProps extends CommonProjectProps {
@@ -762,6 +778,20 @@ export class Project extends ProjectBase {
762778
this.projectName = this.getResourceNameAttribute(resource.ref);
763779

764780
this.addToRolePolicy(this.createLoggingPermission());
781+
// add permissions to create and use test report groups
782+
// with names starting with the project's name,
783+
// unless the customer explicitly opts out of it
784+
if (props.grantReportGroupPermissions !== false) {
785+
this.addToRolePolicy(new iam.PolicyStatement({
786+
actions: [
787+
'codebuild:CreateReportGroup',
788+
'codebuild:CreateReport',
789+
'codebuild:UpdateReport',
790+
'codebuild:BatchPutTestCases',
791+
],
792+
resources: [renderReportGroupArn(this, `${this.projectName}-*`)],
793+
}));
794+
}
765795

766796
if (props.encryptionKey) {
767797
this.encryptionKey = props.encryptionKey;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as cdk from '@aws-cdk/core';
2+
3+
// this file contains a bunch of functions shared
4+
// between Project and ResourceGroup,
5+
// which we don't want to make part of the public API of this module
6+
7+
export function renderReportGroupArn(scope: cdk.Construct, reportGroupName: string): string {
8+
return cdk.Stack.of(scope).formatArn(reportGroupArnComponents(reportGroupName));
9+
}
10+
11+
export function reportGroupArnComponents(reportGroupName: string): cdk.ArnComponents {
12+
return {
13+
service: 'codebuild',
14+
resource: 'report-group',
15+
resourceName: reportGroupName,
16+
};
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import * as s3 from '@aws-cdk/aws-s3';
3+
import * as cdk from '@aws-cdk/core';
4+
import { CfnReportGroup } from './codebuild.generated';
5+
import { renderReportGroupArn, reportGroupArnComponents } from './report-group-utils';
6+
7+
/**
8+
* The interface representing the ReportGroup resource -
9+
* either an existing one, imported using the
10+
* {@link ReportGroup.fromReportGroupName} method,
11+
* or a new one, created with the {@link ReportGroup} class.
12+
*/
13+
export interface IReportGroup extends cdk.IResource {
14+
/**
15+
* The ARN of the ReportGroup.
16+
*
17+
* @attribute
18+
*/
19+
readonly reportGroupArn: string;
20+
21+
/**
22+
* The name of the ReportGroup.
23+
*
24+
* @attribute
25+
*/
26+
readonly reportGroupName: string;
27+
28+
/**
29+
* Grants the given entity permissions to write
30+
* (that is, upload reports to)
31+
* this report group.
32+
*/
33+
grantWrite(identity: iam.IGrantable): iam.Grant;
34+
}
35+
36+
abstract class ReportGroupBase extends cdk.Resource implements IReportGroup {
37+
public abstract readonly reportGroupArn: string;
38+
public abstract readonly reportGroupName: string;
39+
protected abstract readonly exportBucket?: s3.IBucket;
40+
41+
public grantWrite(identity: iam.IGrantable): iam.Grant {
42+
const ret = iam.Grant.addToPrincipal({
43+
grantee: identity,
44+
actions: [
45+
'codebuild:CreateReport',
46+
'codebuild:UpdateReport',
47+
'codebuild:BatchPutTestCases',
48+
],
49+
resourceArns: [this.reportGroupArn],
50+
});
51+
52+
if (this.exportBucket) {
53+
this.exportBucket.grantWrite(identity);
54+
}
55+
56+
return ret;
57+
}
58+
}
59+
60+
/**
61+
* Construction properties for {@link ReportGroup}.
62+
*/
63+
export interface ReportGroupProps {
64+
/**
65+
* The physical name of the report group.
66+
*
67+
* @default - CloudFormation-generated name
68+
*/
69+
readonly reportGroupName?: string;
70+
71+
/**
72+
* An optional S3 bucket to export the reports to.
73+
*
74+
* @default - the reports will not be exported
75+
*/
76+
readonly exportBucket?: s3.IBucket;
77+
78+
/**
79+
* Whether to output the report files into the export bucket as-is,
80+
* or create a ZIP from them before doing the export.
81+
* Ignored if {@link exportBucket} has not been provided.
82+
*
83+
* @default - false (the files will not be ZIPped)
84+
*/
85+
readonly zipExport?: boolean;
86+
87+
/**
88+
* What to do when this resource is deleted from a stack.
89+
* As CodeBuild does not allow deleting a ResourceGroup that has reports inside of it,
90+
* this is set to retain the resource by default.
91+
*
92+
* @default RemovalPolicy.RETAIN
93+
*/
94+
readonly removalPolicy?: cdk.RemovalPolicy;
95+
}
96+
97+
/**
98+
* The ReportGroup resource class.
99+
*/
100+
export class ReportGroup extends ReportGroupBase {
101+
102+
/**
103+
* Reference an existing ReportGroup,
104+
* defined outside of the CDK code,
105+
* by name.
106+
*/
107+
public static fromReportGroupName(scope: cdk.Construct, id: string, reportGroupName: string): IReportGroup {
108+
class Import extends ReportGroupBase {
109+
public readonly reportGroupName = reportGroupName;
110+
public readonly reportGroupArn = renderReportGroupArn(scope, reportGroupName);
111+
protected readonly exportBucket = undefined;
112+
}
113+
114+
return new Import(scope, id);
115+
}
116+
117+
public readonly reportGroupArn: string;
118+
public readonly reportGroupName: string;
119+
protected readonly exportBucket?: s3.IBucket;
120+
121+
constructor(scope: cdk.Construct, id: string, props: ReportGroupProps = {}) {
122+
super(scope, id, {
123+
physicalName: props.reportGroupName,
124+
});
125+
126+
const resource = new CfnReportGroup(this, 'Resource', {
127+
type: 'TEST',
128+
exportConfig: {
129+
exportConfigType: props.exportBucket ? 'S3' : 'NO_EXPORT',
130+
s3Destination: props.exportBucket
131+
? {
132+
bucket: props.exportBucket.bucketName,
133+
encryptionDisabled: props.exportBucket.encryptionKey ? false : undefined,
134+
encryptionKey: props.exportBucket.encryptionKey?.keyArn,
135+
packaging: props.zipExport ? 'ZIP' : undefined,
136+
}
137+
: undefined,
138+
},
139+
});
140+
resource.applyRemovalPolicy(props.removalPolicy, {
141+
default: cdk.RemovalPolicy.RETAIN,
142+
});
143+
this.reportGroupArn = this.getResourceArnAttribute(resource.attrArn,
144+
reportGroupArnComponents(this.physicalName));
145+
this.reportGroupName = this.getResourceNameAttribute(
146+
// there is no separate name attribute,
147+
// so use Fn::Select + Fn::Split to make one
148+
cdk.Fn.select(1, cdk.Fn.split('/', resource.ref)),
149+
);
150+
this.exportBucket = props.exportBucket;
151+
}
152+
}

packages/@aws-cdk/aws-codebuild/test/integ.caching.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ new codebuild.Project(stack, 'MyProject', {
2222
paths: ['/root/.cache/pip/**/*'],
2323
},
2424
}),
25+
grantReportGroupPermissions: false,
2526
});
2627

2728
app.synth();

packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json

+34-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,39 @@
7878
]
7979
}
8080
]
81+
},
82+
{
83+
"Action": [
84+
"codebuild:CreateReportGroup",
85+
"codebuild:CreateReport",
86+
"codebuild:UpdateReport",
87+
"codebuild:BatchPutTestCases"
88+
],
89+
"Effect": "Allow",
90+
"Resource": {
91+
"Fn::Join": [
92+
"",
93+
[
94+
"arn:",
95+
{
96+
"Ref": "AWS::Partition"
97+
},
98+
":codebuild:",
99+
{
100+
"Ref": "AWS::Region"
101+
},
102+
":",
103+
{
104+
"Ref": "AWS::AccountId"
105+
},
106+
":report-group/",
107+
{
108+
"Ref": "MyProject39F7B0AE"
109+
},
110+
"-*"
111+
]
112+
]
113+
}
81114
}
82115
],
83116
"Version": "2012-10-17"
@@ -115,4 +148,4 @@
115148
}
116149
}
117150
}
118-
}
151+
}

packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class TestStack extends cdk.Stack {
1515
},
1616
},
1717
}),
18+
grantReportGroupPermissions: false,
1819
/// !show
1920
environment: {
2021
buildImage: codebuild.LinuxBuildImage.fromAsset(this, 'MyImage', {

packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class TestStack extends cdk.Stack {
1818
},
1919
},
2020
}),
21+
grantReportGroupPermissions: false,
2122
/// !show
2223
environment: {
2324
buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('my-registry/my-repo', {

packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class TestStack extends cdk.Stack {
1717
},
1818
},
1919
}),
20+
grantReportGroupPermissions: false,
2021
/// !show
2122
environment: {
2223
buildImage: codebuild.LinuxBuildImage.fromEcrRepository(ecrRepository, 'v1.0'),

packages/@aws-cdk/aws-codebuild/test/integ.github.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class TestStack extends cdk.Stack {
1212
});
1313
new codebuild.Project(this, 'MyProject', {
1414
source,
15+
grantReportGroupPermissions: false,
1516
});
1617
}
1718
}

packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ new codebuild.Project(stack, 'MyProject', {
1919
environment: {
2020
computeType: codebuild.ComputeType.LARGE,
2121
},
22+
grantReportGroupPermissions: false,
2223
});
2324

2425
app.synth();

0 commit comments

Comments
 (0)