Skip to content

Commit

Permalink
feat(aws-codepipeline): Jenkins build and test Actions.
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Nov 19, 2018
1 parent 127bb59 commit 72fa5d9
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 0 deletions.
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { Artifact } from './artifact';
import { CustomActionRegistrationProps } from './custom-action-registration-props';
import validation = require('./validation');

export enum ActionCategory {
Expand Down Expand Up @@ -65,6 +66,14 @@ export interface IInternalStage {
* @param action the Action to find the input artifact for
*/
_findInputArtifact(action: Action): Artifact;

/**
* Registers a custom Action.
* Done in the Pipeline construct to not register the same custom Action multiple times.
*
* @param props the properties of the custom Action registration
*/
_registerCustomAction(props: CustomActionRegistrationProps): boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ActionArtifactBounds, ActionCategory } from "./action";

/**
* The creation attributes used for defining a configuration property
* of a custom Action.
*/
export interface CustomActionProperty {
/**
* The name of the property.
* You use this name in the `configuration` attribute when defining your custom Action class.
*/
name: string;

/**
* The description of the property.
*
* @default the description will be empty
*/
description?: string;

// because of @see URLs
// tslint:disable:max-line-length

/**
* Whether this property is a key.
*
* @default false
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-key
*/
key?: boolean;

/**
* Whether this property is queryable.
* Note that only a single property of a custom Action can be queryable.
*
* @default false
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-customactiontype-configurationproperties.html#cfn-codepipeline-customactiontype-configurationproperties-queryable
*/
queryable?: boolean;

// tslint:enable:max-line-length

/**
* Whether this property is required.
*/
required: boolean;

/**
* Whether this property is secret,
* like a password, or access key.
*
* @default false
*/
secret?: boolean;

/**
* The type of the property,
* like 'String', 'Number', or 'Boolean'.
*
* @default 'String'
*/
type?: string;
}

/**
* Properties of registering a custom Action.
*/
export interface CustomActionRegistrationProps {
/**
* The category of the Action.
*/
category: ActionCategory;

/**
* The artifact bounds of the Action.
*/
artifactBounds: ActionArtifactBounds;

/**
* The provider of the Action.
*/
provider: string;

/**
* The version of your Action.
*
* @default '1'
*/
version?: string;

/**
* The URL shown for the entire Action in the Pipeline UI.
*/
entityUrl?: string;

/**
* The URL shown for a particular execution of an Action in the Pipeline UI.
*/
executionUrl?: string;

/**
* The properties used for customizing the instance of your Action.
*
* @default []
*/
actionProperties?: CustomActionProperty[];
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codepipeline-api/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './artifact';
export * from './action';
export * from './build-action';
export * from './custom-action-registration-props';
export * from './deploy-action';
export * from './source-action';
export * from './test-action';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import cpapi = require('@aws-cdk/aws-codepipeline-api');
import cdk = require('@aws-cdk/cdk');
import { cloudformation } from './codepipeline.generated';

/**
* The resource representing registering a custom Action with CodePipeline.
* For the Action to be usable, it has to be registered for every region and every account it's used in.
* In addition to this class, you should most likely also provide your clients a class
* represting your custom Action, extending the Action class,
* and taking the `actionProperties` as properly typed, construction properties.
*/
export class CustomActionRegistration extends cdk.Construct {
constructor(parent: cdk.Construct, id: string, props: cpapi.CustomActionRegistrationProps) {
super(parent, id);

new cloudformation.CustomActionTypeResource(this, 'Resource', {
category: props.category,
inputArtifactDetails: {
minimumCount: props.artifactBounds.minInputs,
maximumCount: props.artifactBounds.maxInputs,
},
outputArtifactDetails: {
minimumCount: props.artifactBounds.minOutputs,
maximumCount: props.artifactBounds.maxOutputs,
},
provider: props.provider,
version: props.version || '1',
settings: {
entityUrlTemplate: props.entityUrl,
executionUrlTemplate: props.executionUrl,
},
configurationProperties: props.actionProperties === undefined ? undefined : props.actionProperties.map((ap) => { return {
key: ap.key || false,
secret: ap.secret || false,
...ap,
}; }),
});
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './cross-region-scaffold-stack';
export * from './github-source-action';
export * from './jenkins-actions';
export * from './manual-approval-action';
export * from './pipeline';
export * from './stage';
Expand Down
165 changes: 165 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import cpapi = require('@aws-cdk/aws-codepipeline-api');
import cdk = require('@aws-cdk/cdk');

/**
* Common construction properties of all Jenkins Pipeline Actions.
*/
export interface CommonJenkinsActionProps extends cpapi.CommonActionProps,
cpapi.CommonActionConstructProps {
/**
* The name of the project (sometimes also called job, or task)
* on your Jenkins installation that will be invoked by this Aciton.
*
* @example 'MyJob'
*/
projectName: string;

/**
* The name of the Jenkins provider that you set in the AWS CodePipeline plugin configuration of your Jenkins project.
*
* @example 'MyJenkinsProvider'
*/
providerName: string;

/**
* The base URL of your Jenkins server.
*
* @example 'http://myjenkins.com:8080'
*/
serverUrl: string;

/**
* The version of your provider.
*
* @default '1'
*/
version?: string;

/**
* Whether to register the Jenkins provider as a custom Action with CodePipeline.
* If you've registered this provider-category-version combination in the account and region in a different CDK app,
* or outside the CDK completely, set this to `false`.
* Note that you don't have to set this if you have multiple Actions in your Pipeline using the same Jenkins provider -
* the Pipeline construct will make sure that the custom Action is registered only once.
*
* @default true
*/
registerProvider?: boolean;
}

/**
* Construction properties of {@link JenkinsBuildAction}.
*/
export interface JenkinsBuildActionProps extends CommonJenkinsActionProps {
/**
* 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?: cpapi.Artifact;

/**
* The name of the build's output artifact.
*
* @default an auto-generated name will be used
*/
outputArtifactName?: string;
}

/**
* Jenkins build CodePipeline Action.
*
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html
*/
export class JenkinsBuildAction extends cpapi.BuildAction {
constructor(parent: cdk.Construct, name: string, props: JenkinsBuildActionProps) {
super(parent, name, {
provider: props.providerName,
owner: 'Custom',
artifactBounds: jenkinsArtifactsBounds,
configuration: {
ProjectName: props.projectName,
},
...props,
});

registerCustomJenkinsAction(cpapi.ActionCategory.Build, props);
}
}

/**
* Construction properties of {@link JenkinsTestAction}.
*/
export interface JenkinsTestActionProps extends CommonJenkinsActionProps {
/**
* The source to use as input for this test.
*
* @default CodePipeline will use the output of the last Action from a previous Stage as input
*/
inputArtifact?: cpapi.Artifact;

/**
* The optional name of the primary output artifact.
* If you provide a value here,
* then the `outputArtifact` property of your Action will be non-null.
* If you don't, `outputArtifact` will be `null`.
*
* @default the Action will not have an output artifact
*/
outputArtifactName?: string;
}

/**
* Jenkins test CodePipeline Action.
*
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html
*/
export class JenkinsTestAction extends cpapi.TestAction {
constructor(parent: cdk.Construct, name: string, props: JenkinsTestActionProps) {
super(parent, name, {
provider: props.providerName,
owner: 'Custom',
artifactBounds: jenkinsArtifactsBounds,
configuration: {
ProjectName: props.projectName,
},
...props,
});

registerCustomJenkinsAction(cpapi.ActionCategory.Test, props);
}
}

function registerCustomJenkinsAction(category: cpapi.ActionCategory, props: CommonJenkinsActionProps): void {
if (props.registerProvider === false) {
return;
}

props.stage._internal._registerCustomAction({
category,
artifactBounds: jenkinsArtifactsBounds,
provider: props.providerName,
version: props.version,
entityUrl: appendToUrl(props.serverUrl, 'job/{Config:ProjectName}'),
executionUrl: appendToUrl(props.serverUrl, 'job/{Config:ProjectName}/{ExternalExecutionId}'),
actionProperties: [
{
name: 'ProjectName',
required: true,
key: true,
queryable: true,
},
],
});
}

function appendToUrl(baseUrl: string, path: string): string {
return baseUrl.endsWith('/') ? baseUrl + path : `${baseUrl}/${path}`;
}

const jenkinsArtifactsBounds: cpapi.ActionArtifactBounds = {
minInputs: 0,
maxInputs: 5,
minOutputs: 0,
maxOutputs: 5
};
21 changes: 21 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
import { cloudformation } from './codepipeline.generated';
import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack';
import { CustomActionRegistration } from './custom-action-registration';
import { CommonStageProps, Stage, StagePlacement } from './stage';

export interface PipelineProps {
Expand Down Expand Up @@ -366,6 +367,26 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline {
'Please provide it explicitly with the inputArtifact property.');
}

/**
* Register a custom Action with the CodePipeline API.
* Includes de-duplication logic, so that the same Action is registered only once.
*
* @param props the registration properties of the new Action
* @returns true if the registration was performed, false if this Action was alredy registered
*/
// ignore unused private method (it's actually used in Stage)
// @ts-ignore
private _registerCustomAction(props: cpapi.CustomActionRegistrationProps): boolean {
const registrationId = `CustomActionRegistration_${props.provider}_${props.category}_${props.version}`;
const existingRegistration = this.tryFindChild(registrationId);
if (existingRegistration) {
return false;
} else {
new CustomActionRegistration(this, registrationId, props);
return true;
}
}

private calculateInsertIndexFromPlacement(placement: StagePlacement): number {
// check if at most one placement property was provided
const providedPlacementProps = ['rightBefore', 'justAfter', 'atIndex']
Expand Down
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna
return (this.pipeline as any)._findInputArtifact(this, action);
}

public _registerCustomAction(props: cpapi.CustomActionRegistrationProps): boolean {
return (this.pipeline as any)._registerCustomAction(props);
}

private renderAction(action: cpapi.Action): cloudformation.PipelineResource.ActionDeclarationProperty {
return {
name: action.id,
Expand Down
Loading

0 comments on commit 72fa5d9

Please sign in to comment.