AWS CDK で OpenAPI 定義のプレースホルダーを実際の値に置き換える


# はじめに

AWS CDK を使って OpenAPI 定義を API Gateway にインポートするとき、CDKを用いると、対応するLambdaの名称は動的かつユニークに生成される。ため、それがいつ使用されるか分からないという問題に直面する。今回は OpenAPI 定義の中のプレースホルダーを実際の値に置き換えるテンプレート手法を紹介する。

```plantuml
@startuml

!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v15.0/dist
!include AWSPuml/AWSCommon.puml

!include AWSPuml/AWSSimplified.puml
!include AWSPuml/ApplicationIntegration/APIGateway.puml
!include AWSPuml/Compute/Lambda.puml

left to right direction

rectangle "AWS CDKで構築" {
  artifact OpenAPI定義
  APIGateway(apiGateway, "API Gateway", "API Gateway")
  Lambda(lambda, "Lambda", "Lambda")
}

OpenAPI定義 --> apiGateway:インポート
apiGateway --> lambda

@enduml
```

何もしない場合 Lambda の arn は以下のようになる。 {動的なユニークID} の部分が CDK で扱う上でちょっと厄介な部分だ。

```
arn:aws:lambda:{リージョン}:{アカウントID}:function:{スタック名}-{論理ID}-{動的なユニークID}
```

# 流れ

1. OpenAPI 定義を用意する
1. Lambda の Cfn 論理 ID を任意の名前に変更する
1. OpenAPI 定義を API Gateway にがっちゃんこする

# 前提

- CDK バージョン 2.80

# OpenAPI定義を用意

[こちらのサンプル](https://docs.aws.amazon.com/apigateway/latest/developerguide/import-api-aws-variables.html) を使って OpenAPI 定義を編集する。以下のように Lambda Function の部分をまるっと `${ApiLambda.Arn}` で置き換えて、 Cfn 組み込み関数の [Fn::Sub](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html) を使ってあとで置換できるようにしておく。

```diff
      x-amazon-apigateway-integration:
        uri:
+          Fn::Sub: 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations'
-          arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:LambdaFunctionName/invocations

```

# Lambda の Cfn 論理 ID を任意の名前に変更

`ApiLambda` という名前で動的でユニークな ID を上書きする。

```js
    const apiLambda = new Function(this, 'Function', {
      code: Code.fromAsset('../api/lambda'),
      :
    });
    const cfnLambda = apiLambda.node.defaultChild as CfnFunction;
    cfnLambda.overrideLogicalId('ApiLambda');
```

# OpenAPI 定義を API Gateway にがっちゃんこ

[Fn::Transform](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html) で `AWS::Include`マクロを実行しプレースホルダーを実際の値に置換する。

```js
    // OpenAPI YAMLファイルからCfn Assetを定義
    const openApiAsset = new Asset(this, 'openApiFile', {
      path: 'openapi.yaml',
    });
    // AWS::Includeマクロの入力パラメータを作成
    const transformMap = {
      Location: openApiAsset.s3ObjectUrl,
    };
    // AWS::Include マクロを InlineApiDefinition で使えるようにする
    // これでS3オブジェクトを参照している
    const data: IResolvable = Fn.transform('AWS::Include', transformMap);
    const apiDefinition: InlineApiDefinition = ApiDefinition.fromInline(data);

    const specRestApi = new SpecRestApi(this, 'RestApi', {
      apiDefinition: apiDefinition,
      restApiName: 'tasks-api',
      deployOptions: {
        stageName: '',
        loggingLevel: MethodLoggingLevel.INFO,
      },
    });

```

# 参考

https://stackoverflow.com/questions/62179893/aws-cdk-how-to-create-an-api-gateway-backed-by-lambda-from-openapi-spec

https://github.com/aws/aws-cdk/issues/1461

https://medium.com/@gwieser/solving-a-nightmare-aws-cdk-openapi-and-api-gateway-a1b6fdc1fd24