Skip to content

Commit

Permalink
refactor(codepipeline): unify the Action bind() signature, and introd…
Browse files Browse the repository at this point in the history
…uce IAction interface.
  • Loading branch information
skinny85 committed Jun 23, 2019
1 parent 98afdeb commit f2b481f
Show file tree
Hide file tree
Showing 30 changed files with 883 additions and 714 deletions.
2 changes: 2 additions & 0 deletions packages/@aws-cdk/app-delivery/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@aws-cdk/aws-codebuild": "^0.35.0",
"@aws-cdk/aws-codepipeline": "^0.35.0",
"@aws-cdk/aws-codepipeline-actions": "^0.35.0",
"@aws-cdk/aws-events": "^0.35.0",
"@aws-cdk/aws-iam": "^0.35.0",
"@aws-cdk/cdk": "^0.35.0",
"@aws-cdk/cx-api": "^0.35.0"
Expand Down Expand Up @@ -75,6 +76,7 @@
"@aws-cdk/aws-codebuild": "^0.35.0",
"@aws-cdk/aws-codepipeline": "^0.35.0",
"@aws-cdk/aws-codepipeline-actions": "^0.35.0",
"@aws-cdk/aws-events": "^0.35.0",
"@aws-cdk/aws-iam": "^0.35.0",
"@aws-cdk/cdk": "^0.35.0",
"@aws-cdk/cx-api": "^0.35.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cfn = require('@aws-cdk/aws-cloudformation');
import codebuild = require('@aws-cdk/aws-codebuild');
import codepipeline = require('@aws-cdk/aws-codepipeline');
import cpactions = require('@aws-cdk/aws-codepipeline-actions');
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
Expand Down Expand Up @@ -337,22 +338,35 @@ export = nodeunit.testCase({
}
});

class FakeAction extends codepipeline.Action {
class FakeAction implements codepipeline.IAction {
public readonly actionName: string;
public readonly category = codepipeline.ActionCategory.TEST;
public readonly provider = 'Test';
public readonly artifactBounds = { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 };

public readonly inputs = undefined;
public readonly outputs = undefined;
public readonly owner = undefined;
public readonly version = undefined;
public readonly runOrder = undefined;
public readonly region = undefined;
public readonly resource = undefined;
public readonly role = undefined;

public readonly outputArtifact: codepipeline.Artifact;

constructor(actionName: string) {
super({
actionName,
artifactBounds: { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 },
category: codepipeline.ActionCategory.TEST,
provider: 'Test',
});

this.actionName = actionName;
this.outputArtifact = new codepipeline.Artifact('OutputArtifact');
}

protected bind(_info: codepipeline.ActionBind): void {
// do nothing
public bind(_scope: cdk.Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
return {};
}

public onStateChange(_name: string, _target?: events.IRuleTarget, _options?: events.RuleProps): events.Rule {
throw new Error('onStateChange() is not available on FakeAction');
}
}

Expand Down
177 changes: 177 additions & 0 deletions packages/@aws-cdk/aws-codepipeline-actions/lib/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import codepipeline = require('@aws-cdk/aws-codepipeline');
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import { Construct, IResource } from "@aws-cdk/cdk";

/**
* Construction properties of the low-level {@link Action Action class}.
*/
export interface ActionProps extends codepipeline.CommonActionProps {
readonly category: codepipeline.ActionCategory;
readonly provider: string;

/**
* The region this Action resides in.
*
* @default the Action resides in the same region as the Pipeline
*/
readonly region?: string;

/**
* The service role that is assumed during execution of action.
* This role is not mandatory, however more advanced configuration
* may require specifying it.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html
*/
readonly role?: iam.IRole;

readonly artifactBounds: codepipeline.ActionArtifactBounds;
readonly inputs?: codepipeline.Artifact[];
readonly outputs?: codepipeline.Artifact[];
readonly version?: string;
readonly owner?: string;

/**
* The optional resource that is backing this Action.
* This is used for automatically handling Actions backed by
* resources from a different account and/or region.
*
* @default the Action is not backed by any resource
*/
readonly resource?: IResource;
}

/**
* Low-level class for generic CodePipeline Actions.
*/
export abstract class Action implements codepipeline.IAction {
/**
* The category of the action.
* The category defines which action type the owner
* (the entity that performs the action) performs.
*/
public readonly category: codepipeline.ActionCategory;

/**
* The service provider that the action calls.
*/
public readonly provider: string;

/**
* The AWS region the given Action resides in.
* Note that a cross-region Pipeline requires replication buckets to function correctly.
* You can provide their names with the {@link PipelineProps#crossRegionReplicationBuckets} property.
* If you don't, the CodePipeline Construct will create new Stacks in your CDK app containing those buckets,
* that you will need to `cdk deploy` before deploying the main, Pipeline-containing Stack.
*
* @default the Action resides in the same region as the Pipeline
*/
public readonly region?: string;

/**
* The optional resource that is backing this Action.
* This is used for automatically handling Actions backed by
* resources from a different account and/or region.
*/
public readonly resource?: IResource;

/**
* The order in which AWS CodePipeline runs this action.
* For more information, see the AWS CodePipeline User Guide.
*
* https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements
*/
public readonly runOrder?: number;

public readonly owner?: string;
public readonly version?: string;
public readonly actionName: string;
public readonly artifactBounds: codepipeline.ActionArtifactBounds;
public readonly role?: iam.IRole;
public readonly inputs?: codepipeline.Artifact[];
public readonly outputs?: codepipeline.Artifact[];

private _pipeline?: codepipeline.IPipeline;
private _stage?: codepipeline.IStage;
private _scope?: Construct;

constructor(props: ActionProps) {
this.owner = props.owner;
this.version = props.version;
this.category = props.category;
this.provider = props.provider;
this.region = props.region;
this.artifactBounds = props.artifactBounds;
this.runOrder = props.runOrder;
this.actionName = props.actionName;
this.role = props.role;
this.resource = props.resource;
this.inputs = props.inputs;
this.outputs = props.outputs;
}

public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps) {
const rule = new events.Rule(this.scope, name, options);
rule.addTarget(target);
rule.addEventPattern({
detailType: [ 'CodePipeline Stage Execution State Change' ],
source: [ 'aws.codepipeline' ],
resources: [ this.pipeline.pipelineArn ],
detail: {
stage: [ this.stage.stageName ],
action: [ this.actionName ],
},
});
return rule;
}

public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
this._pipeline = stage.pipeline;
this._stage = stage;
this._scope = scope;

return this.bound(scope, stage, options);
}

/**
* The method called when an Action is attached to a Pipeline.
* This method is guaranteed to be called only once for each Action instance.
*
* @param options an instance of the {@link ActionBindOptions} class,
* that contains the necessary information for the Action
* to configure itself, like a reference to the Role, etc.
*/
protected abstract bound(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig;

private get pipeline(): codepipeline.IPipeline {
if (this._pipeline) {
return this._pipeline;
} else {
throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange');
}
}

private get stage(): codepipeline.IStage {
if (this._stage) {
return this._stage;
} else {
throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange');
}
}

/**
* Retrieves the Construct scope of this Action.
* Only available after the Action has been added to a Stage,
* and that Stage to a Pipeline.
*/
private get scope(): Construct {
if (this._scope) {
return this._scope;
} else {
throw new Error('Action must be added to a stage that is part of a pipeline first');
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import codepipeline = require('@aws-cdk/aws-codepipeline');
import { SecretValue } from '@aws-cdk/cdk';
import { Construct, SecretValue } from '@aws-cdk/cdk';
import { Action } from '../action';

/**
* Construction properties of the {@link AlexaSkillDeployAction Alexa deploy Action}.
Expand Down Expand Up @@ -39,7 +40,9 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr
/**
* Deploys the skill to Alexa
*/
export class AlexaSkillDeployAction extends codepipeline.Action {
export class AlexaSkillDeployAction extends Action {
private readonly props: AlexaSkillDeployActionProps;

constructor(props: AlexaSkillDeployActionProps) {
super({
...props,
Expand All @@ -53,17 +56,21 @@ export class AlexaSkillDeployAction extends codepipeline.Action {
maxOutputs: 0,
},
inputs: getInputs(props),
configuration: {
ClientId: props.clientId,
ClientSecret: props.clientSecret,
RefreshToken: props.refreshToken,
SkillId: props.skillId,
},
});

this.props = props;
}

protected bind(_info: codepipeline.ActionBind): void {
// nothing to do
protected bound(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
return {
configuration: {
ClientId: this.props.clientId,
ClientSecret: this.props.clientSecret,
RefreshToken: this.props.refreshToken,
SkillId: this.props.skillId,
},
};
}
}

Expand Down
Loading

0 comments on commit f2b481f

Please sign in to comment.