From bf00d025095b7b7a7490a5a36be2ab7b27042283 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 16 Feb 2021 14:36:03 +0000 Subject: [PATCH] Don't fail on a concatenated ARN --- packages/@aws-cdk/core/lib/resource.ts | 5 ++- packages/@aws-cdk/core/lib/token.ts | 25 +++++++++++- .../core/test/cross-environment-token.test.ts | 39 +++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/core/lib/resource.ts b/packages/@aws-cdk/core/lib/resource.ts index a3a021715cde2..6445fe718c547 100644 --- a/packages/@aws-cdk/core/lib/resource.ts +++ b/packages/@aws-cdk/core/lib/resource.ts @@ -239,7 +239,10 @@ export abstract class Resource extends CoreConstruct implements IResource { * If the given value is NOT a Reference, just return a simple Lazy. */ function mimicReference(refSource: any, producer: IStringProducer): string { - const reference = Tokenization.reverse(refSource); + const reference = Tokenization.reverse(refSource, { + // If this is an ARN concatenation, just fail to extract a reference. + failConcat: false, + }); if (!Reference.isReference(reference)) { return Lazy.uncachedString(producer); } diff --git a/packages/@aws-cdk/core/lib/token.ts b/packages/@aws-cdk/core/lib/token.ts index f92a2560cac7c..e17227d0f8ef5 100644 --- a/packages/@aws-cdk/core/lib/token.ts +++ b/packages/@aws-cdk/core/lib/token.ts @@ -164,9 +164,16 @@ export class Tokenization { * * In case of a string, the string must not be a concatenation. */ - public static reverse(x: any): IResolvable | undefined { + public static reverse(x: any, options: ReverseOptions = {}): IResolvable | undefined { if (Tokenization.isResolvable(x)) { return x; } - if (typeof x === 'string') { return Tokenization.reverseCompleteString(x); } + if (typeof x === 'string') { + if (options.failConcat === false) { + // Handle this specially because reverseCompleteString might fail + const fragments = Tokenization.reverseString(x); + return fragments.length === 1 ? fragments.firstToken : undefined; + } + return Tokenization.reverseCompleteString(x); + } if (Array.isArray(x)) { return Tokenization.reverseList(x); } if (typeof x === 'number') { return Tokenization.reverseNumber(x); } return undefined; @@ -220,6 +227,20 @@ export class Tokenization { } } +/** + * Options for the 'reverse()' operation + */ +export interface ReverseOptions { + /** + * Fail if the given string is a concatenation + * + * If `false`, just return `undefined`. + * + * @default true + */ + readonly failConcat?: boolean; +} + /** * Options to the resolve() operation * diff --git a/packages/@aws-cdk/core/test/cross-environment-token.test.ts b/packages/@aws-cdk/core/test/cross-environment-token.test.ts index 9b59ac80f4729..a0d833996121c 100644 --- a/packages/@aws-cdk/core/test/cross-environment-token.test.ts +++ b/packages/@aws-cdk/core/test/cross-environment-token.test.ts @@ -269,6 +269,11 @@ test.each([undefined, 'SomeName'])('stack.exportValue() on name attributes with expect(templateA).toEqual(templateM); }); +test('can instantiate resource with constructed physicalname ARN', () => { + const stack = new Stack(); + new MyResourceWithConstructedArnAttribute(stack, 'Resource'); +}); + class MyResource extends Resource { public readonly arn: string; public readonly name: string; @@ -293,3 +298,37 @@ class MyResource extends Resource { }); } } + +/** + * Some resources build their own Arn attribute by constructing strings + * + * This will be because the L1 doesn't expose a `{ Fn::GetAtt: ['Arn'] }`. + * + * They won't be able to `exportValue()` it, but it shouldn't crash. + */ +class MyResourceWithConstructedArnAttribute extends Resource { + public readonly arn: string; + public readonly name: string; + + constructor(scope: Construct, id: string, physicalName?: string) { + super(scope, id, { physicalName }); + + const res = new CfnResource(this, 'Resource', { + type: 'My::Resource', + properties: { + resourceName: this.physicalName, + }, + }); + + this.name = this.getResourceNameAttribute(res.ref.toString()); + this.arn = this.getResourceArnAttribute(Stack.of(this).formatArn({ + resource: 'my-resource', + resourceName: res.ref.toString(), + service: 'myservice', + }), { + resource: 'my-resource', + resourceName: this.physicalName, + service: 'myservice', + }); + } +}