Skip to content

Commit 47bf435

Browse files
rix0rrrElad Ben-Israel
authored andcommitted
fix(ssm): correctly deduplicate parameter names (#3183)
Parameter names with '/' in them would not be correctly deduplicated, since the '/' is also used to delimit identifiers in construct paths. Change findChild() so it no longer traverses into the tree; if we assume it will only ever find one child deep, we can do the same kind of escaping there as we apply to construct IDs. Fixes #3076. BREAKING CHANGE: `construct.findChild()` now only looks up direct children
1 parent 4b88621 commit 47bf435

File tree

5 files changed

+45
-31
lines changed

5 files changed

+45
-31
lines changed

packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ export = {
155155
test.done();
156156
},
157157

158+
"multiple clusters with default capacity"(test: Test) {
159+
// GIVEN
160+
const stack = new cdk.Stack();
161+
const vpc = new ec2.Vpc(stack, 'MyVpc', {});
162+
163+
// WHEN
164+
for (let i = 0; i < 2; i++) {
165+
const cluster = new ecs.Cluster(stack, `EcsCluster${i}`, { vpc, });
166+
cluster.addCapacity('MyCapacity', {
167+
instanceType: new ec2.InstanceType('m3.medium'),
168+
});
169+
}
170+
171+
test.done();
172+
},
173+
158174
'lifecycle hook is automatically added'(test: Test) {
159175
// GIVEN
160176
const stack = new cdk.Stack();

packages/@aws-cdk/aws-ssm/lib/parameter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export class StringParameter extends ParameterBase implements IStringParameter {
248248
const stack = Stack.of(scope);
249249
const id = makeIdentityForImportedValue(parameterName);
250250
const exists = stack.node.tryFindChild(id) as IStringParameter;
251+
251252
if (exists) { return exists.stringValue; }
252253

253254
return this.fromStringParameterAttributes(stack, id, { parameterName, version }).stringValue;

packages/@aws-cdk/aws-ssm/test/test.parameter.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,17 @@ export = {
281281
}
282282
});
283283
test.done();
284-
}
284+
},
285+
286+
'can query actual SSM Parameter Names, multiple times'(test: Test) {
287+
// GIVEN
288+
const stack = new Stack();
285289

290+
// WHEN
291+
ssm.StringParameter.valueForStringParameter(stack, '/my/param/name');
292+
ssm.StringParameter.valueForStringParameter(stack, '/my/param/name');
293+
294+
test.done();
295+
},
286296
}
287297
};

packages/@aws-cdk/core/lib/construct.ts

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class ConstructNode {
129129
constructor(private readonly host: Construct, scope: IConstruct, id: string) {
130130
id = id || ''; // if undefined, convert to empty string
131131

132-
this.id = id;
132+
this.id = sanitizeId(id);
133133
this.scope = scope;
134134

135135
// We say that scope is required, but root scopes will bypass the type
@@ -146,9 +146,6 @@ export class ConstructNode {
146146
this.id = id;
147147
}
148148

149-
// escape any path separators so they don't wreck havoc
150-
this.id = this._escapePathSeparator(this.id);
151-
152149
if (Token.isUnresolved(id)) {
153150
throw new Error(`Cannot use tokens in construct ID: ${id}`);
154151
}
@@ -174,25 +171,13 @@ export class ConstructNode {
174171
}
175172

176173
/**
177-
* Return a descendant by path, or undefined
174+
* Return a direct child by id, or undefined
178175
*
179-
* Note that if the original ID of the construct you are looking for contained
180-
* a '/', then it would have been replaced by '--'.
181-
*
182-
* @param path Relative path of a direct or indirect child
183-
* @returns a child by path or undefined if not found.
176+
* @param id Identifier of direct child
177+
* @returns the child if found, or undefined
184178
*/
185-
public tryFindChild(path: string): IConstruct | undefined {
186-
if (path.startsWith(ConstructNode.PATH_SEP)) {
187-
throw new Error('Path must be relative');
188-
}
189-
const parts = path.split(ConstructNode.PATH_SEP);
190-
191-
let curr: IConstruct | undefined = this.host;
192-
while (curr != null && parts.length > 0) {
193-
curr = curr.node._children[parts.shift()!];
194-
}
195-
return curr;
179+
public tryFindChild(id: string): IConstruct | undefined {
180+
return this._children[sanitizeId(id)];
196181
}
197182

198183
/**
@@ -527,14 +512,6 @@ export class ConstructNode {
527512
this.invokedAspects.push(aspect);
528513
}
529514
}
530-
531-
/**
532-
* If the construct ID contains a path separator, it is replaced by double dash (`--`).
533-
*/
534-
private _escapePathSeparator(id: string) {
535-
if (!id) { return id; }
536-
return id.split(ConstructNode.PATH_SEP).join('--');
537-
}
538515
}
539516

540517
/**
@@ -715,3 +692,13 @@ function ignore(_x: any) {
715692
// Import this _after_ everything else to help node work the classes out in the correct order...
716693

717694
import { Reference } from './reference';
695+
696+
const PATH_SEP_REGEX = new RegExp(`${ConstructNode.PATH_SEP}`, 'g');
697+
698+
/**
699+
* Return a sanitized version of an arbitrary string, so it can be used as an ID
700+
*/
701+
function sanitizeId(id: string) {
702+
// Escape path seps as double dashes
703+
return id.replace(PATH_SEP_REGEX, '--');
704+
}

packages/@aws-cdk/core/lib/private/encoding.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class TokenString {
7878
/**
7979
* Quote a string for use in a regex
8080
*/
81-
function regexQuote(s: string) {
81+
export function regexQuote(s: string) {
8282
return s.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&");
8383
}
8484

0 commit comments

Comments
 (0)