Skip to content

Commit fc11ae2

Browse files
authored
feat(pipelines): ECR source action (#16385)
Closes #16378 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 040238e commit fc11ae2

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

packages/@aws-cdk/pipelines/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,16 @@ const bucket = s3.Bucket.fromBucketName(this, 'Bucket', 'my-bucket');
426426
pipelines.CodePipelineSource.s3(bucket, 'my/source.zip');
427427
```
428428

429+
##### ECR
430+
431+
You can use a Docker image in ECR as the source of the pipeline. The pipeline will be
432+
triggered every time an image is pushed to ECR:
433+
434+
```ts
435+
const repository = new ecr.Repository(this, 'Repository');
436+
pipelines.CodePipelineSource.ecr(repository);
437+
```
438+
429439
#### Additional inputs
430440

431441
`ShellStep` allows passing in more than one input: additional

packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts

+58-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import * as cp from '@aws-cdk/aws-codepipeline';
33
import { Artifact } from '@aws-cdk/aws-codepipeline';
44
import * as cp_actions from '@aws-cdk/aws-codepipeline-actions';
55
import { Action, CodeCommitTrigger, GitHubTrigger, S3Trigger } from '@aws-cdk/aws-codepipeline-actions';
6+
import { IRepository } from '@aws-cdk/aws-ecr';
67
import * as iam from '@aws-cdk/aws-iam';
78
import { IBucket } from '@aws-cdk/aws-s3';
8-
import { SecretValue, Token } from '@aws-cdk/core';
9+
import { Fn, SecretValue, Token } from '@aws-cdk/core';
910
import { Node } from 'constructs';
1011
import { FileSet, Step } from '../blueprint';
1112
import { CodePipelineActionFactoryResult, ProduceActionOptions, ICodePipelineActionFactory } from './codepipeline-action-factory';
@@ -57,6 +58,22 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc
5758
return new S3Source(bucket, objectKey, props);
5859
}
5960

61+
/**
62+
* Returns an ECR source.
63+
*
64+
* @param repository The repository that will be watched for changes.
65+
* @param props The options, which include the image tag to be checked for changes.
66+
*
67+
* @example
68+
* declare const repository: ecr.IRepository;
69+
* pipelines.CodePipelineSource.ecr(repository, {
70+
* imageTag: 'latest',
71+
* });
72+
*/
73+
public static ecr(repository: IRepository, props: ECRSourceOptions = {}): CodePipelineSource {
74+
return new ECRSource(repository, props);
75+
}
76+
6077
/**
6178
* Returns a CodeStar connection source. A CodeStar connection allows AWS CodePipeline to
6279
* access external resources, such as repositories in GitHub, GitHub Enterprise or
@@ -264,6 +281,46 @@ class S3Source extends CodePipelineSource {
264281
}
265282
}
266283

284+
/**
285+
* Options for ECR sources
286+
*/
287+
export interface ECRSourceOptions {
288+
/**
289+
* The image tag that will be checked for changes.
290+
*
291+
* @default latest
292+
*/
293+
readonly imageTag?: string;
294+
295+
/**
296+
* The action name used for this source in the CodePipeline
297+
*
298+
* @default - The repository name
299+
*/
300+
readonly actionName?: string;
301+
}
302+
303+
class ECRSource extends CodePipelineSource {
304+
constructor(readonly repository: IRepository, readonly props: ECRSourceOptions) {
305+
super(Node.of(repository).addr);
306+
307+
this.configurePrimaryOutput(new FileSet('Source', this));
308+
}
309+
310+
protected getAction(output: Artifact, _actionName: string, runOrder: number, variablesNamespace: string) {
311+
// RepositoryName can contain '/' that is not a valid ActionName character, use '_' instead
312+
const formattedRepositoryName = Fn.join('_', Fn.split('/', this.repository.repositoryName));
313+
return new cp_actions.EcrSourceAction({
314+
output,
315+
actionName: this.props.actionName ?? formattedRepositoryName,
316+
runOrder,
317+
repository: this.repository,
318+
imageTag: this.props.imageTag,
319+
variablesNamespace,
320+
});
321+
}
322+
}
323+
267324
/**
268325
* Configuration options for CodeStar source
269326
*/

packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-sources.test.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Capture, Match, Template } from '@aws-cdk/assertions';
22
import * as ccommit from '@aws-cdk/aws-codecommit';
33
import { CodeCommitTrigger, GitHubTrigger } from '@aws-cdk/aws-codepipeline-actions';
4+
import * as ecr from '@aws-cdk/aws-ecr';
45
import { AnyPrincipal, Role } from '@aws-cdk/aws-iam';
56
import * as s3 from '@aws-cdk/aws-s3';
67
import { SecretValue, Stack, Token } from '@aws-cdk/core';
@@ -75,9 +76,9 @@ test('CodeCommit source honors all valid properties', () => {
7576
});
7677

7778
test('S3 source handles tokenized names correctly', () => {
78-
const buckit = new s3.Bucket(pipelineStack, 'Buckit');
79+
const bucket = new s3.Bucket(pipelineStack, 'Bucket');
7980
new ModernTestGitHubNpmPipeline(pipelineStack, 'Pipeline', {
80-
input: cdkp.CodePipelineSource.s3(buckit, 'thefile.zip'),
81+
input: cdkp.CodePipelineSource.s3(bucket, 'thefile.zip'),
8182
});
8283

8384
Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', {
@@ -96,6 +97,40 @@ test('S3 source handles tokenized names correctly', () => {
9697
});
9798
});
9899

100+
test('ECR source handles tokenized and namespaced names correctly', () => {
101+
const repository = new ecr.Repository(pipelineStack, 'Repository', { repositoryName: 'namespace/repo' });
102+
new ModernTestGitHubNpmPipeline(pipelineStack, 'Pipeline', {
103+
input: cdkp.CodePipelineSource.ecr(repository),
104+
});
105+
106+
const template = Template.fromStack(pipelineStack);
107+
template.hasResourceProperties('AWS::CodePipeline::Pipeline', {
108+
Stages: Match.arrayWith([{
109+
Name: 'Source',
110+
Actions: [
111+
Match.objectLike({
112+
Configuration: Match.objectLike({
113+
RepositoryName: { Ref: Match.anyValue() },
114+
}),
115+
Name: Match.objectLike({
116+
'Fn::Join': [
117+
'_',
118+
{
119+
'Fn::Split': [
120+
'/',
121+
{
122+
Ref: Match.anyValue(),
123+
},
124+
],
125+
},
126+
],
127+
}),
128+
}),
129+
],
130+
}]),
131+
});
132+
});
133+
99134
test('GitHub source honors all valid properties', () => {
100135
new ModernTestGitHubNpmPipeline(pipelineStack, 'Pipeline', {
101136
input: cdkp.CodePipelineSource.gitHub('owner/repo', 'main', {

0 commit comments

Comments
 (0)