diff --git a/packages/@aws-cdk/aws-ssm/lib/index.ts b/packages/@aws-cdk/aws-ssm/lib/index.ts index e1ed1ca33b93a..dd7718ab70fd8 100644 --- a/packages/@aws-cdk/aws-ssm/lib/index.ts +++ b/packages/@aws-cdk/aws-ssm/lib/index.ts @@ -1,5 +1,4 @@ export * from './parameter'; -export * from './parameter-store-string'; // AWS::SSM CloudFormation Resources: export * from './ssm.generated'; diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts b/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts deleted file mode 100644 index 0a17bba387c4f..0000000000000 --- a/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts +++ /dev/null @@ -1,90 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); - -/** - * Properties for a ParameterStoreValue - */ -export interface ParameterStoreStringProps { - /** - * The name of the parameter store value - */ - readonly parameterName: string; - - /** - * The version number of the value you wish to retrieve. - * - * @default The latest version will be retrieved. - */ - readonly version?: number; -} - -/** - * References a public value in AWS Systems Manager Parameter Store - * - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html - */ -export class ParameterStoreString extends cdk.Construct { - public readonly stringValue: string; - - constructor(scope: cdk.Construct, id: string, props: ParameterStoreStringProps) { - super(scope, id); - - // If we don't validate this here it will lead to a very unclear - // error message in CloudFormation, so better do it. - if (!props.parameterName) { - throw new Error('ParameterStoreString: parameterName cannot be empty'); - } - - // We use a different inner construct depend on whether we want the latest - // or a specific version. - // - // * Latest - generate a Parameter and reference that. - // * Specific - use a Dynamic Reference. - if (props.version === undefined) { - // Construct/get a singleton parameter under the stack - const param = new cdk.CfnParameter(this, 'Parameter', { - type: 'AWS::SSM::Parameter::Value', - default: props.parameterName - }); - this.stringValue = param.valueAsString; - } else { - // Use a dynamic reference - const dynRef = new cdk.CfnDynamicReference(cdk.CfnDynamicReferenceService.Ssm, `${props.parameterName}:${props.version}`); - this.stringValue = dynRef.toString(); - } - } -} - -/** - * Properties for a ParameterStoreValue - */ -export interface ParameterStoreSecureStringProps { - /** - * The name of the parameter store secure string value - */ - readonly parameterName: string; - - /** - * The version number of the value you wish to retrieve. - */ - readonly version: number; -} - -/** - * References a secret value in AWS Systems Manager Parameter Store - * - * It is not possible to retrieve the "latest" value of a secret. - * Use Secrets Manager if you need that ability. - * - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html - */ -export class ParameterStoreSecureString extends cdk.CfnDynamicReference { - constructor(props: ParameterStoreSecureStringProps) { - super(cdk.CfnDynamicReferenceService.SsmSecure, `${props.parameterName}:${props.version}`); - - // If we don't validate this here it will lead to a very unclear - // error message in CloudFormation, so better do it. - if (!props.parameterName) { - throw new Error('ParameterStoreSecureString: parameterName cannot be empty'); - } - } -} diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index 12f6cab9541a9..e79729cf26120 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -1,5 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); -import { CfnDynamicReference, CfnDynamicReferenceService, Construct, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; +import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, Construct, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; import ssm = require('./ssm.generated'); /** @@ -86,9 +86,9 @@ export interface ParameterOptions { /** * The name of the parameter. * - * @default a name will be generated by CloudFormation + * @default - a name will be generated by CloudFormation */ - readonly name?: string; + readonly parameterName?: string; } /** @@ -145,8 +145,35 @@ abstract class ParameterBase extends Resource implements IParameter { } const STRING_PARAM_TYPE = 'String'; +const SECURE_STRING_PARAM_TYPE = 'SecureString'; const STRINGLIST_PARAM_TYPE = 'StringList'; +export interface StringParameterAttributes { + /** + * The name of the parameter store value + */ + readonly parameterName: string; + + /** + * The version number of the value you wish to retrieve. + * + * @default The latest version will be retrieved. + */ + readonly version?: number; +} + +export interface SecureStringParameterAttributes { + /** + * The name of the parameter store value + */ + readonly parameterName: string; + + /** + * The version number of the value you wish to retrieve. This is required for secure strings. + */ + readonly version: number; +} + /** * Creates a new String SSM Parameter. * @resource AWS::SSM::Parameter @@ -154,13 +181,43 @@ const STRINGLIST_PARAM_TYPE = 'StringList'; export class StringParameter extends ParameterBase implements IStringParameter { /** - * Imports an external string parameter. + * Imports an external string parameter by name. */ public static fromStringParameterName(scope: Construct, id: string, stringParameterName: string): IStringParameter { + return this.fromStringParameterAttributes(scope, id, { parameterName: stringParameterName }); + } + + /** + * Imports an external string parameter with name and optional version. + */ + public static fromStringParameterAttributes(scope: Construct, id: string, attrs: StringParameterAttributes): IStringParameter { + if (!attrs.parameterName) { + throw new Error(`parameterName cannot be an empty string`); + } + + const stringValue = attrs.version + ? new CfnDynamicReference(CfnDynamicReferenceService.Ssm, `${attrs.parameterName}:${attrs.version}`).toString() + : new CfnParameter(scope, `${id}.Parameter`, { type: 'AWS::SSM::Parameter::Value', default: attrs.parameterName }).valueAsString; + class Import extends ParameterBase { - public readonly parameterName = stringParameterName; + public readonly parameterName = attrs.parameterName; public readonly parameterType = STRING_PARAM_TYPE; - public readonly stringValue = new CfnDynamicReference(CfnDynamicReferenceService.Ssm, stringParameterName).toString(); + public readonly stringValue = stringValue; + } + + return new Import(scope, id); + } + + /** + * Imports a secure string parameter from the SSM parameter store. + */ + public static fromSecureStringParameterAttributes(scope: Construct, id: string, attrs: SecureStringParameterAttributes): IStringParameter { + const stringValue = new CfnDynamicReference(CfnDynamicReferenceService.SsmSecure, `${attrs.parameterName}:${attrs.version}`).toString(); + + class Import extends ParameterBase { + public readonly parameterName = attrs.parameterName; + public readonly parameterType = SECURE_STRING_PARAM_TYPE; + public readonly stringValue = stringValue; } return new Import(scope, id); @@ -180,7 +237,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { const resource = new ssm.CfnParameter(this, 'Resource', { allowedPattern: props.allowedPattern, description: props.description, - name: props.name, + name: props.parameterName, type: STRING_PARAM_TYPE, value: props.stringValue, }); @@ -228,7 +285,7 @@ export class StringListParameter extends ParameterBase implements IStringListPar const resource = new ssm.CfnParameter(this, 'Resource', { allowedPattern: props.allowedPattern, description: props.description, - name: props.name, + name: props.parameterName, type: STRINGLIST_PARAM_TYPE, value: props.stringListValue.join(','), }); diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.expected.json b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.expected.json index 85a93ef6c2476..1d3d7baba28e0 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.expected.json +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.expected.json @@ -13,17 +13,22 @@ }, { "Parameters": { - "MyValueParameterCCC58B13": { + "MyValueParameter": { "Type": "AWS::SSM::Parameter::Value", "Default": "/My/Public/Parameter" } }, + "Resources": { + "Dummy": { + "Type": "AWS::SNS::Topic" + } + }, "Outputs": { "TheValue": { "Value": { - "Ref": "MyValueParameterCCC58B13" + "Ref": "MyValueParameter" } } } } -] +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts index f9e79e408af4e..9066582ff5513 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts @@ -7,7 +7,7 @@ class CreatingStack extends cdk.Stack { super(scope, id); new ssm.StringParameter(this, 'String', { - name: '/My/Public/Parameter', + parameterName: '/My/Public/Parameter', stringValue: 'abcdef' }); } @@ -20,19 +20,20 @@ class UsingStack extends cdk.Stack { /// !show // Retrieve the latest value of the non-secret parameter // with name "/My/String/Parameter". - const stringValue = new ssm.ParameterStoreString(this, 'MyValue', { + const stringValue = ssm.StringParameter.fromStringParameterAttributes(this, 'MyValue', { parameterName: '/My/Public/Parameter', // 'version' can be specified but is optional. }).stringValue; // Retrieve a specific version of the secret (SecureString) parameter. // 'version' is always required. - const secretValue = new ssm.ParameterStoreSecureString({ + const secretValue = ssm.StringParameter.fromSecureStringParameterAttributes(this, 'MySecureValue', { parameterName: '/My/Secret/Parameter', version: 5 }); /// !hide + new cdk.CfnResource(this, 'Dummy', { type: 'AWS::SNS::Topic' }); new cdk.CfnOutput(this, 'TheValue', { value: stringValue }); // Cannot be provisioned so cannot be actually used diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts index 05b1fb9759c35..50aba31e32e6b 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts @@ -9,7 +9,7 @@ export = { const stack = new cdk.Stack(); // WHEN - const ref = new ssm.ParameterStoreString(stack, 'Ref', { + const ref = ssm.StringParameter.fromStringParameterAttributes(stack, 'Ref', { parameterName: '/some/key', version: 123 }); @@ -25,21 +25,21 @@ export = { const stack = new cdk.Stack(); // WHEN - const ref = new ssm.ParameterStoreString(stack, 'Ref', { + const ref = ssm.StringParameter.fromStringParameterAttributes(stack, 'Ref', { parameterName: '/some/key', }); // THEN expect(stack).toMatch({ Parameters: { - RefParameter407AF5C8: { + RefParameter: { Type: "AWS::SSM::Parameter::Value", Default: "/some/key" } } }); - test.deepEqual(stack.resolve(ref.stringValue), { Ref: 'RefParameter407AF5C8' }); + test.deepEqual(stack.resolve(ref.stringValue), { Ref: 'RefParameter' }); test.done(); }, @@ -49,10 +49,10 @@ export = { const stack = new cdk.Stack(); // WHEN - const ref = new ssm.ParameterStoreSecureString({ + const ref = ssm.StringParameter.fromSecureStringParameterAttributes(stack, 'Ref', { parameterName: '/some/key', version: 123 - }); + }).stringValue; // THEN test.equal(stack.resolve(ref), '{{resolve:ssm-secure:/some/key:123}}'); @@ -66,10 +66,10 @@ export = { // WHEN test.throws(() => { - new ssm.ParameterStoreString(stack, 'Ref', { + ssm.StringParameter.fromStringParameterAttributes(stack, 'Ref', { parameterName: '', }); - }, /parameterName cannot be empty/); + }, /parameterName cannot be an empty string/); test.done(); }, diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts index 71d9af9245c0a..5fcaebf1b679f 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts @@ -13,7 +13,7 @@ export = { new ssm.StringParameter(stack, 'Parameter', { allowedPattern: '.*', description: 'The value Foo', - name: 'FooParameter', + parameterName: 'FooParameter', stringValue: 'Foo', }); @@ -60,7 +60,7 @@ export = { new ssm.StringListParameter(stack, 'Parameter', { allowedPattern: '(Foo|Bar)', description: 'The values Foo and Bar', - name: 'FooParameter', + parameterName: 'FooParameter', stringListValue: ['Foo', 'Bar'], }); @@ -128,7 +128,7 @@ export = { test.done(); }, - 'StringParameter.fromName'(test: Test) { + 'StringParameter.fromStringParameterName'(test: Test) { // GIVEN const stack = new Stack(); @@ -148,7 +148,42 @@ export = { }); test.deepEqual(stack.resolve(param.parameterName), 'MyParamName'); test.deepEqual(stack.resolve(param.parameterType), 'String'); - test.deepEqual(stack.resolve(param.stringValue), '{{resolve:ssm:MyParamName}}'); + test.deepEqual(stack.resolve(param.stringValue), { Ref: 'MyParamNameParameter' }); + expect(stack).toMatch({ + Parameters: { + MyParamNameParameter: { + Type: "AWS::SSM::Parameter::Value", + Default: "MyParamName" + } + } + }); + test.done(); + }, + + 'StringParameter.fromStringParameterAttributes'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const param = ssm.StringParameter.fromStringParameterAttributes(stack, 'MyParamName', { + parameterName: 'MyParamName', + version: 2 + }); + + // THEN + test.deepEqual(stack.resolve(param.parameterArn), { + 'Fn::Join': [ '', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':ssm:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':parameterMyParamName' ] ] + }); + test.deepEqual(stack.resolve(param.parameterName), 'MyParamName'); + test.deepEqual(stack.resolve(param.parameterType), 'String'); + test.deepEqual(stack.resolve(param.stringValue), '{{resolve:ssm:MyParamName:2}}'); test.done(); },