From df6b452b64942b2b4b4588673a67fb36559c550b Mon Sep 17 00:00:00 2001 From: tmokmss Date: Fri, 26 May 2023 12:57:59 +0900 Subject: [PATCH 1/9] fix(ssm): cannot import a ssm parameter when a name contains unresolved tokens --- .../aws-ssm/test/integ.import-parameter.ts | 37 ++++++++++++++++ packages/aws-cdk-lib/aws-ssm/lib/parameter.ts | 12 ++++-- .../aws-ssm/test/parameter.test.ts | 42 +++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts new file mode 100644 index 0000000000000..d15ecc9737e48 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts @@ -0,0 +1,37 @@ +import * as cdk from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'Import-SSM-Parameter'); + +const parameterName = 'import-parameter-test'; + +const param = new ssm.StringParameter(stack, 'StringParameter', { + stringValue: 'Initial parameter value', + parameterName, +}); + +// This will use a CfnParameter. +// We have to use an existing parameter to reference it with a concrete name, hence using a parameter managed by EC2. +const importedWithName = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithName', { + parameterName: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs', +}); + +// This will use a dynamic reference. +const importedWithToken = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithToken', { + simpleName: true, + parameterName: param.parameterName, +}); + +new cdk.CfnOutput(stack, 'ImportedWithNameOutput', { + value: importedWithName.stringValue, +}); + +new cdk.CfnOutput(stack, 'ImportedWithTokenOutput', { + value: importedWithToken.stringValue, +}); + +new IntegTest(app, 'cdk-integ-import-ssm-parameter', { + testCases: [stack], +}); diff --git a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts index 5bb4f6cc5d9b9..cd5f2a87ace67 100644 --- a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts +++ b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts @@ -473,9 +473,15 @@ export class StringParameter extends ParameterBase implements IStringParameter { const type = attrs.type ?? attrs.valueType ?? ParameterValueType.STRING; - const stringValue = attrs.version - ? new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString() - : new CfnParameter(scope, `${id}.Parameter`, { type: `AWS::SSM::Parameter::Value<${type}>`, default: attrs.parameterName }).valueAsString; + let stringValue: string; + if (attrs.version) { + stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString(); + } else if (Token.isUnresolved(attrs.parameterName)) { + // the default value of a CfnParameter can only contain strings, so we cannot use it when a parameter name contains tokens. + stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, attrs.parameterName).toString(); + } else { + stringValue = new CfnParameter(scope, `${id}.Parameter`, { type: `AWS::SSM::Parameter::Value<${type}>`, default: attrs.parameterName }).valueAsString; + } class Import extends ParameterBase { public readonly parameterName = attrs.parameterName; diff --git a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts index be0b07ba7538d..8f0e8f01cc410 100644 --- a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts +++ b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts @@ -963,3 +963,45 @@ test('fails if parameterName is undefined and simpleName is "false"', () => { // THEN expect(() => new ssm.StringParameter(stack, 'p', { simpleName: false, stringValue: 'foo' })).toThrow(/If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names/); }); + +test('When a parameter name contains unresolved tokens, use dynamic reference instead', () => { + // GIVEN + const app = new cdk.App(); + + const PARAM_NAME = 'service-token-param-name'; + + const stackA = new cdk.Stack(app, 'StackA'); + new cdk.CfnOutput(stackA, 'OutputParamName', { + exportName: PARAM_NAME, + value: 'service-token', + }); + + const stackB = new cdk.Stack(app, 'StackB'); + stackB.addDependency(stackA); + + // WHEN + const param = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { + simpleName: true, + parameterName: cdk.Fn.importValue(PARAM_NAME), + }); + new cdk.CfnOutput(stackB, 'OutputParamValue', { + value: param.stringValue, + }); + + // THEN + const template = Template.fromStack(stackB); + template.hasOutput('OutputParamValue', { + Value: { + 'Fn::Join': [ + '', + [ + '{{resolve:ssm:', + { + 'Fn::ImportValue': 'service-token-param-name', + }, + '}}', + ], + ], + }, + }); +}); From 09ae020efaebc987df0c0f1925a9d87488525c02 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Fri, 26 May 2023 12:58:18 +0900 Subject: [PATCH 2/9] integ --- .../Import-SSM-Parameter.assets.json | 19 ++ .../Import-SSM-Parameter.template.json | 71 ++++++++ .../cdk.out | 1 + ...efaultTestDeployAssert2A3D6843.assets.json | 19 ++ ...aultTestDeployAssert2A3D6843.template.json | 36 ++++ .../integ.json | 12 ++ .../manifest.json | 129 ++++++++++++++ .../tree.json | 167 ++++++++++++++++++ 8 files changed, 454 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json new file mode 100644 index 0000000000000..97b600e3d9f72 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json @@ -0,0 +1,19 @@ +{ + "version": "32.0.0", + "files": { + "b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f": { + "source": { + "path": "Import-SSM-Parameter.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json new file mode 100644 index 0000000000000..7a82ae071eb55 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json @@ -0,0 +1,71 @@ +{ + "Resources": { + "StringParameter472EED0E": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "Initial parameter value", + "Name": "import-parameter-test" + } + } + }, + "Parameters": { + "ImportedWithNameParameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs" + }, + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Outputs": { + "ImportedWithNameOutput": { + "Value": { + "Ref": "ImportedWithNameParameter" + } + }, + "ImportedWithTokenOutput": { + "Value": { + "Fn::Join": [ + "", + [ + "{{resolve:ssm:", + { + "Ref": "StringParameter472EED0E" + }, + "}}" + ] + ] + } + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdk.out new file mode 100644 index 0000000000000..f0b901e7c06e5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"32.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets.json new file mode 100644 index 0000000000000..407b910b761ad --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets.json @@ -0,0 +1,19 @@ +{ + "version": "32.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/integ.json new file mode 100644 index 0000000000000..e0d4a4a12411a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "32.0.0", + "testCases": { + "cdk-integ-import-ssm-parameter/DefaultTest": { + "stacks": [ + "Import-SSM-Parameter" + ], + "assertionStack": "cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert", + "assertionStackName": "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json new file mode 100644 index 0000000000000..98da8a94aa92c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json @@ -0,0 +1,129 @@ +{ + "version": "32.0.0", + "artifacts": { + "Import-SSM-Parameter.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "Import-SSM-Parameter.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "Import-SSM-Parameter": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "Import-SSM-Parameter.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "Import-SSM-Parameter.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "Import-SSM-Parameter.assets" + ], + "metadata": { + "/Import-SSM-Parameter/StringParameter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StringParameter472EED0E" + } + ], + "/Import-SSM-Parameter/ImportedWithName.Parameter": [ + { + "type": "aws:cdk:logicalId", + "data": "ImportedWithNameParameter" + } + ], + "/Import-SSM-Parameter/ImportedWithNameOutput": [ + { + "type": "aws:cdk:logicalId", + "data": "ImportedWithNameOutput" + } + ], + "/Import-SSM-Parameter/ImportedWithTokenOutput": [ + { + "type": "aws:cdk:logicalId", + "data": "ImportedWithTokenOutput" + } + ], + "/Import-SSM-Parameter/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/Import-SSM-Parameter/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "Import-SSM-Parameter" + }, + "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "cdkintegimportssmparameterDefaultTestDeployAssert2A3D6843.assets" + ], + "metadata": { + "/cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json new file mode 100644 index 0000000000000..8b99851af9f7c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json @@ -0,0 +1,167 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Import-SSM-Parameter": { + "id": "Import-SSM-Parameter", + "path": "Import-SSM-Parameter", + "children": { + "StringParameter": { + "id": "StringParameter", + "path": "Import-SSM-Parameter/StringParameter", + "children": { + "Resource": { + "id": "Resource", + "path": "Import-SSM-Parameter/StringParameter/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SSM::Parameter", + "aws:cdk:cloudformation:props": { + "type": "String", + "value": "Initial parameter value", + "name": "import-parameter-test" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ssm.CfnParameter", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_ssm.StringParameter", + "version": "0.0.0" + } + }, + "ImportedWithName.Parameter": { + "id": "ImportedWithName.Parameter", + "path": "Import-SSM-Parameter/ImportedWithName.Parameter", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "ImportedWithName": { + "id": "ImportedWithName", + "path": "Import-SSM-Parameter/ImportedWithName", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ImportedWithToken": { + "id": "ImportedWithToken", + "path": "Import-SSM-Parameter/ImportedWithToken", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ImportedWithNameOutput": { + "id": "ImportedWithNameOutput", + "path": "Import-SSM-Parameter/ImportedWithNameOutput", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "ImportedWithTokenOutput": { + "id": "ImportedWithTokenOutput", + "path": "Import-SSM-Parameter/ImportedWithTokenOutput", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "Import-SSM-Parameter/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "Import-SSM-Parameter/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "cdk-integ-import-ssm-parameter": { + "id": "cdk-integ-import-ssm-parameter", + "path": "cdk-integ-import-ssm-parameter", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-integ-import-ssm-parameter/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-integ-import-ssm-parameter/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-integ-import-ssm-parameter/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.26" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file From a3cb011c19cfdfa9cee7df641cc6a8617bafc1e2 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Fri, 26 May 2023 13:09:30 +0900 Subject: [PATCH 3/9] Update parameter.test.ts --- .../aws-cdk-lib/aws-ssm/test/parameter.test.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts index 8f0e8f01cc410..bb4d8e2e67494 100644 --- a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts +++ b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts @@ -968,24 +968,19 @@ test('When a parameter name contains unresolved tokens, use dynamic reference in // GIVEN const app = new cdk.App(); - const PARAM_NAME = 'service-token-param-name'; - const stackA = new cdk.Stack(app, 'StackA'); - new cdk.CfnOutput(stackA, 'OutputParamName', { - exportName: PARAM_NAME, - value: 'service-token', + const paramA = new ssm.StringParameter(stackA, 'StringParameter', { + stringValue: 'Initial parameter value', }); - const stackB = new cdk.Stack(app, 'StackB'); - stackB.addDependency(stackA); // WHEN - const param = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { + const paramB = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { simpleName: true, - parameterName: cdk.Fn.importValue(PARAM_NAME), + parameterName: paramA.parameterName, }); new cdk.CfnOutput(stackB, 'OutputParamValue', { - value: param.stringValue, + value: paramB.stringValue, }); // THEN @@ -997,7 +992,7 @@ test('When a parameter name contains unresolved tokens, use dynamic reference in [ '{{resolve:ssm:', { - 'Fn::ImportValue': 'service-token-param-name', + 'Fn::ImportValue': 'StackA:ExportsOutputRefStringParameter472EED0ECEFE290A', }, '}}', ], From 65e19cb6d038cfa03d14b8028382d8f2339cd1ec Mon Sep 17 00:00:00 2001 From: tmokmss Date: Tue, 30 May 2023 23:40:30 +0900 Subject: [PATCH 4/9] add a test case for string tokens --- .../aws-ssm/test/parameter.test.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts index bb4d8e2e67494..2124b085672e0 100644 --- a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts +++ b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts @@ -964,7 +964,7 @@ test('fails if parameterName is undefined and simpleName is "false"', () => { expect(() => new ssm.StringParameter(stack, 'p', { simpleName: false, stringValue: 'foo' })).toThrow(/If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names/); }); -test('When a parameter name contains unresolved tokens, use dynamic reference instead', () => { +test('When a parameter name contains resolvable tokens, use dynamic reference instead', () => { // GIVEN const app = new cdk.App(); @@ -1000,3 +1000,36 @@ test('When a parameter name contains unresolved tokens, use dynamic reference in }, }); }); + +test('When a parameter name contains unresolved tokens, use parameter', () => { + // GIVEN + const app = new cdk.App(); + + const stackA = new cdk.Stack(app, 'StackA', { env: { region: 'us-east-1', account: '123456789012' } }); + const role = new iam.Role(stackA, 'Role', { + roleName: cdk.PhysicalName.GENERATE_IF_NEEDED, + assumedBy: new iam.AccountRootPrincipal(), + }); + const stackB = new cdk.Stack(app, 'StackB', { env: { region: 'us-east-2', account: '123456789012' } }); + + // WHEN + const paramB = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { + simpleName: true, + parameterName: role.roleName, + }); + new cdk.CfnOutput(stackB, 'OutputParamValue', { + value: paramB.stringValue, + }); + + // THEN + const template = Template.fromStack(stackB); + template.hasOutput('OutputParamValue', { + Value: { + Ref: 'importstringparamParameter', + }, + }); + template.hasParameter('importstringparamParameter', { + Type: 'AWS::SSM::Parameter::Value', + Default: 'stackastackarole438bb295b7bd8838d703', + }); +}); From 861735d970d3d5a9e58260f20f3f4d00e2b7d398 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Wed, 31 May 2023 12:59:45 +0900 Subject: [PATCH 5/9] add test --- .../aws-ssm/test/parameter.test.ts | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts index 2124b085672e0..282a6fe1a713d 100644 --- a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts +++ b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts @@ -964,7 +964,43 @@ test('fails if parameterName is undefined and simpleName is "false"', () => { expect(() => new ssm.StringParameter(stack, 'p', { simpleName: false, stringValue: 'foo' })).toThrow(/If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names/); }); -test('When a parameter name contains resolvable tokens, use dynamic reference instead', () => { +test('When a parameter name contains tokens, use dynamic reference instead', () => { + // GIVEN + const app = new cdk.App(); + + const stack = new cdk.Stack(app, 'Stack'); + const paramA = new ssm.StringParameter(stack, 'StringParameter', { + stringValue: 'Initial parameter value', + }); + + // WHEN + const paramB = ssm.StringParameter.fromStringParameterAttributes(stack, 'import-string-param', { + simpleName: true, + parameterName: paramA.parameterName, + }); + new cdk.CfnOutput(stack, 'OutputParamValue', { + value: paramB.stringValue, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasOutput('OutputParamValue', { + Value: { + 'Fn::Join': [ + '', + [ + '{{resolve:ssm:', + { + Ref: 'StringParameter472EED0E', + }, + '}}', + ], + ], + }, + }); +}); + +test('When a parameter name contains tokens, use dynamic reference instead (cross-stack)', () => { // GIVEN const app = new cdk.App(); @@ -1001,7 +1037,7 @@ test('When a parameter name contains resolvable tokens, use dynamic reference in }); }); -test('When a parameter name contains unresolved tokens, use parameter', () => { +test('When a parameter name contains tokens that can be resolved to string, use parameter', () => { // GIVEN const app = new cdk.App(); From 0d73f842b0965e631c356636dcfbba18388909df Mon Sep 17 00:00:00 2001 From: tmokmss Date: Wed, 31 May 2023 12:59:57 +0900 Subject: [PATCH 6/9] not working solution --- packages/aws-cdk-lib/aws-ssm/lib/parameter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts index cd5f2a87ace67..0466b7d671a64 100644 --- a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts +++ b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts @@ -9,6 +9,7 @@ import { ContextProvider, Fn, IResource, Resource, Stack, Token, Tokenization, } from '../../core'; +import { TokenString } from '../../core/lib/private/encoding'; /** * An SSM Parameter reference. @@ -476,7 +477,8 @@ export class StringParameter extends ParameterBase implements IStringParameter { let stringValue: string; if (attrs.version) { stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString(); - } else if (Token.isUnresolved(attrs.parameterName)) { + // } else if (typeof Stack.of(scope).resolve(attrs.parameterName) != 'string') { + } else if (TokenString.forString(attrs.parameterName).test()) { // the default value of a CfnParameter can only contain strings, so we cannot use it when a parameter name contains tokens. stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, attrs.parameterName).toString(); } else { From 54bcad2b172946e7879e0296b19c656a4716eca7 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Wed, 31 May 2023 13:00:18 +0900 Subject: [PATCH 7/9] fix --- packages/aws-cdk-lib/aws-ssm/lib/parameter.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts index 0466b7d671a64..21e92b1dfa82a 100644 --- a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts +++ b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts @@ -9,7 +9,6 @@ import { ContextProvider, Fn, IResource, Resource, Stack, Token, Tokenization, } from '../../core'; -import { TokenString } from '../../core/lib/private/encoding'; /** * An SSM Parameter reference. @@ -477,8 +476,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { let stringValue: string; if (attrs.version) { stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString(); - // } else if (typeof Stack.of(scope).resolve(attrs.parameterName) != 'string') { - } else if (TokenString.forString(attrs.parameterName).test()) { + } else if (typeof Stack.of(scope).resolve(attrs.parameterName) != 'string') { // the default value of a CfnParameter can only contain strings, so we cannot use it when a parameter name contains tokens. stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, attrs.parameterName).toString(); } else { From 45f9ebcd69284cf6895b6a12ecf4d82ce411b6b9 Mon Sep 17 00:00:00 2001 From: tmokmss Date: Fri, 30 Jun 2023 16:27:50 +0900 Subject: [PATCH 8/9] use dynamic reference only with explicit Fn use. Add forceDynamicReference flag. --- .../Import-SSM-Parameter.assets.json | 4 +- .../Import-SSM-Parameter.template.json | 16 ++++- .../manifest.json | 12 +++- .../tree.json | 28 +++++++-- .../aws-ssm/test/integ.import-parameter.ts | 21 +++++-- packages/aws-cdk-lib/aws-ssm/lib/parameter.ts | 13 +++- .../aws-ssm/test/parameter.test.ts | 62 ++++--------------- packages/aws-cdk-lib/core/lib/cfn-fn.ts | 13 ++++ 8 files changed, 102 insertions(+), 67 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json index 97b600e3d9f72..2f05db545aadc 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.assets.json @@ -1,7 +1,7 @@ { "version": "32.0.0", "files": { - "b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f": { + "0c9f637062451a2002409e9c30b657f39990631000a05a12bef7fcdb73ec5332": { "source": { "path": "Import-SSM-Parameter.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f.json", + "objectKey": "0c9f637062451a2002409e9c30b657f39990631000a05a12bef7fcdb73ec5332.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json index 7a82ae071eb55..e40c93c965b57 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/Import-SSM-Parameter.template.json @@ -26,7 +26,21 @@ "Ref": "ImportedWithNameParameter" } }, - "ImportedWithTokenOutput": { + "ImportedWithIntrinsicOutput": { + "Value": { + "Fn::Join": [ + "", + [ + "{{resolve:ssm:", + { + "Ref": "StringParameter472EED0E" + }, + "}}" + ] + ] + } + }, + "ImportedWithForceFlagOutput": { "Value": { "Fn::Join": [ "", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json index 98da8a94aa92c..e0bf437490464 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/manifest.json @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b7085cd29c0621b299812d40aa9d08d41f1d4981782299bcf9e99c100430396f.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0c9f637062451a2002409e9c30b657f39990631000a05a12bef7fcdb73ec5332.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -51,10 +51,16 @@ "data": "ImportedWithNameOutput" } ], - "/Import-SSM-Parameter/ImportedWithTokenOutput": [ + "/Import-SSM-Parameter/ImportedWithIntrinsicOutput": [ { "type": "aws:cdk:logicalId", - "data": "ImportedWithTokenOutput" + "data": "ImportedWithIntrinsicOutput" + } + ], + "/Import-SSM-Parameter/ImportedWithForceFlagOutput": [ + { + "type": "aws:cdk:logicalId", + "data": "ImportedWithForceFlagOutput" } ], "/Import-SSM-Parameter/BootstrapVersion": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json index 8b99851af9f7c..e372294ce0e5b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.js.snapshot/tree.json @@ -50,9 +50,17 @@ "version": "0.0.0" } }, - "ImportedWithToken": { - "id": "ImportedWithToken", - "path": "Import-SSM-Parameter/ImportedWithToken", + "ImportedWithIntrinsic": { + "id": "ImportedWithIntrinsic", + "path": "Import-SSM-Parameter/ImportedWithIntrinsic", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "ImportedWithForceFlag": { + "id": "ImportedWithForceFlag", + "path": "Import-SSM-Parameter/ImportedWithForceFlag", "constructInfo": { "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" @@ -66,9 +74,17 @@ "version": "0.0.0" } }, - "ImportedWithTokenOutput": { - "id": "ImportedWithTokenOutput", - "path": "Import-SSM-Parameter/ImportedWithTokenOutput", + "ImportedWithIntrinsicOutput": { + "id": "ImportedWithIntrinsicOutput", + "path": "Import-SSM-Parameter/ImportedWithIntrinsicOutput", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "ImportedWithForceFlagOutput": { + "id": "ImportedWithForceFlagOutput", + "path": "Import-SSM-Parameter/ImportedWithForceFlagOutput", "constructInfo": { "fqn": "aws-cdk-lib.CfnOutput", "version": "0.0.0" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts index d15ecc9737e48..20e6cc317dce2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-ssm/test/integ.import-parameter.ts @@ -13,23 +13,34 @@ const param = new ssm.StringParameter(stack, 'StringParameter', { }); // This will use a CfnParameter. -// We have to use an existing parameter to reference it with a concrete name, hence using a parameter managed by EC2. +// We have to use an existing parameter to reference it with a concrete name, so using a parameter managed by EC2. const importedWithName = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithName', { parameterName: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs', }); -// This will use a dynamic reference. -const importedWithToken = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithToken', { +// This will use a dynamic reference (deduced). +const importedWithIntrinsic = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithIntrinsic', { + simpleName: true, + parameterName: cdk.Fn.ref((param.node.defaultChild as cdk.CfnResource).logicalId), +}); + +// This will use a dynamic reference (forced). +const importedWithForceFlag = ssm.StringParameter.fromStringParameterAttributes(stack, 'ImportedWithForceFlag', { simpleName: true, parameterName: param.parameterName, + forceDynamicReference: true, }); new cdk.CfnOutput(stack, 'ImportedWithNameOutput', { value: importedWithName.stringValue, }); -new cdk.CfnOutput(stack, 'ImportedWithTokenOutput', { - value: importedWithToken.stringValue, +new cdk.CfnOutput(stack, 'ImportedWithIntrinsicOutput', { + value: importedWithIntrinsic.stringValue, +}); + +new cdk.CfnOutput(stack, 'ImportedWithForceFlagOutput', { + value: importedWithForceFlag.stringValue, }); new IntegTest(app, 'cdk-integ-import-ssm-parameter', { diff --git a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts index 21e92b1dfa82a..e5f65f01333a1 100644 --- a/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts +++ b/packages/aws-cdk-lib/aws-ssm/lib/parameter.ts @@ -389,6 +389,14 @@ export interface StringParameterAttributes extends CommonStringParameterAttribut * @default ParameterValueType.STRING */ readonly valueType?: ParameterValueType; + + /** + * Use a dynamic reference as the representation in CloudFormation template level. + * By default, CDK tries to deduce an appropriate representation based on the parameter value (a CfnParameter or a dynamic reference). Use this flag to override the representation when it does not work. + * + * @default false + */ + readonly forceDynamicReference?: boolean; } /** @@ -472,11 +480,14 @@ export class StringParameter extends ParameterBase implements IStringParameter { } const type = attrs.type ?? attrs.valueType ?? ParameterValueType.STRING; + const forceDynamicReference = attrs.forceDynamicReference ?? false; let stringValue: string; if (attrs.version) { stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString(); - } else if (typeof Stack.of(scope).resolve(attrs.parameterName) != 'string') { + } else if (forceDynamicReference) { + stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, attrs.parameterName).toString(); + } else if (Token.isUnresolved(attrs.parameterName) && Fn._isFnBase(Tokenization.reverseString(attrs.parameterName).firstToken)) { // the default value of a CfnParameter can only contain strings, so we cannot use it when a parameter name contains tokens. stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SSM, attrs.parameterName).toString(); } else { diff --git a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts index 282a6fe1a713d..8e0c20abd426d 100644 --- a/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts +++ b/packages/aws-cdk-lib/aws-ssm/test/parameter.test.ts @@ -964,22 +964,19 @@ test('fails if parameterName is undefined and simpleName is "false"', () => { expect(() => new ssm.StringParameter(stack, 'p', { simpleName: false, stringValue: 'foo' })).toThrow(/If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names/); }); -test('When a parameter name contains tokens, use dynamic reference instead', () => { +test('When a parameter name contains a CFn intrinsic, use dynamic reference instead', () => { // GIVEN const app = new cdk.App(); const stack = new cdk.Stack(app, 'Stack'); - const paramA = new ssm.StringParameter(stack, 'StringParameter', { - stringValue: 'Initial parameter value', - }); // WHEN - const paramB = ssm.StringParameter.fromStringParameterAttributes(stack, 'import-string-param', { + const param = ssm.StringParameter.fromStringParameterAttributes(stack, 'import-string-param1', { simpleName: true, - parameterName: paramA.parameterName, + parameterName: cdk.Fn.importValue('some-exported-value'), }); new cdk.CfnOutput(stack, 'OutputParamValue', { - value: paramB.stringValue, + value: param.stringValue, }); // THEN @@ -991,7 +988,7 @@ test('When a parameter name contains tokens, use dynamic reference instead', () [ '{{resolve:ssm:', { - Ref: 'StringParameter472EED0E', + 'Fn::ImportValue': 'some-exported-value', }, '}}', ], @@ -1000,27 +997,27 @@ test('When a parameter name contains tokens, use dynamic reference instead', () }); }); -test('When a parameter name contains tokens, use dynamic reference instead (cross-stack)', () => { +test('When a parameter representation overridden, use dynamic reference', () => { // GIVEN const app = new cdk.App(); - const stackA = new cdk.Stack(app, 'StackA'); - const paramA = new ssm.StringParameter(stackA, 'StringParameter', { + const stack = new cdk.Stack(app, 'Stack'); + const paramA = new ssm.StringParameter(stack, 'StringParameter', { stringValue: 'Initial parameter value', }); - const stackB = new cdk.Stack(app, 'StackB'); // WHEN - const paramB = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { + const paramB = ssm.StringParameter.fromStringParameterAttributes(stack, 'import-string-param', { simpleName: true, parameterName: paramA.parameterName, + forceDynamicReference: true, }); - new cdk.CfnOutput(stackB, 'OutputParamValue', { + new cdk.CfnOutput(stack, 'OutputParamValue', { value: paramB.stringValue, }); // THEN - const template = Template.fromStack(stackB); + const template = Template.fromStack(stack); template.hasOutput('OutputParamValue', { Value: { 'Fn::Join': [ @@ -1028,7 +1025,7 @@ test('When a parameter name contains tokens, use dynamic reference instead (cros [ '{{resolve:ssm:', { - 'Fn::ImportValue': 'StackA:ExportsOutputRefStringParameter472EED0ECEFE290A', + Ref: 'StringParameter472EED0E', }, '}}', ], @@ -1036,36 +1033,3 @@ test('When a parameter name contains tokens, use dynamic reference instead (cros }, }); }); - -test('When a parameter name contains tokens that can be resolved to string, use parameter', () => { - // GIVEN - const app = new cdk.App(); - - const stackA = new cdk.Stack(app, 'StackA', { env: { region: 'us-east-1', account: '123456789012' } }); - const role = new iam.Role(stackA, 'Role', { - roleName: cdk.PhysicalName.GENERATE_IF_NEEDED, - assumedBy: new iam.AccountRootPrincipal(), - }); - const stackB = new cdk.Stack(app, 'StackB', { env: { region: 'us-east-2', account: '123456789012' } }); - - // WHEN - const paramB = ssm.StringParameter.fromStringParameterAttributes(stackB, 'import-string-param', { - simpleName: true, - parameterName: role.roleName, - }); - new cdk.CfnOutput(stackB, 'OutputParamValue', { - value: paramB.stringValue, - }); - - // THEN - const template = Template.fromStack(stackB); - template.hasOutput('OutputParamValue', { - Value: { - Ref: 'importstringparamParameter', - }, - }); - template.hasParameter('importstringparamParameter', { - Type: 'AWS::SSM::Parameter::Value', - Default: 'stackastackarole438bb295b7bd8838d703', - }); -}); diff --git a/packages/aws-cdk-lib/core/lib/cfn-fn.ts b/packages/aws-cdk-lib/core/lib/cfn-fn.ts index 889af6c465aec..79f10fc520e2c 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-fn.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-fn.ts @@ -444,15 +444,28 @@ export class Fn { return Token.asNumber(new FnLength(array)); } + /** + * Test whether the given construct extends FnBase class. + * + * @internal + */ + public static _isFnBase(x: any): x is FnBase { + return x !== null && typeof(x) === 'object' && FN_BASE_SYMBOL in x; + } + private constructor() { } } +const FN_BASE_SYMBOL = Symbol.for('@aws-cdk/core.CfnFnBase'); + /** * Base class for tokens that represent CloudFormation intrinsic functions. */ class FnBase extends Intrinsic { constructor(name: string, value: any) { super({ [name]: value }); + + Object.defineProperty(this, FN_BASE_SYMBOL, { value: true }); } } From 310151ee4feec000fd519d046c8533c97a0fe87c Mon Sep 17 00:00:00 2001 From: tmokmss Date: Fri, 30 Jun 2023 18:20:53 +0900 Subject: [PATCH 9/9] Update cfn-fn.ts --- packages/aws-cdk-lib/core/lib/cfn-fn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/core/lib/cfn-fn.ts b/packages/aws-cdk-lib/core/lib/cfn-fn.ts index 79f10fc520e2c..730e3fe9f6708 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-fn.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-fn.ts @@ -445,7 +445,7 @@ export class Fn { } /** - * Test whether the given construct extends FnBase class. + * Test whether the given object extends FnBase class. * * @internal */