[AWS] CDKネタ - Transfer family サーバのプライベートIPアドレスを取得する

# お題

ヤクの毛を狩るまでのお話です。

* Network Load Balancer のターゲットとして、Transfer Family で作成した FTPS サーバを指定したい
* 指定するにはIPアドレスが必要
* 残念ながら CDK 標準では、取得できない
* CDK カスタムリソースを利用して AWS SDK を呼び出し、IPアドレスを取得すべし！ :bulb:


# コードの断片

CDK スタックまるごとは長すぎるので、関連部分のみです。


## FTPS サーバの定義

Transfer family は CDK L1 で作らなくてはいけません。

`vpc` 変数には、`ec2.IVpc` な値が入っていると思ってください。

```ts
import * as cdk from "aws-cdk-lib";
import * as cr from "aws-cdk-lib/custom-resources";

// （略）

    // FTPサーバ
    const server = new cdk.aws_transfer.CfnServer(this, "FtpServer", {
      endpointType: "VPC",
      endpointDetails: {
        // NLB ターゲットにする都合上 subnet ひとつだけ
        subnetIds: [vpc.privateSubnets[0].subnetId],
        // （略）
      },
      // （略）
    });
```

## プライベートIPアドレスを取得するカスタムリソース

Transfer DescribeServer → EC2 DescribeVpcEndpoints → EC2 DescribeNetworkInterfaces と順番に呼び出し、プライベートIPアドレスを取得しています。


```ts
  /**
   * サーバのIPアドレスを取得
   * @param server
   */
  private retrieveIpAddressOf(
    server: cdk.aws_transfer.CfnServer,
    id: string
  ): string {
    // describeServer API を呼び出し
    const describeServer = {
      service: "Transfer",
      action: "describeServer",
      parameters: { ServerId: server.attrServerId },
      outputPaths: ["Server.EndpointDetails.VpcEndpointId"],
      physicalResourceId: cr.PhysicalResourceId.of(id),
    };
    const serverCr = new cr.AwsCustomResource(this, "DescribeServer", {
      onCreate: describeServer,
      onUpdate: describeServer,
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
      }),
    });

    const vpcEndpointId = serverCr.getResponseField(
      "Server.EndpointDetails.VpcEndpointId"
    );

    // describeVpcEndpoints API を呼び出し
    const desribeVpcEndpoints = {
      service: "EC2",
      action: "describeVpcEndpoints",
      parameters: { VpcEndpointIds: [vpcEndpointId] },
      outputPaths: ["VpcEndpoints.0.NetworkInterfaceIds.0"],
      physicalResourceId: cr.PhysicalResourceId.of(id),
    };
    const vpcEndpointCr = new cr.AwsCustomResource(
      this,
      "DescribeVpcEndpoints",
      {
        onCreate: desribeVpcEndpoints,
        onUpdate: desribeVpcEndpoints,
        policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
          resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
        }),
      }
    );

    const networkInterfaceId = vpcEndpointCr.getResponseField(
      "VpcEndpoints.0.NetworkInterfaceIds.0"
    );

    // describeNetworkInterfaces を呼び出し
    const describeNetworkInterfaces = {
      service: "EC2",
      action: "describeNetworkInterfaces",
      parameters: { NetworkInterfaceIds: [networkInterfaceId] },
      outputPaths: ["NetworkInterfaces.0.PrivateIpAddress"],
      physicalResourceId: cr.PhysicalResourceId.of(id),
    };
    const networkInterfaceCr = new cr.AwsCustomResource(
      this,
      "DescribeNetworkInterfaces",
      {
        onCreate: describeNetworkInterfaces,
        onUpdate: describeNetworkInterfaces,
        policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
          resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
        }),
      }
    );

    return networkInterfaceCr.getResponseField(
      "NetworkInterfaces.0.PrivateIpAddress"
    );
  }
```



# 参考リンク

使用するAPIの情報はこちらから拝借しました。感謝！

https://qiita.com/shin_goe/items/c17ac880b5d87a224d9b

それぞれAPIの詳細はこちらです。

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Transfer.html#describeServer-property

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#describeVpcEndpoints-property

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#describeNetworkInterfaces-property

CDK カスタムリソースのコンストラクタです。

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources.AwsCustomResource.html

クラスメソッドの記事です。いつもお世話になってます。 ペコリ

https://dev.classmethod.jp/articles/create-custom-resources-with-aws-cdk-without-using-lambda-functions/


# ひとりごと

ところで、なんでわざわざ NLB を使うかといいますと、各種サーバ（FTP、WWW）に同じ名前で接続したいからなんだってさー。