Skip to content

Commit

Permalink
feat(aws-codebuild): add support for additional sources and artifact …
Browse files Browse the repository at this point in the history
…in Projects. (#1110)

This also adds support for multiple input and output artifacts in the CodeBuild
CodePipeline Actions.

BREAKING CHANGE: this changes the way CodeBuild Sources are constructed
(we moved away from multiple parameters in the constructor,
in favor of the more idiomatic property interface).
  • Loading branch information
skinny85 authored and rix0rrr committed Nov 16, 2018
1 parent 18166ce commit d911b08
Show file tree
Hide file tree
Showing 14 changed files with 1,645 additions and 113 deletions.
140 changes: 138 additions & 2 deletions packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import codecommit = require('@aws-cdk/aws-codecommit');

const repo = new codecommit.Repository(this, 'MyRepo', { repositoryName: 'foo' });
new codebuild.Project(this, 'MyFirstCodeCommitProject', {
source: new codebuild.CodeCommitSource(repo)
source: new codebuild.CodeCommitSource({
repository: repo,
}),
});
```

Expand All @@ -28,7 +30,10 @@ import s3 = require('@aws-cdk/aws-s3');

const bucket = new s3.Bucket(this, 'MyBucket');
new codebuild.Project(this, 'MyProject', {
source: new codebuild.S3BucketSource(bucket, 'path/to/source.zip')
source: new codebuild.S3BucketSource({
bucket: bucket,
path: 'path/to/file.zip',
}),
});
```

Expand Down Expand Up @@ -119,3 +124,134 @@ To define CloudWatch event rules for build projects, use one of the `onXxx` meth
const rule = project.onStateChange('BuildStateChange');
rule.addTarget(lambdaFunction);
```
### Secondary sources and artifacts
CodeBuild Projects can get their sources from multiple places,
and produce multiple outputs. For example:
```ts
const project = new codebuild.Project(this, 'MyProject', {
secondarySources: [
new codebuild.CodeCommitSource({
identifier: 'source2',
repository: repo,
}),
],
secondaryArtifacts: [
new codebuild.S3BucketBuildArtifacts({
identifier: 'artifact2',
bucket: bucket,
path: 'some/path',
name: 'file.zip',
}),
],
// ...
});
```
Note that the `identifier` property is required for both secondary sources and artifacts.
The contents of the secondary source will be available to the build under the directory
specified by the `CODEBUILD_SRC_DIR_<identifier>` environment variable
(so, `CODEBUILD_SRC_DIR_source2` in the above case).
The secondary artifacts have their own section in the buildspec,
under the regular `artifacts` one.
Each secondary artifact has its own section,
beginning with their identifier.
So, a buildspec for the above Project could look something like this:
```ts
const project = new codebuild.Project(this, 'MyProject', {
// secondary sources and artifacts as above...
buildSpec: {
version: '0.2',
phases: {
build: {
commands: [
'cd $CODEBUILD_SRC_DIR_source2',
'touch output2.txt',
],
},
},
artifacts: {
'secondary-artifacts': {
'artifact2': {
'base-directory': '$CODEBUILD_SRC_DIR_source2',
'files': [
'output2.txt',
],
},
},
},
},
});
```
#### Multiple inputs and outputs in CodePipeline
When you want to have multiple inputs and/or outputs for a Project used in a Pipeline,
instead of using the `secondarySources` and `secondaryArtifacts` properties,
you need to use the `additionalInputArtifacts` and `additionalOutputArtifactNames`
properties of the CodeBuild CodePipeline Actions.
Example:
```ts
const sourceStage = pipeline.addStage('Source');
const sourceAction1 = repository1.addToPipeline(sourceStage, 'Source1');
const sourceAction2 = repository2.addToPipeline(sourceStage, 'Source2', {
outputArtifactName: 'source2',
});

const buildStage = pipeline.addStage('Build');
const buildAction = project.addBuildToPipeline(buildStage, 'Build', {
inputArtifact: sourceAction1.outputArtifact,
outputArtifactName: 'artifact1', // for better buildspec readability - see below
additionalInputArtifacts: [
sourceAction2.outputArtifact, // this is where 'source2' comes from
],
additionalOutputArtifactNames: [
'artifact2',
],
});
```
**Note**: when a CodeBuild Action in a Pipeline has more than one output,
it will only use the `secondary-artifacts` field of the buildspec,
never the primary output specification directly under `artifacts`.
Because of that, it pays to name even your primary output artifact on the Pipeline,
like we did above, so that you know what name to use in the buildspec.
Example buildspec for the above project:
```ts
const project = new codebuild.PipelineProject(this, 'MyProject', {
buildSpec: {
version: '0.2',
phases: {
build: {
commands: [
// By default, you're in a directory with the contents of the repository from sourceAction1.
// Use the CODEBUILD_SRC_DIR_source2 environment variable
// to get a path to the directory with the contents of the second input repository.
],
},
},
artifacts: {
'secondary-artifacts': {
'artifact1': {
// primary Action output artifact,
// available as buildAction.outputArtifact
},
'artifact2': {
// additional output artifact,
// available as buildAction.additionalOutputArtifact('artifact2')
},
},
},
},
// ...
});
```
104 changes: 73 additions & 31 deletions packages/@aws-cdk/aws-codebuild/lib/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,80 @@ import s3 = require('@aws-cdk/aws-s3');
import { cloudformation } from './codebuild.generated';
import { Project } from './project';

/**
* Properties common to all Artifacts classes.
*/
export interface BuildArtifactsProps {
/**
* The artifact identifier.
* This property is required on secondary artifacts.
*/
identifier?: string;
}

/**
* Artifacts definition for a CodeBuild Project.
*/
export abstract class BuildArtifacts {
public abstract toArtifactsJSON(): cloudformation.ProjectResource.ArtifactsProperty;
public bind(_project: Project) {
public readonly identifier?: string;
protected abstract readonly type: string;

constructor(props: BuildArtifactsProps) {
this.identifier = props.identifier;
}

public _bind(_project: Project) {
return;
}

public toArtifactsJSON(): cloudformation.ProjectResource.ArtifactsProperty {
const artifactsProp = this.toArtifactsProperty();
return {
artifactIdentifier: this.identifier,
type: this.type,
...artifactsProp,
};
}

protected toArtifactsProperty(): any {
return {
};
}
}

/**
* A `NO_ARTIFACTS` CodeBuild Project Artifact definition.
* This is the default artifact type,
* if none was specified when creating the Project
* (and the source was not specified to be CodePipeline).
* *Note*: the `NO_ARTIFACTS` type cannot be used as a secondary artifact,
* and because of that, you're not allowed to specify an identifier for it.
*/
export class NoBuildArtifacts extends BuildArtifacts {
public toArtifactsJSON(): cloudformation.ProjectResource.ArtifactsProperty {
return { type: 'NO_ARTIFACTS' };
protected readonly type = 'NO_ARTIFACTS';

constructor() {
super({});
}
}

/**
* CodePipeline Artifact definition for a CodeBuild Project.
* *Note*: this type cannot be used as a secondary artifact,
* and because of that, you're not allowed to specify an identifier for it.
*/
export class CodePipelineBuildArtifacts extends BuildArtifacts {
public toArtifactsJSON(): cloudformation.ProjectResource.ArtifactsProperty {
return { type: 'CODEPIPELINE' };
protected readonly type = 'CODEPIPELINE';

constructor() {
super({});
}
}

export interface S3BucketBuildArtifactsProps {
/**
* Construction properties for {@link S3BucketBuildArtifacts}.
*/
export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps {
/**
* The name of the output bucket.
*/
Expand All @@ -37,8 +91,8 @@ export interface S3BucketBuildArtifactsProps {
/**
* The name of the build output ZIP file or folder inside the bucket.
*
* The full S3 object key will be "<path>/build-ID/<name>" or
* "<path>/<artifactsName>" depending on whether `includeBuildId` is set to true.
* The full S3 object key will be "<path>/<build-id>/<name>" or
* "<path>/<name>" depending on whether `includeBuildID` is set to true.
*/
name: string;

Expand All @@ -59,39 +113,27 @@ export interface S3BucketBuildArtifactsProps {
packageZip?: boolean;
}

/**
* S3 Artifact definition for a CodeBuild Project.
*/
export class S3BucketBuildArtifacts extends BuildArtifacts {
protected readonly type = 'S3';

constructor(private readonly props: S3BucketBuildArtifactsProps) {
super();
super(props);
}

public bind(project: Project) {
public _bind(project: Project) {
this.props.bucket.grantReadWrite(project.role);
}

public toArtifactsJSON(): cloudformation.ProjectResource.ArtifactsProperty {
protected toArtifactsProperty(): any {
return {
type: 'S3',
location: this.props.bucket.bucketName,
path: this.props.path,
namespaceType: this.parseNamespaceType(this.props.includeBuildID),
namespaceType: this.props.includeBuildID === false ? 'NONE' : 'BUILD_ID',
name: this.props.name,
packaging: this.parsePackaging(this.props.packageZip),
packaging: this.props.packageZip === false ? 'NONE' : 'ZIP',
};
}

private parseNamespaceType(includeBuildID?: boolean) {
if (includeBuildID != null) {
return includeBuildID ? 'BUILD_ID' : 'NONE';
} else {
return 'BUILD_ID';
}
}

private parsePackaging(packageZip?: boolean) {
if (packageZip != null) {
return packageZip ? 'ZIP' : 'NONE';
} else {
return 'ZIP';
}
}
}
Loading

0 comments on commit d911b08

Please sign in to comment.