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(codepipeline): Add CodeConnections source for creating codepipeline #31028

Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { Construct } from 'constructs';
import * as codepipeline from '../../../aws-codepipeline';
import * as iam from '../../../aws-iam';
import { Action } from '../action';
import { sourceArtifactBounds } from '../common';

/**
* The CodePipeline variables emitted by CodeStar source Action.
*/
export interface CodeConnectionsSourceVariables {
/** The name of the repository this action points to. */
readonly fullRepositoryName: string;
/** The name of the branch this action tracks. */
readonly branchName: string;
/** The date the currently last commit on the tracked branch was authored, in ISO-8601 format. */
readonly authorDate: string;
/** The SHA1 hash of the currently last commit on the tracked branch. */
readonly commitId: string;
/** The message of the currently last commit on the tracked branch. */
readonly commitMessage: string;
/** The connection ARN this source uses. */
readonly connectionArn: string;
}

/**
* Construction properties for `CodeStarConnectionsSourceAction`.
*/
export interface CodeConnectionsSourceActionProps extends codepipeline.CommonAwsActionProps {
/**
* The output artifact that this action produces.
* Can be used as input for further pipeline actions.
*/
readonly output: codepipeline.Artifact;

/**
* The ARN of the CodeStar Connection created in the AWS console
* that has permissions to access this GitHub or BitBucket repository.
*
* @example 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh'
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-create.html
*/
readonly connectionArn: string;

/**
* The owning user or organization of the repository.
*
* @example 'aws'
*/
readonly owner: string;

/**
* The name of the repository.
*
* @example 'aws-cdk'
*/
readonly repo: string;

/**
* The branch to build.
*
* @default 'master'
*/
readonly branch?: string;

// long URL in @see
/**
* Whether the output should be the contents of the repository
* (which is the default),
* or a link that allows CodeBuild to clone the repository before building.
*
* **Note**: if this option is true,
* then only CodeBuild actions can use the resulting `output`.
*
* @default false
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodestarConnectionSource.html#action-reference-CodestarConnectionSource-config
*/
readonly codeBuildCloneOutput?: boolean;

/**
* Controls automatically starting your pipeline when a new commit
* is made on the configured repository and branch. If unspecified,
* the default value is true, and the field does not display by default.
*
* @default true
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodestarConnectionSource.html
*/
readonly triggerOnPush?: boolean;
}

/**
* A CodePipeline source action for the CodeStar Connections source,
* which allows connecting to GitHub and BitBucket.
*/
export class CodeConnectionsSourceAction extends Action {
/**
* The name of the property that holds the ARN of the CodeStar Connection
* inside of the CodePipeline Artifact's metadata.
*
* @internal
*/
public static readonly _CONNECTION_ARN_PROPERTY = 'CodeConnectionsArnProperty';

private readonly props: CodeConnectionsSourceActionProps;

constructor(props: CodeConnectionsSourceActionProps) {
super({
...props,
category: codepipeline.ActionCategory.SOURCE,
owner: 'AWS', // because props also has a (different!) owner property!
provider: 'CodeConnections',
artifactBounds: sourceArtifactBounds(),
outputs: [props.output],
});

this.props = props;
}

/** The variables emitted by this action. */
public get variables(): CodeConnectionsSourceVariables {
return {
fullRepositoryName: this.variableExpression('FullRepositoryName'),
branchName: this.variableExpression('BranchName'),
authorDate: this.variableExpression('AuthorDate'),
commitId: this.variableExpression('CommitId'),
commitMessage: this.variableExpression('CommitMessage'),
connectionArn: this.variableExpression('ConnectionArn'),
};
}

protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig {
// https://docs.aws.amazon.com/codepipeline/latest/userguide/security-iam.html#how-to-update-role-new-services
options.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
'codeconnections:UseConnection',
],
resources: [
this.props.connectionArn,
],
}));

// the action needs to write the output to the pipeline bucket
options.bucket.grantReadWrite(options.role);
options.bucket.grantPutAcl(options.role);

// if codeBuildCloneOutput is true,
// save the connectionArn in the Artifact instance
// to be read by the CodeBuildAction later
if (this.props.codeBuildCloneOutput === true) {
this.props.output.setMetadata(CodeConnectionsSourceAction._CONNECTION_ARN_PROPERTY,
this.props.connectionArn);
}

return {
configuration: {
ConnectionArn: this.props.connectionArn,
FullRepositoryId: `${this.props.owner}/${this.props.repo}`,
BranchName: this.props.branch ?? 'master',
OutputArtifactFormat: this.props.codeBuildCloneOutput === true
? 'CODEBUILD_CLONE_REF'
: undefined,
DetectChanges: this.props.triggerOnPush,
},
};
}
}
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/aws-codepipeline-actions/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './alexa-ask/deploy-action';
export * from './bitbucket/source-action';
export * from './codestar-connections/source-action';
export * from './codeconnections/source-action';
export * from './cloudformation';
export * from './codebuild/build-action';
export * from './codecommit/source-action';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Stack } from 'aws-cdk-lib';
import { Artifact } from 'aws-cdk-lib/aws-codepipeline';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { CodeConnectionsSourceAction, CodeConnectionsSourceActionProps } from '../../lib';

describe('CodeConnectionsSourceAction', () => {
test('requires connectionArn', () => {
const stack = new Stack();

expect(() => {
new CodeConnectionsSourceAction({
actionName: 'GitHub_Source',
owner: 'aws',
repo: 'aws-cdk',
output: new Artifact(),
} as CodeConnectionsSourceActionProps); // Type assertion to bypass type check for missing props
}).toThrow(/connectionArn is required/);
});

test('creates correct configuration', () => {
const stack = new Stack();
const action = new CodeConnectionsSourceAction({
actionName: 'GitHub_Source',
connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh',
owner: 'aws',
repo: 'aws-cdk',
output: new Artifact(),
});

expect(action).toBeDefined();
const config = action.bind(stack, {
role: new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
}),
bucket: new s3.Bucket(stack, 'PipelineBucket')
});

expect(config.configuration.ConnectionArn).toEqual('arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh');
expect(config.configuration.FullRepositoryId).toEqual('aws/aws-cdk');
expect(config.configuration.BranchName).toEqual('master');
});

test('creates correct configuration with custom branch', () => {
const stack = new Stack();
const action = new CodeConnectionsSourceAction({
actionName: 'GitHub_Source',
connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh',
owner: 'aws',
repo: 'aws-cdk',
output: new Artifact(),
branch: 'develop',
});

expect(action).toBeDefined();
const config = action.bind(stack, {
role: new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
}),
bucket: new s3.Bucket(stack, 'PipelineBucket')
});

expect(config.configuration.BranchName).toEqual('develop');
});

test('handles codeBuildCloneOutput option', () => {
const stack = new Stack();
const outputArtifact = new Artifact();
const action = new CodeConnectionsSourceAction({
actionName: 'GitHub_Source',
connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh',
owner: 'aws',
repo: 'aws-cdk',
output: outputArtifact,
codeBuildCloneOutput: true,
});

expect(action).toBeDefined();
const config = action.bind(stack, {
role: new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
}),
bucket: new s3.Bucket(stack, 'PipelineBucket')
});

expect(config.configuration.OutputArtifactFormat).toEqual('CODEBUILD_CLONE_REF');

// Indirect way to verify that the metadata was set
const outputArtifactMetadata = (outputArtifact as any)._metadata; // Type assertion to access private member for test purposes
expect(outputArtifactMetadata[CodeConnectionsSourceAction._CONNECTION_ARN_PROPERTY]).toEqual('arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh');
});

test('default triggerOnPush is true', () => {
const stack = new Stack();
const action = new CodeConnectionsSourceAction({
actionName: 'GitHub_Source',
connectionArn: 'arn:aws:codestar-connections:us-east-1:123456789012:connection/12345678-abcd-12ab-34cdef5678gh',
owner: 'aws',
repo: 'aws-cdk',
output: new Artifact(),
});

expect(action).toBeDefined();
const config = action.bind(stack, {
role: new iam.Role(stack, 'Role', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
}),
bucket: new s3.Bucket(stack, 'PipelineBucket')
});

expect(config.configuration.DetectChanges).toEqual(true);
});
});
Loading
Loading