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(aws-codebuild): add support for secondary sources and artifact in Projects #1110

Merged
Merged
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
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({
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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': {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
'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: [
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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,
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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