Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssm): reference existing SSM list parameters #21880

Merged
merged 9 commits into from
Sep 15, 2022
Merged
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-ec2/lib/machine-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export class GenericSSMParameterImage implements IMachineImage {
* Return the image to use in the given context
*/
public getImage(scope: Construct): MachineImageConfig {
const ami = ssm.StringParameter.valueForTypedStringParameter(scope, this.parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);
const ami = ssm.StringParameter.valueForTypedStringParameterV2(scope, this.parameterName, ssm.ParameterValueType.AWS_EC2_IMAGE_ID);
return {
imageId: ami,
osType: this.os,
Expand Down Expand Up @@ -732,5 +732,5 @@ export interface LookupMachineImageProps {
function lookupImage(scope: Construct, cachedInContext: boolean | undefined, parameterName: string) {
return cachedInContext
? ssm.StringParameter.valueFromLookup(scope, parameterName)
: ssm.StringParameter.valueForTypedStringParameter(scope, parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);
: ssm.StringParameter.valueForTypedStringParameterV2(scope, parameterName, ssm.ParameterValueType.AWS_EC2_IMAGE_ID);
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ecs/lib/amis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,5 @@ export class BottleRocketImage implements ec2.IMachineImage {
function lookupImage(scope: Construct, cachedInContext: boolean | undefined, parameterName: string) {
return cachedInContext
? ssm.StringParameter.valueFromLookup(scope, parameterName)
: ssm.StringParameter.valueForTypedStringParameter(scope, parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);
: ssm.StringParameter.valueForTypedStringParameterV2(scope, parameterName, ssm.ParameterValueType.AWS_EC2_IMAGE_ID);
}
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-ssm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ your CDK app by using `ssm.StringParameter.fromStringParameterAttributes`:

[using SSM parameter](test/integ.parameter-store-string.lit.ts)

You can also reference an existing SSM Parameter Store value that matches an
[AWS specific parameter type](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#aws-specific-parameter-types):

```ts
ssm.StringParameter.valueForTypedStringParameterV2(stack, '/My/Public/Parameter', ssm.ParameterValueType.AWS_EC2_IMAGE_ID);
```

To do the same for a SSM Parameter Store value that is stored as a list:

```ts
ssm.StringListParameter.valueForTypedListParameter(stack, '/My/Public/Parameter', ssm.ParameterValueType.AWS_EC2_IMAGE_ID);
```

### Lookup existing parameters

You can also use an existing parameter by looking up the parameter from the AWS environment.
Expand Down
204 changes: 186 additions & 18 deletions packages/@aws-cdk/aws-ssm/lib/parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export interface StringParameterProps extends ParameterOptions {
* The type of the string parameter
*
* @default ParameterType.STRING
* @deprecated - type will always be 'String'
*/
readonly type?: ParameterType;

Expand Down Expand Up @@ -199,8 +200,75 @@ abstract class ParameterBase extends Resource implements IParameter {
}
}

/**
* The type of CFN SSM Parameter
*
* Using specific types can be helpful in catching invalid values
* at the start of creating or updating a stack. CloudFormation validates
* the values against existing values in the account.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#aws-ssm-parameter-types
*/
export enum ParameterValueType {
/**
* String
*/
STRING = 'String',

/**
* An Availability Zone, such as us-west-2a.
*/
AWS_EC2_AVAILABILITYZONE_NAME = 'AWS::EC2::AvailabilityZone::Name',

/**
* An Amazon EC2 image ID, such as ami-0ff8a91507f77f867.
*/
AWS_EC2_IMAGE_ID = 'AWS::EC2::Image::Id',

/**
* An Amazon EC2 instance ID, such as i-1e731a32.
*/
AWS_EC2_INSTANCE_ID = 'AWS::EC2::Instance::Id',

/**
* An Amazon EC2 key pair name.
*/
AWS_EC2_KEYPAIR_KEYNAME = 'AWS::EC2::KeyPair::KeyName',

/**
* An EC2-Classic or default VPC security group name, such as my-sg-abc.
*/
AWS_EC2_SECURITYGROUP_GROUPNAME = 'AWS::EC2::SecurityGroup::GroupName',

/**
* A security group ID, such as sg-a123fd85.
*/
AWS_EC2_SECURITYGROUP_ID = 'AWS::EC2::SecurityGroup::Id',

/**
* A subnet ID, such as subnet-123a351e.
*/
AWS_EC2_SUBNET_ID = 'AWS::EC2::Subnet::Id',

/**
* An Amazon EBS volume ID, such as vol-3cdd3f56.
*/
AWS_EC2_VOLUME_ID = 'AWS::EC2::Volume::Id',

/**
* A VPC ID, such as vpc-a123baa3.
*/
AWS_EC2_VPC_ID = 'AWS::EC2::VPC::Id',

/**
* An Amazon Route 53 hosted zone ID, such as Z23YXV4OVPL04A.
*/
AWS_ROUTE53_HOSTEDZONE_ID = 'AWS::Route53::HostedZone::Id',
}

/**
* SSM parameter type
* @deprecated these types are no longer used
*/
export enum ParameterType {
/**
Expand Down Expand Up @@ -302,8 +370,55 @@ export interface StringParameterAttributes extends CommonStringParameterAttribut
* The type of the string parameter
*
* @default ParameterType.STRING
* @deprecated - use valueType instead
*/
readonly type?: ParameterType;

/**
* The type of the string parameter value
*
* Using specific types can be helpful in catching invalid values
* at the start of creating or updating a stack. CloudFormation validates
* the values against existing values in the account.
*
* Note - if you want to allow values from different AWS accounts, use
* ParameterValueType.STRING
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#aws-ssm-parameter-types
*
* @default ParameterValueType.STRING
*/
readonly valueType?: ParameterValueType;
}

/**
* Attributes for parameters of string list type.
*
* @see ParameterType
*/
export interface ListParameterAttributes extends CommonStringParameterAttributes {
/**
* The version number of the value you wish to retrieve.
*
* @default The latest version will be retrieved.
*/
readonly version?: number;

/**
* The type of the string list parameter value.
*
* Using specific types can be helpful in catching invalid values
* at the start of creating or updating a stack. CloudFormation validates
* the values against existing values in the account.
*
* Note - if you want to allow values from different AWS accounts, use
* ParameterValueType.STRING
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#aws-ssm-parameter-types
*
* @default ParameterValueType.STRING
*/
readonly elementType?: ParameterValueType;
}

/**
Expand Down Expand Up @@ -331,25 +446,10 @@ export interface SecureStringParameterAttributes extends CommonStringParameterAt
* @resource AWS::SSM::Parameter
*
* @example
*
* const ssmParameter = new ssm.StringParameter(this, 'mySsmParameter', {
* parameterName: 'mySsmParameter',
* stringValue: 'mySsmParameterValue',
* type: ssm.ParameterType.STRING,
* });
*
* const secureParameter = new ssm.StringParameter(this, 'mySecretParameter', {
* parameterName: 'mySecretParameter',
* stringValue: 'mySecretParameterValue',
* type: ssm.ParameterType.SECURE_STRING,
* });
*
* const listParameter = new ssm.StringParameter(this, 'myListParameter', {
* parameterName: 'myListParameter',
* stringValue: ["myListParameterValue1", "myListParameterValue2"],
* type: ssm.ParameterType.STRING_LIST,
* });
*
*/
export class StringParameter extends ParameterBase implements IStringParameter {

Expand All @@ -367,8 +467,11 @@ export class StringParameter extends ParameterBase implements IStringParameter {
if (!attrs.parameterName) {
throw new Error('parameterName cannot be an empty string');
}
if (attrs.type && ![ParameterType.STRING, ParameterType.AWS_EC2_IMAGE_ID].includes(attrs.type)) {
throw new Error(`fromStringParameterAttributes does not support ${attrs.type}. Please use ParameterType.STRING or ParameterType.AWS_EC2_IMAGE_ID`);
}

const type = attrs.type || ParameterType.STRING;
const type = attrs.type ?? attrs.valueType ?? ParameterValueType.STRING;

const stringValue = attrs.version
? new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toString()
Expand All @@ -377,7 +480,7 @@ export class StringParameter extends ParameterBase implements IStringParameter {
class Import extends ParameterBase {
public readonly parameterName = attrs.parameterName;
public readonly parameterArn = arnForParameterName(this, attrs.parameterName, { simpleName: attrs.simpleName });
public readonly parameterType = type;
public readonly parameterType = ParameterType.STRING; // this is the type returned by CFN @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-parameter.html#aws-resource-ssm-parameter-return-values
public readonly stringValue = stringValue;
}

Expand Down Expand Up @@ -426,7 +529,24 @@ export class StringParameter extends ParameterBase implements IStringParameter {
* @param version The parameter version (recommended in order to ensure that the value won't change during deployment)
*/
public static valueForStringParameter(scope: Construct, parameterName: string, version?: number): string {
return StringParameter.valueForTypedStringParameter(scope, parameterName, ParameterType.STRING, version);
return StringParameter.valueForTypedStringParameterV2(scope, parameterName, ParameterValueType.STRING, version);
}

/**
* Returns a token that will resolve (during deployment) to the string value of an SSM string parameter.
* @param scope Some scope within a stack
* @param parameterName The name of the SSM parameter.
* @param type The type of the SSM parameter.
* @param version The parameter version (recommended in order to ensure that the value won't change during deployment)
*/
public static valueForTypedStringParameterV2(scope: Construct, parameterName: string, type = ParameterValueType.STRING, version?: number): string {
const stack = Stack.of(scope);
const id = makeIdentityForImportedValue(parameterName);
const exists = stack.node.tryFindChild(id) as IStringParameter;

if (exists) { return exists.stringValue; }

return this.fromStringParameterAttributes(stack, id, { parameterName, version, valueType: type }).stringValue;
}

/**
Expand All @@ -435,8 +555,13 @@ export class StringParameter extends ParameterBase implements IStringParameter {
* @param parameterName The name of the SSM parameter.
* @param type The type of the SSM parameter.
* @param version The parameter version (recommended in order to ensure that the value won't change during deployment)
* @deprecated - use valueForTypedStringParameterV2 instead
*/
public static valueForTypedStringParameter(scope: Construct, parameterName: string, type = ParameterType.STRING, version?: number): string {
if (type === ParameterType.STRING_LIST) {
throw new Error('valueForTypedStringParameter does not support STRING_LIST, '
+'use valueForTypedListParameter instead');
}
const stack = Stack.of(scope);
const id = makeIdentityForImportedValue(parameterName);
const exists = stack.node.tryFindChild(id) as IStringParameter;
Expand Down Expand Up @@ -528,6 +653,49 @@ export class StringListParameter extends ParameterBase implements IStringListPar
return new Import(scope, id);
}

/**
* Imports an external string list parameter with name and optional version.
*/
public static fromListParameterAttributes(scope: Construct, id: string, attrs: ListParameterAttributes): IStringListParameter {
if (!attrs.parameterName) {
throw new Error('parameterName cannot be an empty string');
}

const type = attrs.elementType ?? ParameterValueType.STRING;
const valueType = `List<${type}>`;

const stringValue = attrs.version
? new CfnDynamicReference(CfnDynamicReferenceService.SSM, `${attrs.parameterName}:${Tokenization.stringifyNumber(attrs.version)}`).toStringList()
: new CfnParameter(scope, `${id}.Parameter`, { type: `AWS::SSM::Parameter::Value<${valueType}>`, default: attrs.parameterName }).valueAsList;

class Import extends ParameterBase {
public readonly parameterName = attrs.parameterName;
public readonly parameterArn = arnForParameterName(this, attrs.parameterName, { simpleName: attrs.simpleName });
public readonly parameterType = valueType; // it doesn't really matter what this is since a CfnParameter can only be `String | StringList`
public readonly stringListValue = stringValue;
}

return new Import(scope, id);
}

/**
* Returns a token that will resolve (during deployment) to the list value of an SSM StringList parameter.
* @param scope Some scope within a stack
* @param parameterName The name of the SSM parameter.
* @param type the type of the SSM list parameter
* @param version The parameter version (recommended in order to ensure that the value won't change during deployment)
*/
public static valueForTypedListParameter(scope: Construct, parameterName: string, type = ParameterValueType.STRING, version?: number): string[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does typing this value even add value? (ha)

We return it as a string[] anyway... so does anybody does any checking on the types at all? If so, who?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloudFormation does the type checking in this case. If you request a ParameterValueType.AWS_EC2_IMAGE_ID it expects the value to be a string in the AMI format, and if it is not it will throw an error.

const stack = Stack.of(scope);
const id = makeIdentityForImportedValue(parameterName);
const exists = stack.node.tryFindChild(id) as IStringListParameter;

if (exists) { return exists.stringListValue; }

return this.fromListParameterAttributes(stack, id, { parameterName, elementType: type, version }).stringListValue;
}


public readonly parameterArn: string;
public readonly parameterName: string;
public readonly parameterType: string;
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ssm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2",
Expand Down
Loading