Skip to content

Commit 43214b4

Browse files
authored
fix(codepipeline): S3 source Action with trigger=Events fails for bucketKey a Token (#9575)
We use bucketKey to differentiate between multiple source actions that observe the same bucket using trigger=Events. However, we can't do that if bucketKey is a lazy value, as Tokens can't be used as parts of identifier for the created Event. So, check for that case explicitly. Fixes #9554 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent cb6de0a commit 43214b4

File tree

2 files changed

+71
-7
lines changed

2 files changed

+71
-7
lines changed

packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as codepipeline from '@aws-cdk/aws-codepipeline';
22
import * as targets from '@aws-cdk/aws-events-targets';
33
import * as s3 from '@aws-cdk/aws-s3';
4-
import { Construct } from '@aws-cdk/core';
4+
import { Construct, Token } from '@aws-cdk/core';
55
import { Action } from '../action';
66
import { sourceArtifactBounds } from '../common';
77

@@ -110,11 +110,7 @@ export class S3SourceAction extends Action {
110110
protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
111111
codepipeline.ActionConfig {
112112
if (this.props.trigger === S3Trigger.EVENTS) {
113-
const id = stage.pipeline.node.uniqueId + 'SourceEventRule' + this.props.bucketKey;
114-
if (this.props.bucket.node.tryFindChild(id)) {
115-
// this means a duplicate path for the same bucket - error out
116-
throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`);
117-
}
113+
const id = this.generateEventId(stage);
118114
this.props.bucket.onCloudTrailWriteObject(id, {
119115
target: new targets.CodePipeline(stage.pipeline),
120116
paths: [this.props.bucketKey],
@@ -135,4 +131,33 @@ export class S3SourceAction extends Action {
135131
},
136132
};
137133
}
134+
135+
private generateEventId(stage: codepipeline.IStage): string {
136+
let ret: string;
137+
const baseId = stage.pipeline.node.uniqueId + 'SourceEventRule';
138+
139+
if (Token.isUnresolved(this.props.bucketKey)) {
140+
// If bucketKey is a Token, don't include it in the ID.
141+
// Instead, use numbers to differentiate if multiple actions observe the same bucket
142+
let candidate = baseId;
143+
let counter = 0;
144+
while (this.props.bucket.node.tryFindChild(candidate) !== undefined) {
145+
counter += 1;
146+
candidate = baseId + counter;
147+
}
148+
ret = candidate;
149+
} else {
150+
// we can't use Tokens in construct IDs,
151+
// however, if bucketKey is not a Token,
152+
// we want it to differentiate between multiple actions
153+
// observing the same Bucket with different keys
154+
ret = baseId + this.props.bucketKey;
155+
if (this.props.bucket.node.tryFindChild(ret)) {
156+
// this means a duplicate path for the same bucket - error out
157+
throw new Error(`S3 source action with path '${this.props.bucketKey}' is already present in the pipeline for this source bucket`);
158+
}
159+
}
160+
161+
return ret;
162+
}
138163
}

packages/@aws-cdk/aws-codepipeline-actions/test/s3/test.s3-source-action.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { countResources, expect, haveResourceLike, not } from '@aws-cdk/assert';
22
import * as codebuild from '@aws-cdk/aws-codebuild';
33
import * as codepipeline from '@aws-cdk/aws-codepipeline';
44
import * as s3 from '@aws-cdk/aws-s3';
5-
import { Stack } from '@aws-cdk/core';
5+
import { Lazy, Stack } from '@aws-cdk/core';
66
import { Test } from 'nodeunit';
77
import * as cpactions from '../../lib';
88

@@ -176,6 +176,45 @@ export = {
176176
test.done();
177177
},
178178

179+
'allows using a Token bucketKey with trigger = Events, multiple times'(test: Test) {
180+
const stack = new Stack();
181+
182+
const bucket = new s3.Bucket(stack, 'MyBucket');
183+
const sourceStage = minimalPipeline(stack, {
184+
bucket,
185+
bucketKey: Lazy.stringValue({ produce: () => 'my-bucket-key1' }),
186+
trigger: cpactions.S3Trigger.EVENTS,
187+
});
188+
sourceStage.addAction(new cpactions.S3SourceAction({
189+
actionName: 'Source2',
190+
bucket,
191+
bucketKey: Lazy.stringValue({ produce: () => 'my-bucket-key2' }),
192+
trigger: cpactions.S3Trigger.EVENTS,
193+
output: new codepipeline.Artifact(),
194+
}));
195+
196+
expect(stack, /* skipValidation = */ true).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
197+
'Stages': [
198+
{
199+
'Actions': [
200+
{
201+
'Configuration': {
202+
'S3ObjectKey': 'my-bucket-key1',
203+
},
204+
},
205+
{
206+
'Configuration': {
207+
'S3ObjectKey': 'my-bucket-key2',
208+
},
209+
},
210+
],
211+
},
212+
],
213+
}));
214+
215+
test.done();
216+
},
217+
179218
'exposes variables for other actions to consume'(test: Test) {
180219
const stack = new Stack();
181220

0 commit comments

Comments
 (0)