CDKでSSMパラメータ(StringListParameter)を使う方法


VPCやサブネットを定義したスタックでサブネットIDのリストをSSMパラメータストアに保持しておき、そのパラメータを別のスタックから参照したい場合がありますが、SSMパラメータのString型とStringList型どちらでも参照時は文字列として扱われるため、CDK側でうまく扱わないと正常にデプロイできず結構悩んだため、久々の投稿ですが備忘録として残しておきます。

内容としてはStringListなパラメータを取得してリソース定義に使いたい場合はどうするか・・・になります。
CDKのバージョンは2.50.0になります。この時点ではStringListParameterで取得した値を直接リソースのパラメータとして指定することはできないようです。


まずはOKパターンから記載します。


## OKパターン

1. SSMパラメータストアには文字列のリストをStringとして保管

ssm get-parameterの結果例として、以下のようにします。

```
    "Parameter": {
        "Name": "SubnetIdsParameter",
        "Type": "String",
        "Value": "subnet-a1,subnet-a2",
        "Version": 1,
        "LastModifiedDate": "...",
        "ARN": "arn:aws:ssm:region:account:parameter/SubnetIdsParameter",
        "DataType": "text"
    }
```

2. CDKからはStringParameterで値を取得し、Fn.splitにてパラメータ指定

以下はTransitGatewayアタッチメントのL1 Constructsの利用例になります。

```
    let subnetIds = StringParameter.fromStringParameterName(
      this,
      'SubnetIdsParameter',
      `SubnetIdsParameter`,
    ).stringValue;

    new CfnTransitGatewayAttachment(this, 'VPCAttachement1', {
      vpcId: vpcId,
      transitGatewayId: transitGateway.ref,
      subnetIds: Fn.split(',', subnetIds),
      options: {
        ApplianceModeSupport: 'disable',
        DnsSupport: 'enable',
        Ipv6Support: 'disable',
      },
      tags: [
        {
          key: 'Name',
          value: 'VpcAttachement1',
        },
      ],
    });
```

CfnTransitGatewayAttachmentのsubnetIdsには["subnet-a1","subnet-a2"]が渡されるため、正常にデプロイされました。


## NGパターン

SSMパラメータストアとCDK側で型合わせればいいだろうというノリで記述すると初めは大体こんな感じにしそうですが、うまく動かないパターンになります。

1. SSMには文字列のリストをStringListとして保管

```
    "Parameter": {
        "Name": "SubnetIdsParameter",
        "Type": "StringList",
        "Value": "subnet-a1,subnet-a2",
        "Version": 1,
        "LastModifiedDate": "...",
        "ARN": "arn:aws:ssm:region:account:parameter/SubnetIdsParameter",
        "DataType": "text"
```

登録時はリストとして保管するが、取得時は"subnet-a1,subnet-a2"という文字列になる。
カンマ区切りの1つの文字列という扱い
StringもStringListも参照時の動作は同じ、、、というのが曲者ポイントかもしれません。


2. CDKからはStringListParameterで値を取得し、そのままL1constructsに直接指定

```
    let subnetIds = StringParameter.fromStringParameterName(
      this,
      'SubnetIdsParameter',
      `SubnetIdsParameter`,
    ).stringValue;

    new CfnTransitGatewayAttachment(this, 'VPCAttachement1', {
      vpcId: vpcId,
      transitGatewayId: transitGateway.ref,
      subnetIds: subnetIds,
      options: {
        ApplianceModeSupport: 'disable',
        DnsSupport: 'enable',
        Ipv6Support: 'disable',
      },
      tags: [
        {
          key: 'Name',
          value: 'VpcAttachement1',
        },
      ],
    });
```

StringListParameterで取得した文字列がそのままパラメータとしてされるため、"subnet-a1,subnet-a2"というサブネットIDとして認識される。
結果として、npx cdk deployはエラーとなる。


CloudFormationテンプレート上だとこの記述になるのですが、これだとどうも正常に動かない模様。
```
     "Fn::Split": [
      ",",
      "{{resolve:ssm:SubnetIdsParameter}}"
     ]
```

またNGパターン2としては、StringListのまま、CDK側でFn.split(',', subnetIds)を指定する、、、もありそうですがその場合は、
```Types for SSM parameters [SubnetIdsParameter] defined in CFN template and SSM are incompatible```と互換性がないと怒られてしまいます。




色々と情報を漁ったりしましたが、正しい情報が不明すぎたので残しておきます。
ややハック的な記述方法ですが、将来的にはStringListでも正しく取得できるようになって欲しいところです。

本当はsubnetIdsを分割して、CfnClientVpnTargetNetworkAssociationを複数作り出す、、、というようなことをやりたかったけど、どうやら無理そうだったので、カスタムリソースを使うことになりました・・・、残念。


参考情報
- [AWS SSM Parameter StoreのString型とStringList型はどう違う?](https://dev.classmethod.jp/articles/when-to-use-aws-ssm-parameter-store-stringlist-type/)