先日AWS Cloud Development Kit(AWS CDK)がGAされたので、なにができるのかソースを眺めたり使ってみたりしていたところ、面白そうなパッケージをみつけたのでお試ししていました。
AWS Cloud Development Kit(AWS CDK)とは
AWS クラウド開発キット (CDK) – TypeScript と Python 用がご利用可能に | Amazon Web Services ブログ
https://aws.amazon.com/jp/blogs/news/aws-cloud-development-kit-cdk-typescript-and-python-are-now-generally-available/
Infrastructure as Code によって、手動での実行手順に頼る代わりに、管理者と開発者の両方が構成ファイルを使用し、アプリケーションに必要なコンピューティング、ストレージ、ネットワーク、アプリケーションサービスのプロビジョニングを自動化できるようになります。
AWS CloudFormation(CFn)を利用するとYAMLやJSONでインフラストラクチャの管理ができますが、それを発展させてインフラストラクチャの管理もプログラミングしようぜって感じのやつです。
詳細は上記ブログでだいたいわかるかと思います。
AWS CDKを利用してインフラストラクチャを定義する場合、リソースごとにパッケージをインストールして実装します。
下記はaws-samples/aws-cdk-examples
リポジトリにあるサンプルでLambda関数とスケジュール起動させるためのAmazon CloudWatch Eventsのリソースを定義しています。package.json
をみると@aws-cdk/aws-events
, @aws-cdk/aws-events-targets
, @aws-cdk/aws-lambda
が別途インストールされていることが確認できます。Python 2.7を指定しているのがむず痒いですね
aws-cdk-examples/index.ts at master · aws-samples/aws-cdk-examples
https://github.com/aws-samples/aws-cdk-examples/blob/master/typescript/lambda-cron/index.ts
import events = require('@aws-cdk/aws-events');
import targets = require('@aws-cdk/aws-events-targets');
import lambda = require('@aws-cdk/aws-lambda');
import cdk = require('@aws-cdk/core');
import fs = require('fs');
export class LambdaCronStack extends cdk.Stack {
constructor(app: cdk.App, id: string) {
super(app, id);
const lambdaFn = new lambda.Function(this, 'Singleton', {
code: new lambda.InlineCode(fs.readFileSync('lambda-handler.py', { encoding: 'utf-8' })),
handler: 'index.main',
timeout: cdk.Duration.seconds(300),
runtime: lambda.Runtime.PYTHON_2_7,
});
// Run every day at 6PM UTC
// See https://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html
const rule = new events.Rule(this, 'Rule', {
schedule: events.Schedule.expression('cron(0 18 ? * MON-FRI *)')
});
rule.addTarget(new targets.LambdaFunction(lambdaFn));
}
}
const app = new cdk.App();
new LambdaCronStack(app, 'LambdaCronExample');
app.synth();
{
"name": "lambda-cron",
(略)
"dependencies": {
"@aws-cdk/aws-events": "*",
"@aws-cdk/aws-events-targets": "*",
"@aws-cdk/aws-lambda": "*",
"@aws-cdk/core": "*"
}
}
こんな感じでこれまでYAMLやJSONでテンプレート定義してスタック管理していたのをプログラミングに落とし込めるのがAWS CDKです。
面白そうなパッケージ
aws-cdk
リポジトリのソースを眺めていたら@aws-cdk/custom-resources
というパッケージがありました。
aws-cdk/packages/@aws-cdk/custom-resources at master · aws/aws-cdk
https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/custom-resources
名前のとおりカスタムリソースのパッケージですが、CFnのいわゆるカスタムリソースのパッケージは別にあり、利用用途が少し異なるものでした。
こちらがCFnのいわゆるカスタムリソースのパッケージ
aws-cdk/packages/@aws-cdk/aws-cloudformation at master · aws/aws-cdk
https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-cloudformation
単にカスタムリソースの使い方を調べていて先に@aws-cdk/custom-resources
に目がいったという。。。
@aws-cdk/custom-resources
のREADMEの冒頭で
This is a developer preview (public beta) module. Releases might lack important features and might have future breaking changes.
This API is still under active development and subject to non-backward compatible changes or removal in any future version. Use of the API is not recommended in production environments. Experimental APIs are not subject to the Semantic Versioning model.
とあり、AWS CDKはGAされたにも関わらず、絶賛開発中のパッケージです。(2019/07/23時点)
サンプルをみてみるとCFnのカスタムリソースをより簡単に実装できるようにするためのパッケージみたいです。
Lambda関数がでてこない!
const verifyDomainIdentity = new AwsCustomResource(this, 'VerifyDomainIdentity', {
onCreate: {
service: 'SES',
action: 'verifyDomainIdentity',
parameters: {
Domain: 'example.com'
},
physicalResourceIdPath: 'VerificationToken' // Use the token returned by the call as physical id
}
});
new route53.TxtRecord(zone, 'SESVerificationRecord', {
recordName: `_amazonses.example.com`,
recordValue: verifyDomainIdentity.getData('VerificationToken')
});
つかってみた
CFnのカスタムリソースといえば、先日散々苦労してCFnのテンプレートを作成したAmazon Managed Blockchain(AMB)しかおもい浮かばなかったのでお題にしてみます。
苦労したやつはこちら
Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(使い方編) - Qiita
https://qiita.com/kai_kou/items/dc7dbe947a7cc6d0a8db
Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(解説編) - Qiita
https://qiita.com/kai_kou/items/ab51cb7d16c680f591d2
前提
- AWSアカウントがある
- AWS CLIが利用できる
- Node.jsがインストール済み
AWS CDKのインストール
AWS CDKのコマンドが利用できるようにするため、aws-cdk
をインストールします。
> node -v
v10.11.0
> npm -v
6.10.1
> npm i -g aws-cdk
# fishの場合
> exec fish -l
> cdk --version
1.1.0 (build 1a11e96)
AWS CDKプロジェクト作成
cdk
コマンドでプロジェクトを作成します。言語はTypeScriptを利用します。
> mkdir use-cdk-custom-resources
> cd use-cdk-custom-resources
> cdk init app --language=typescript
Applying project template app for typescript
Initializing a new git repository...
Executing npm install...
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN use-cdk-custom-resources@0.1.0 No repository field.
npm WARN use-cdk-custom-resources@0.1.0 No license field.
# Useful commands
* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
cdk init
コマンドを実行すると以下のようにファイルが自動生成されました。
コマンド実行したディレクトリの名前が反映されました。
> tree . -L 2
.
├── README.md
├── bin
│ └── use-cdk-custom-resources.ts
├── cdk.json
├── lib
│ └── use-cdk-custom-resources-stack.ts
├── node_modules
(略)
├── package-lock.json
├── package.json
└── tsconfig.json
3 directories, 5 files
@aws-cdk/custom-resources
のインストール
@aws-cdk/custom-resources
をインストールして利用できるようにします。
> npm i @aws-cdk/custom-resources
+ @aws-cdk/custom-resources@1.1.0
added 19 packages from 4 contributors and audited 939 packages in 170.2s
found 0 vulnerabilities
実装する
@aws-cdk/custom-resources
パッケージのAwsCustomResource
を利用して既存のAMBネットワーク情報を取得するカスタムリソースを実装してみました。
取得した情報をcdk.CfnOutput
でCFnスタックのアウトプットとします。
AMBのネットワークがないって方は下記を参考に構築してみてください。
Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(使い方編) - Qiita
https://qiita.com/kai_kou/items/dc7dbe947a7cc6d0a8db
実装はcdk init
コマンドで自動生成されたlib/use-cdk-custom-resources-stack.ts
に行います。
import cdk = require('@aws-cdk/core');
import { AwsCustomResource } from '@aws-cdk/custom-resources/lib';
export class UseCdkCustomResourcesStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const getNetworkTest = new AwsCustomResource(this, 'getNetworkTest', {
onCreate: {
apiVersion: '2018-09-24', // 指定しなくてもOK
service: 'ManagedBlockchain',
action: 'getNetwork',
parameters: {
NetworkId: 'n-XXXXXXXXXXXXXXXXXXXXXXXXXX'
},
physicalResourceIdPath: 'Network.Id'
}
});
new cdk.CfnOutput(this, 'BcNetworkId', {
description: 'The message that came back from the Custom Resource',
value: getNetworkTest.getData('Network.Id').toString()
});
}
}
ビルドしてデプロイする
実装できたらビルドしてデプロイします。
> npm run build
> use-cdk-custom-resources@0.1.0 build /Users/kai/dev/aws/cdk/use-cdk-custom-resources
> tsc
AWS CDKで初回デプロイ時にcdk bootstrap
コマンドを実行する必要がありました。
実行するとCFnにCDKToolkit
というスタックが作成されてリソースとしてS3バケットが作成されました。
--profile
オプションでAWSアカウントを指定する必要があります。こちらはAWS CLIと同じ設定をみてくれます。
> cdk bootstrap --profile default
⏳ Bootstrapping environment aws://xxxxxxxxxxxx/us-east-1...
CDKToolkit: creating CloudFormation changeset...
0/2 | 16:11:40 | CREATE_IN_PROGRESS | AWS::S3::Bucket | StagingBucket
0/2 | 16:11:41 | CREATE_IN_PROGRESS | AWS::S3::Bucket | StagingBucket Resource creation Initiated
1/2 | 16:12:02 | CREATE_COMPLETE | AWS::S3::Bucket | StagingBucket
2/2 | 16:12:05 | CREATE_COMPLETE | AWS::CloudFormation::Stack | CDKToolkit
✅ Environment aws://xxxxxxxxxxxx/us-east-1 bootstrapped.
CDKToolkit
スタック情報を確認するとS3バケットが作成されたのを確認できます。
> aws cloudformation describe-stacks \
--stack-name CDKToolkit
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/CDKToolkit/92106320-ad05-11e9-bffe-0a9fca2e6786",
"StackName": "CDKToolkit",
"ChangeSetId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:changeSet/CDK-63a06f96-830e-4f26-b47a-9d2a79fa6ca4/834d5f97-8053-4974-8091-ce0fbac66f72",
"Description": "The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.",
"CreationTime": "2019-07-23T04:51:44.877Z",
"LastUpdatedTime": "2019-07-23T04:51:52.481Z",
"RollbackConfiguration": {},
"StackStatus": "CREATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Capabilities": [
"CAPABILITY_IAM",
"CAPABILITY_NAMED_IAM",
"CAPABILITY_AUTO_EXPAND"
],
"Outputs": [
{
"OutputKey": "BucketName",
"OutputValue": "cdktoolkit-stagingbucket-xxxxxxxxxxxx",
"Description": "The name of the S3 bucket owned by the CDK toolkit stack"
},
{
"OutputKey": "BucketDomainName",
"OutputValue": "cdktoolkit-stagingbucket-xxxxxxxxxxxx.s3.amazonaws.com",
"Description": "The domain name of the S3 bucket owned by the CDK toolkit stack"
}
],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
> aws cloudformation describe-stack-resources \
--stack-name CDKToolkit
{
"StackResources": [
{
"StackName": "CDKToolkit",
"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/CDKToolkit/92106320-ad05-11e9-bffe-0a9fca2e6786",
"LogicalResourceId": "StagingBucket",
"PhysicalResourceId": "cdktoolkit-stagingbucket-xxxxxxxxxxxx",
"ResourceType": "AWS::S3::Bucket",
"Timestamp": "2019-07-23T04:52:17.264Z",
"ResourceStatus": "CREATE_COMPLETE",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
}
]
}
準備ができたのでcdk deploy
でデプロイしてみます。
コマンド実行するとスタックで作成されるロールの情報が表示されて実行するか確認されます。
確認後、CFnにスタック作成されてイベントログが出力されます。わかりやすくていいですね。
> cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:
IAM Statement Changes
┌───┬────────────────────────────────┬────────┬────────────────────────────────┬────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼────────────────────────────────┼────────┼────────────────────────────────┼────────────────────────────────┼───────────┤
│ + │ ${AWS679f53fac002430cb0da5b798 │ Allow │ sts:AssumeRole │ Service:lambda.${AWS::URLSuffi │ │
│ │ 2bd2287/ServiceRole.Arn} │ │ │ x} │ │
├───┼────────────────────────────────┼────────┼────────────────────────────────┼────────────────────────────────┼───────────┤
│ + │ * │ Allow │ managedblockchain:GetNetwork │ AWS:${AWS679f53fac002430cb0da5 │ │
│ │ │ │ │ b7982bd2287/ServiceRole} │ │
└───┴────────────────────────────────┴────────┴────────────────────────────────┴────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬───────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────┐
│ │ Resource │ Managed Policy ARN │
├───┼───────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────┤
│ + │ ${AWS679f53fac002430cb0da5b7982bd2287/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLam │
│ │ │ bdaBasicExecutionRole │
└───┴───────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)
Do you wish to deploy these changes (y/n)? y
UseCdkCustomResourcesStack: deploying...
UseCdkCustomResourcesStack: creating CloudFormation changeset...
0/6 | 16:09:48 | CREATE_IN_PROGRESS | AWS::IAM::Role | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2)
0/6 | 16:09:48 | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata
0/6 | 16:09:48 | CREATE_IN_PROGRESS | AWS::IAM::Role | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2) Resource creation Initiated
0/6 | 16:09:50 | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
1/6 | 16:09:50 | CREATE_COMPLETE | AWS::CDK::Metadata | CDKMetadata
2/6 | 16:10:01 | CREATE_COMPLETE | AWS::IAM::Role | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2)
2/6 | 16:10:05 | CREATE_IN_PROGRESS | AWS::IAM::Policy | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/DefaultPolicy (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E)
2/6 | 16:10:08 | CREATE_IN_PROGRESS | AWS::IAM::Policy | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/DefaultPolicy (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E) Resource creation Initiated
3/6 | 16:10:13 | CREATE_COMPLETE | AWS::IAM::Policy | AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/DefaultPolicy (AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E)
3/6 | 16:10:16 | CREATE_IN_PROGRESS | AWS::Lambda::Function | AWS679f53fac002430cb0da5b7982bd2287 (AWS679f53fac002430cb0da5b7982bd22872D164C4C)
3/6 | 16:10:16 | CREATE_IN_PROGRESS | AWS::Lambda::Function | AWS679f53fac002430cb0da5b7982bd2287 (AWS679f53fac002430cb0da5b7982bd22872D164C4C) Resource creation Initiated
4/6 | 16:10:16 | CREATE_COMPLETE | AWS::Lambda::Function | AWS679f53fac002430cb0da5b7982bd2287 (AWS679f53fac002430cb0da5b7982bd22872D164C4C)
4/6 | 16:10:22 | CREATE_IN_PROGRESS | Custom::AWS | getNetworkTest/Resource/Default (getNetworkTest425BEE14)
4/6 | 16:10:27 | CREATE_IN_PROGRESS | Custom::AWS | getNetworkTest/Resource/Default (getNetworkTest425BEE14) Resource creation Initiated
5/6 | 16:10:27 | CREATE_COMPLETE | Custom::AWS | getNetworkTest/Resource/Default (getNetworkTest425BEE14)
6/6 | 16:10:29 | CREATE_COMPLETE | AWS::CloudFormation::Stack | UseCdkCustomResourcesStack
✅ UseCdkCustomResourcesStack
Outputs:
UseCdkCustomResourcesStack.BcNetworkId = n-XXXXXXXXXXXXXXXXXXXXXXXXXX
Stack ARN:
arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/UseCdkCustomResourcesStack/ca79efc0-ad18-11e9-8a12-0a3a983b5e88
スタック作成されるとOutputs
情報も出力され、AMBネットワーク情報が取得できたのを確認できます。
CFnスタック情報を確認してみるとParameters
が設定されていたり、AWS::CDK::Metadata
リソースなどAWS CDK特有のリソースがあったりします。
> aws cloudformation describe-stacks \
--stack-name UseCdkCustomResourcesStack
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/UseCdkCustomResourcesStack/7d97bb10-ae85-11e9-93b7-0a51b82e168a",
"StackName": "UseCdkCustomResourcesStack",
"ChangeSetId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:changeSet/CDK-8a18b8d3-a2c2-4095-8f99-1c3da69255a2/3ad6c643-6d89-4e96-9cf9-5403bff5b01e",
"Parameters": [
{
"ParameterKey": "AWS679f53fac002430cb0da5b7982bd2287CodeS3BucketF55839B6",
"ParameterValue": "cdktoolkit-stagingbucket-xxxxxxxxxxxx"
},
{
"ParameterKey": "AWS679f53fac002430cb0da5b7982bd2287CodeS3VersionKey3C45B02F",
"ParameterValue": "assets/UseCdkCustomResourcesStackAWS679f53fac002430cb0da5b7982bd2287Code44CF678B/||6d3e1aface6af068a2d1abe48f7aa26b140d9231a6078e03e076239b8189ae2a.zip"
},
{
"ParameterKey": "AWS679f53fac002430cb0da5b7982bd2287CodeArtifactHash49FACC2E",
"ParameterValue": "6d3e1aface6af068a2d1abe48f7aa26b140d9231a6078e03e076239b8189ae2a"
}
],
"CreationTime": "2019-07-25T02:39:57.405Z",
"LastUpdatedTime": "2019-07-25T02:40:05.168Z",
"RollbackConfiguration": {},
"StackStatus": "CREATE_COMPLETE",
"DisableRollback": false,
"NotificationARNs": [],
"Capabilities": [
"CAPABILITY_IAM",
"CAPABILITY_NAMED_IAM",
"CAPABILITY_AUTO_EXPAND"
],
"Outputs": [
{
"OutputKey": "BcNetworkId",
"OutputValue": "n-XXXXXXXXXXXXXXXXXXXXXXXXXX",
"Description": "The message that came back from the Custom Resource"
}
],
"Tags": [],
"EnableTerminationProtection": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
}
}
]
}
> aws cloudformation describe-stack-resources \
--stack-name UseCdkCustomResourcesStack \
--query "StackResources[*].ResourceType"
[
"AWS::Lambda::Function",
"AWS::IAM::Role",
"AWS::IAM::Policy",
"AWS::CDK::Metadata",
"Custom::AWS"
]
のぞいてみる
AWS CDKのデプロイ時に作成されるファイルをのぞいてみました。
npm run build
コマンドでjs
ファイルが作成されています。
cdk deploy
コマンドでcdk.out
フォルダが生成され、こちらにCFn関連のファイルが作成されています。
> tree . -L 2
.
├── README.md
├── bin
│ ├── use-cdk-custom-resources.d.ts
│ ├── use-cdk-custom-resources.js
│ └── use-cdk-custom-resources.ts
├── cdk.json
├── cdk.out
│ ├── UseCdkCustomResourcesStack.template.json
│ ├── asset.01d077466681f165ec462417f525188e4be806c35136c1a5e4bdcd3b71c12942
│ ├── cdk.out
│ └── manifest.json
├── lib
│ ├── use-cdk-custom-resources-stack.d.ts
│ ├── use-cdk-custom-resources-stack.js
│ └── use-cdk-custom-resources-stack.ts
├── node_modules
(略)
├── package-lock.json
├── package.json
└── tsconfig.json
212 directories, 19 files
cdk.out/asset.xxxxx
フォルダにindex.js
ファイルがあり、これがCFnのLambda-Backedカスタムリソースで利用されるLambda関数のファイルになるみたいです。
> tree cdk.out -L 2
cdk.out
├── UseCdkCustomResourcesStack.template.json
├── asset.01d077466681f165ec462417f525188e4be806c35136c1a5e4bdcd3b71c12942
│ ├── index.d.ts
│ └── index.js
├── cdk.out
└── manifest.json
cdk.out/asset.xxxxx/index.js
をみてみるとflatten
メソッドっていうAWS SDKから得られたJSONファイルをフラット化する実装があったりします。
なぜJSONファイルをフラット化する必要があるかは下記が詳しいです。
AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない - Qiita
https://qiita.com/kai_kou/items/61a2b3c69ae2af4f2e40
AWS CloudFormationのLambda-BackedカスタムリソースでネストされてるっぽいJSONを返す方法 - Qiita
https://qiita.com/kai_kou/items/d030117c694531410203
AWS SDKの利用方法もconst awsService = new AWS[call.service](call.apiVersion && { apiVersion: call.apiVersion });
って呼び方ができるのかぁ。など参考になります。
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// tslint:disable:no-console
const AWS = require("aws-sdk");
/**
* Flattens a nested object
*
* @param object the object to be flattened
* @returns a flat object with path as keys
*/
function flatten(object) {
return Object.assign({}, ...function _flatten(child, path = []) {
return [].concat(...Object.keys(child)
.map(key => typeof child[key] === 'object'
? _flatten(child[key], path.concat([key]))
: ({ [path.concat([key]).join('.')]: child[key] })));
}(object));
}
/**
* Converts true/false strings to booleans in an object
*/
function fixBooleans(object) {
return JSON.parse(JSON.stringify(object), (_k, v) => v === 'true'
? true
: v === 'false'
? false
: v);
}
/**
* Filters the keys of an object.
*/
function filterKeys(object, pred) {
return Object.entries(object)
.reduce((acc, [k, v]) => pred(k)
? { ...acc, [k]: v }
: acc, {});
}
async function handler(event, context) {
try {
console.log(JSON.stringify(event));
console.log('AWS SDK VERSION: ' + AWS.VERSION);
let physicalResourceId = event.PhysicalResourceId;
let flatData = {};
let data = {};
const call = event.ResourceProperties[event.RequestType];
if (call) {
const awsService = new AWS[call.service](call.apiVersion && { apiVersion: call.apiVersion });
try {
const response = await awsService[call.action](call.parameters && fixBooleans(call.parameters)).promise();
flatData = flatten(response);
data = call.outputPath
? filterKeys(flatData, k => k.startsWith(call.outputPath))
: flatData;
}
catch (e) {
if (!call.catchErrorPattern || !new RegExp(call.catchErrorPattern).test(e.code)) {
throw e;
}
}
physicalResourceId = call.physicalResourceIdPath
? flatData[call.physicalResourceIdPath]
: call.physicalResourceId;
}
await respond('SUCCESS', 'OK', physicalResourceId, data);
}
catch (e) {
console.log(e);
await respond('FAILED', e.message || 'Internal Error', context.logStreamName, {});
}
function respond(responseStatus, reason, physicalResourceId, data) {
const responseBody = JSON.stringify({
Status: responseStatus,
Reason: reason,
PhysicalResourceId: physicalResourceId,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
NoEcho: false,
Data: data
});
console.log('Responding', responseBody);
const parsedUrl = require('url').parse(event.ResponseURL);
const requestOptions = {
hostname: parsedUrl.hostname,
path: parsedUrl.path,
method: 'PUT',
headers: { 'content-type': '', 'content-length': responseBody.length }
};
return new Promise((resolve, reject) => {
try {
const request = require('https').request(requestOptions, resolve);
request.on('error', reject);
request.write(responseBody);
request.end();
}
catch (e) {
reject(e);
}
});
}
}
exports.handler = handler;
//# sourceMappingURL=data:application/json;base64,eyJ2(略)
cdk.out/manifest.json
ファイルがCFnでスタック作成する際のテンプレートになっています。
リソース名にランダムな英数字が含まれているので、扱うリソースが増えるとなかなかに読み応えがありそうです(白目
おもしろいなぁと感じたのは、AWS SDKのサービス名とアクション(メソッド) = AWS::IAM::Policyのアクションになる点です。
今回はAMBのgetNetworkを利用しているので、
- AWS SDKのメソッド呼び出し:
new AWS['ManagedBlockchain']['getNetwork'](parameters)
- AWS::IAM::Policyのアクション:
managedblockchain:GetNetwork
となります。
{
"Resources": {
"getNetworkTest425BEE14": {
"Type": "Custom::AWS",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"AWS679f53fac002430cb0da5b7982bd22872D164C4C",
"Arn"
]
},
"Create": {
"service": "ManagedBlockchain",
"action": "getNetwork",
"parameters": {
"NetworkId": "n-XXXXXXXXXXXXXXXXXXXXXXXXXX"
},
"physicalResourceIdPath": "Network.Id"
}
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete",
"Metadata": {
"aws:cdk:path": "UseCdkCustomResourcesStack/getNetworkTest/Resource/Default"
}
},
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": {
"Fn::Join": [
"",
[
"lambda.",
{
"Ref": "AWS::URLSuffix"
}
]
]
}
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
},
"Metadata": {
"aws:cdk:path": "UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/Resource"
}
},
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "managedblockchain:GetNetwork",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E",
"Roles": [
{
"Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2"
}
]
},
"Metadata": {
"aws:cdk:path": "UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/DefaultPolicy/Resource"
}
},
"AWS679f53fac002430cb0da5b7982bd22872D164C4C": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AWS679f53fac002430cb0da5b7982bd2287CodeS3BucketF55839B6"
},
"S3Key": {
"Fn::Join": [
"",
[
{
"Fn::Select": [
0,
{
"Fn::Split": [
"||",
{
"Ref": "AWS679f53fac002430cb0da5b7982bd2287CodeS3VersionKey3C45B02F"
}
]
}
]
},
{
"Fn::Select": [
1,
{
"Fn::Split": [
"||",
{
"Ref": "AWS679f53fac002430cb0da5b7982bd2287CodeS3VersionKey3C45B02F"
}
]
}
]
}
]
]
}
},
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2",
"Arn"
]
},
"Runtime": "nodejs10.x"
},
"DependsOn": [
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleDefaultPolicyD28E1A5E",
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2"
],
"Metadata": {
"aws:cdk:path": "UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/Resource",
"aws:asset:path": "asset.01d077466681f165ec462417f525188e4be806c35136c1a5e4bdcd3b71c12942",
"aws:asset:property": "Code"
}
}
},
"Parameters": {
"AWS679f53fac002430cb0da5b7982bd2287CodeS3BucketF55839B6": {
"Type": "String",
"Description": "S3 bucket for asset \"UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/Code\""
},
"AWS679f53fac002430cb0da5b7982bd2287CodeS3VersionKey3C45B02F": {
"Type": "String",
"Description": "S3 key for asset version \"UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/Code\""
},
"AWS679f53fac002430cb0da5b7982bd2287CodeArtifactHash49FACC2E": {
"Type": "String",
"Description": "Artifact hash for asset \"UseCdkCustomResourcesStack/AWS679f53fac002430cb0da5b7982bd2287/Code\""
}
},
"Outputs": {
"BcNetworkId": {
"Description": "The message that came back from the Custom Resource",
"Value": {
"Fn::GetAtt": [
"getNetworkTest425BEE14",
"Network.Id"
]
}
}
}
}
Lambda-Backedカスタムリソースで利用されてるLambda関数の実行ログをみてみます。
> aws logs get-log-events \
--log-group-name /aws/lambda/UseCdkCustomResourcesStac-AWS679f53fac002430cb0da5-2LAVFV782HQ4 \
--log-stream-name '2019/07/25/[$LATEST]3954b08a05354dcf97a63939586c5575' \
--query "events[*].message"
[
"START RequestId: 44b992e7-fb71-4c19-bf44-af78d7f79520 Version: $LATEST\n",
"2019-07-25T02:41:05.271Z\t44b992e7-fb71-4c19-bf44-af78d7f79520\tINFO\t{\"RequestType\":\"Create\",\"ServiceToken\":\"arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:UseCdkCustomResourcesStac-AWS679f53fac002430cb0da5-2LAVFV782HQ4\",\"ResponseURL\":\"https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/(略)\",\"StackId\":\"arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/UseCdkCustomResourcesStack/7d97bb10-ae85-11e9-93b7-0a51b82e168a\",\"RequestId\":\"73e446ee-e9e6-4d7d-9e5f-fd400865dec6\",\"LogicalResourceId\":\"getNetworkTest425BEE14\",\"ResourceType\":\"Custom::AWS\",\"ResourceProperties\":{\"ServiceToken\":\"arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:UseCdkCustomResourcesStac-AWS679f53fac002430cb0da5-2LAVFV782HQ4\",\"Create\":{\"service\":\"ManagedBlockchain\",\"action\":\"getNetwork\",\"physicalResourceIdPath\":\"Network.Id\",\"parameters\":{\"NetworkId\":\"n-XXXXXXXXXXXXXXXXXXXXXXXXXX\"}}}}\n",
"2019-07-25T02:41:05.272Z\t44b992e7-fb71-4c19-bf44-af78d7f79520\tINFO\tAWS SDK VERSION: 2.488.0\n",
"2019-07-25T02:41:06.112Z\t44b992e7-fb71-4c19-bf44-af78d7f79520\tINFO\tResponding {\"Status\":\"SUCCESS\",\"Reason\":\"OK\",\"PhysicalResourceId\":\"n-XXXXXXXXXXXXXXXXXXXXXXXXXX\",\"StackId\":\"arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/UseCdkCustomResourcesStack/7d97bb10-ae85-11e9-93b7-0a51b82e168a\",\"RequestId\":\"73e446ee-e9e6-4d7d-9e5f-fd400865dec6\",\"LogicalResourceId\":\"getNetworkTest425BEE14\",\"NoEcho\":false,\"Data\":{\"Network.Id\":\"n-XXXXXXXXXXXXXXXXXXXXXXXXXX\",\"Network.Name\":\"TestNetwork\",\"Network.Description\":\"TestNetworkDescription\",\"Network.Framework\":\"HYPERLEDGER_FABRIC\",\"Network.FrameworkVersion\":\"1.2\",\"Network.FrameworkAttributes.Fabric.OrderingServiceEndpoint\":\"orderer.n-XXXXXXXXXXXXXXXXXXXXXXXXXX.managedblockchain.us-east-1.amazonaws.com:30001\",\"Network.FrameworkAttributes.Fabric.Edition\":\"STARTER\",\"Network.VpcEndpointServiceName\":\"com.amazonaws.us-east-1.managedblockchain.n-XXXXXXXXXXXXXXXXXXXXXXXXXX\",\"Network.VotingPolicy.ApprovalThresholdPolicy.ThresholdPercentage\":50,\"Network.VotingPolicy.ApprovalThresholdPolicy.ProposalDurationInHours\":24,\"Network.VotingPolicy.ApprovalThresholdPolicy.ThresholdComparator\":\"GREATER_THAN\",\"Network.Status\":\"AVAILABLE\"}}\n",
"END RequestId: 44b992e7-fb71-4c19-bf44-af78d7f79520\n",
"REPORT RequestId: 44b992e7-fb71-4c19-bf44-af78d7f79520\tDuration: 1159.89 ms\tBilled Duration: 1200 ms \tMemory Size: 128 MB\tMax Memory Used: 38 MB\t\n"
]
ログに出力されてるCFnへ返すJSONをみてみるとData
がAWS SDKのgetNetwork
で得られる情報となりフラット化されているのが確認できます。
{
"Status": "SUCCESS",
"Reason": "OK",
"PhysicalResourceId": "n-XXXXXXXXXXXXXXXXXXXXXXXXXX",
"StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/UseCdkCustomResourcesStack/7d97bb10-ae85-11e9-93b7-0a51b82e168a",
"RequestId": "73e446ee-e9e6-4d7d-9e5f-fd400865dec6",
"LogicalResourceId": "getNetworkTest425BEE14",
"NoEcho": false,
"Data": {
"Network.Id": "n-XXXXXXXXXXXXXXXXXXXXXXXXXX",
"Network.Name": "TestNetwork",
"Network.Description": "TestNetworkDescription",
"Network.Framework": "HYPERLEDGER_FABRIC",
"Network.FrameworkVersion": "1.2",
"Network.FrameworkAttributes.Fabric.OrderingServiceEndpoint": "orderer.n-XXXXXXXXXXXXXXXXXXXXXXXXXX.managedblockchain.us-east-1.amazonaws.com:30001",
"Network.FrameworkAttributes.Fabric.Edition": "STARTER",
"Network.VpcEndpointServiceName": "com.amazonaws.us-east-1.managedblockchain.n-XXXXXXXXXXXXXXXXXXXXXXXXXX",
"Network.VotingPolicy.ApprovalThresholdPolicy.ThresholdPercentage": 50,
"Network.VotingPolicy.ApprovalThresholdPolicy.ProposalDurationInHours": 24,
"Network.VotingPolicy.ApprovalThresholdPolicy.ThresholdComparator": "GREATER_THAN",
"Network.Status": "AVAILABLE"
}
}
動作確認ができてスタックが不要になったらcdk destroy
コマンドでスタック削除ができます。
> cdk destroy
Are you sure you want to delete: UseCdkCustomResourcesStack (y/n)? y
UseCdkCustomResourcesStack: destroying...
✅ UseCdkCustomResourcesStack: destroyed
cdk bootstrap
で作成されたスタックを削除するAWS CDKのコマンドは見当たらないのでAWS CDKを今後利用しないのであれば、CFnでCDKToolkit
スタックを削除すればよさそうです。
まとめ
実際に動かして出力されるファイルやログを確認することで、AWS CDKがどのように機能しているのか知ることができました。
Infrastructure as Codeが主流になるとするならば、AWS CDKは抑えておいて損はないかなと思います。
参考
AWS クラウド開発キット (CDK) – TypeScript と Python 用がご利用可能に | Amazon Web Services ブログ
https://aws.amazon.com/jp/blogs/news/aws-cloud-development-kit-cdk-typescript-and-python-are-now-generally-available/
aws-cdk-examples/index.ts at master · aws-samples/aws-cdk-examples
https://github.com/aws-samples/aws-cdk-examples/blob/master/typescript/lambda-cron/index.ts
aws-cdk/packages/@aws-cdk/custom-resources at master · aws/aws-cdk
https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/custom-resources
aws-cdk/packages/@aws-cdk/aws-cloudformation at master · aws/aws-cdk
https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-cloudformation
Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(使い方編) - Qiita
https://qiita.com/kai_kou/items/dc7dbe947a7cc6d0a8db
Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークをさくっと構築するAWS CloudFormationのテンプレートを作ってみた(解説編) - Qiita
https://qiita.com/kai_kou/items/ab51cb7d16c680f591d2
AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない - Qiita
https://qiita.com/kai_kou/items/61a2b3c69ae2af4f2e40
AWS CloudFormationのLambda-BackedカスタムリソースでネストされてるっぽいJSONを返す方法 - Qiita
https://qiita.com/kai_kou/items/d030117c694531410203