Skip to content

Commit

Permalink
feat(aws-codepipeline): make input and output artifact names optional…
Browse files Browse the repository at this point in the history
… when creating Actions.

Previously, we always required customers to explicitly name the output artifacts the Actions used in the Pipeline,
and to explicitly "wire together" the outputs of one Action as inputs to another.
With this change, the CodePipeline Construct generates artifact names,
if the customer didn't provide one explicitly,
and tries to find the first available output artifact to use as input to a newly created Action that needs it,
thus turning both the input and output artifacts from required to optional properties.
  • Loading branch information
skinny85 committed Oct 3, 2018
1 parent df33047 commit b8e701c
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 49 deletions.
10 changes: 7 additions & 3 deletions packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import { ProjectRef } from './project';
*/
export interface CommonPipelineBuildActionProps {
/**
* The source to use as input for this build
* The source to use as input for this build.
*
* @default CodePipeline will use the output of the last Action from a previous Stage as input
*/
inputArtifact: codepipeline.Artifact;
inputArtifact?: codepipeline.Artifact;

/**
* The name of the build's output artifact
* The name of the build's output artifact.
*
* @default an auto-generated name will be used
*/
artifactName?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export abstract class ProjectRef extends cdk.Construct implements events.IEventR
* @param props the properties of the new Action
* @returns the newly created {@link PipelineBuildAction} build Action
*/
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineBuildActionProps): PipelineBuildAction {
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineBuildActionProps): PipelineBuildAction {
return new PipelineBuildAction(this.parent!, name, {
stage,
project: this,
Expand Down
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export interface CommonPipelineSourceActionProps {
/**
* The name of the source's output artifact.
* Output artifacts are used by CodePipeline as inputs into other actions.
*
* @default a name will be auto-generated
*/
artifactName: string;
artifactName?: string;

/**
* @default 'master'
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codecommit/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export abstract class RepositoryRef extends cdk.Construct {
* @param props the properties of the new Action
* @returns the newly created {@link PipelineSourceAction}
*/
public addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction {
public addToPipeline(stage: actions.IStage, name: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction {
return new PipelineSourceAction(this.parent!, name, {
stage,
repository: this,
Expand Down
33 changes: 29 additions & 4 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ export interface IStage {
* @param action the Action to add to this Stage
*/
_attachAction(action: Action): void;

/**
* Generates a unique output artifact name for the given Action.
* This is an internal operation -
* you should never need to call it directly.
*
* @param action the Action to generate the output artifact name for
*/
_generateOutputArtifactName(action: Action): string;

/**
* Finds an input artifact for the given Action
* among the existing output artifacts currently in the Pipeline.
* This is an internal operation -
* you should never need to call it directly.
*
* @param action the Action to find the input artifact for
*/
_findInputArtifactFor(action: Action): Artifact;
}

/**
Expand Down Expand Up @@ -192,13 +211,19 @@ export abstract class Action extends cdk.Construct {
}
}

protected addOutputArtifact(name: string): Artifact {
const artifact = new Artifact(this, name);
protected addOutputArtifact(name?: string): Artifact {
const outputArtifactName = name === undefined
? this.stage._generateOutputArtifactName(this)
: name;

const artifact = new Artifact(this, outputArtifactName);
return artifact;
}

protected addInputArtifact(artifact: Artifact): Action {
this._inputArtifacts.push(artifact);
protected addInputArtifact(artifact?: Artifact): Action {
this._inputArtifacts.push(artifact === undefined
? this.stage._findInputArtifactFor(this)
: artifact);
return this;
}
}
Expand Down
8 changes: 3 additions & 5 deletions packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface BuildActionProps extends CommonActionProps {
/**
* The source to use as input for this build.
*/
inputArtifact: Artifact;
inputArtifact?: Artifact;

/**
* The service provider that the action calls. For example, a valid provider for Source actions is CodeBuild.
Expand All @@ -36,7 +36,7 @@ export interface BuildActionProps extends CommonActionProps {
* such as {@link codebuild.PipelineBuildAction}.
*/
export abstract class BuildAction extends Action {
public readonly artifact?: Artifact;
public readonly artifact: Artifact;

constructor(parent: cdk.Construct, name: string, props: BuildActionProps) {
super(parent, name, {
Expand All @@ -48,8 +48,6 @@ export abstract class BuildAction extends Action {
});

this.addInputArtifact(props.inputArtifact);
if (props.artifactName) {
this.artifact = this.addOutputArtifact(props.artifactName);
}
this.artifact = this.addOutputArtifact(props.artifactName);
}
}
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ export interface SourceActionProps extends CommonActionProps {
/**
* The name of the source's output artifact.
* Output artifacts are used by CodePipeline as inputs into other actions.
*
* @default a name will be auto-generated
*/
artifactName: string;
artifactName?: string;

/**
* The service provider that the action calls.
Expand Down
69 changes: 50 additions & 19 deletions packages/@aws-cdk/aws-codepipeline/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
## AWS CodePipeline construct library
## AWS CodePipeline Construct Library

Construct an empty Pipeline:
### Pipeline

To construct an empty Pipeline:

```ts
import codepipeline = require('@aws-cdk/aws-codepipeline');

const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline');
```

To give the Pipeline a nice, human-readable name:

```ts
const pipeline = new Pipeline(this, 'MyFirstPipeline', {
pipelineName: 'MyFirstPipeline',
const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', {
pipelineName: 'MyPipeline',
});
```

Append a Stage to the Pipeline:
### Stages

To append a Stage to a Pipeline:

```ts
const sourceStage = pipeline.addStage('Source');
Expand All @@ -21,28 +33,47 @@ You can insert the new Stage at an arbitrary point in the Pipeline:

```ts
const sourceStage = pipeline.addStage('Source', {
placement: {
// note: you can only specify one of the below properties
rightBefore: anotherStage,
justAfter: anotherStage,
atIndex: 3, // indexing starts at 0
// pipeline.stageCount returns the number of Stages currently in the Pipeline
}
placement: {
// note: you can only specify one of the below properties
rightBefore: anotherStage,
justAfter: anotherStage,
atIndex: 3, // indexing starts at 0
// pipeline.stageCount returns the number of Stages currently in the Pipeline
}
});
```

Add an Action to a Stage:
### Actions

To add an Action to a Stage:

```ts
new codecommit.PipelineSourceAction(this, 'Source', {
stage: sourceStage,
artifactName: 'MyPackageSourceArtifact',
repository: codecommit.RepositoryRef.import(this, 'MyExistingRepository', {
repositoryName: new codecommit.RepositoryName('MyExistingRepository'),
}),
new codepipeline.GitHubSourceAction(this, 'GitHub_Source', {
stage: sourceStage,
owner: 'awslabs',
repo: 'aws-cdk',
branch: 'develop', // default: 'master'
oauthToken: ...,
})
```

The Pipeline construct will automatically generate and wire together the artifact names CodePipeline uses.
If you need, you can also name the artifacts explicitly:

```ts
const sourceAction = new codepipeline.GitHubSourceAction(this, 'GitHub_Source', {
// other properties as above...
artifactName: 'SourceOutput', // this will be the name of the output artifact in the Pipeline
});

// in a build Action later...

new codepipeline.JenkinsBuildAction(this, 'Jenkins_Build', {
// other properties...
inputArtifact: sourceAction.artifact,
});
```

### Events

#### Using a pipeline as an event target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export interface GitHubSourceActionProps extends actions.CommonActionProps {
/**
* The name of the source's output artifact. Output artifacts are used by CodePipeline as
* inputs into other actions.
*
* @default a name will be auto-generated
*/
artifactName: string;
artifactName?: string;

/**
* The GitHub account/user that owns the repo.
Expand Down
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget {

private readonly stages = new Array<Stage>();
private eventsRole?: iam.Role;
private artifactsCounter = 0;

constructor(parent: cdk.Construct, name: string, props?: PipelineProps) {
super(parent, name);
Expand Down Expand Up @@ -245,6 +246,37 @@ export class Pipeline extends cdk.Construct implements events.IEventRuleTarget {
this.stages.splice(index, 0, stage);
}

// ignore unused private method (it's actually used in Stage)
// @ts-ignore
private _generateOutputArtifactName(stage: actions.IStage, action: actions.Action): string {
// for now, just return a generic output artifact,
// ignoring the names of both the Stage, and the Action
return 'Artifact_' + (++this.artifactsCounter);
}

// ignore unused private method (it's actually used in Stage)
// @ts-ignore
private _findInputArtifactFor(stage: actions.IStage, action: actions.Action): actions.Artifact {
// search for the first Action that has an outputArtifact,
// and return that
const startIndex = this.stages.findIndex(s => s === stage);
for (let i = startIndex; i >= 0; i--) {
const currentStage = this.stages[i];

// get all of the Actions in the Stage, sorted by runOrder, descending
const currentActions = currentStage.actions.sort((a1, a2) => -(a1.runOrder - a2.runOrder));
for (const currentAction of currentActions) {
// for the first Stage (the one that `action` belongs to)
// we need to only take into account Actions with a smaller runOrder than `action`
if ((i !== startIndex || currentAction.runOrder < action.runOrder) && currentAction.outputArtifacts.length > 0) {
return currentAction.outputArtifacts[0];
}
}
}
throw new Error(`Could not determine the input artifact for Action with name '${action.id}'. ` +
'Please provide it explicitly with the inputArtifact property.');
}

private calculateInsertIndexFromPlacement(placement: StagePlacement): number {
// check if at most one placement property was provided
const providedPlacementProps = ['rightBefore', 'justAfter', 'atIndex']
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ export class Stage extends cdk.Construct implements actions.IStage {
}
}

public _generateOutputArtifactName(action: actions.Action): string {
return (this.pipeline as any)._generateOutputArtifactName(this, action);
}

public _findInputArtifactFor(action: actions.Action): actions.Artifact {
return (this.pipeline as any)._findInputArtifactFor(this, action);
}

private renderAction(action: actions.Action): cloudformation.PipelineResource.ActionDeclarationProperty {
return {
name: action.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@
}
],
"Name": "build",
"OutputArtifacts": [],
"OutputArtifacts": [
{
"Name": "Artifact_1"
}
],
"RunOrder": 1
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@
}
],
"Name": "CodeBuildAction",
"OutputArtifacts": [],
"OutputArtifacts": [
{
"Name": "Artifact_1"
}
],
"RunOrder": 1
}
],
Expand Down
Loading

0 comments on commit b8e701c

Please sign in to comment.