Serverless Web Application Workshop をCLIでやってみた Lab 4: Product Update Blog
サーバーレスアーキテクチャ Advent Calendar 2016 の25日目の記事です。
「Serverless Web Application Workshop をCLIでやってみた Lab 3: Administrative Interface」 の続きになります。
yamano3201.hatenablog.jp
大枠はこちらを参考。
qiita.com
README記載のコンソール作業をCLIで置き換えて構築を確認していくというものです。
github.com
ワークショップは全部で4つあります。
- Lab 1: Static Website Hosting
- Lab 2: Beta Sign-up Mailing List
- Lab 3: Administrative Interface
- Lab 4: Product Update Blog
今回は最後のLab 4: Product Update Blog について書いていきます。
- やること
- ブログ記事を保存するためのテーブルをAmazon DynamoDBで作成
- ブログ記事を保存するLambdaと、ブログ記事一覧を取得するLambdaを作成
- Lambda連携とCognito user pool認証を設定したAPI Gateway作成
- ワークショップ用に用意されたlab4のファイルをS3にsync
- 動作確認
- 最後に
やること
管理者がアップデートできるブログ機能の構築。ブログ記事を保存するテーブルをDynamoDBで作成し、記事一覧を取得するLambdaと記事をポストをするLambdaとIAM roleを作成し、API Gateway(GET/POST resource)でLambda Proxy連携とCognito user pool認証を設定してウェブアプリから呼び出せるAPI endpointを作成してウェブアプリ側に埋め込む。ウェブアプリで管理者としてログイン後、記事のポストと最新記事一覧が表示される事を確認。
ブログ記事を保存するためのテーブルをAmazon DynamoDBで作成
- パーティションキーにBlog、ソートキーにTimestampを指定して作成します。
$ export BLOG_TABLE=$(aws dynamodb create-table --table-name Wildrydes_Posts --attribute-definitions AttributeName=Blog,AttributeType=S AttributeName=Timestamp,AttributeType=N --key-schema AttributeName=Blog,KeyType=HASH AttributeName=Timestamp,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 | jq -r '.TableDescription.TableArn')
ブログ記事を保存するLambdaと、ブログ記事一覧を取得するLambdaを作成
- ロジックはREADMEに書かれているコードをコピーします。
- DynamoDBにブログ記事を保存するコードと、ブログ記事一覧を取得するコードになっています
- コードはzip化して渡します。
$ cat << EOF > index.js var aws = require('aws-sdk'); var dynamodb = new aws.DynamoDB({}); exports.handler = (event, context, callback) => { var body = JSON.parse(event.body); var params = { TableName: 'Wildrydes_Posts', Item: { Blog: { S: 'product-updates' }, Timestamp: { N: body.Timestamp }, Title: { S: body.Title }, Body: { S: body.Body } } }; dynamodb.putItem(params, (err, data) => { if (!err) { context.succeed({ statusCode: '200', headers: { 'Access-Control-Allow-Origin': '*'}, body: JSON.stringify({}) }); } else { context.fail(err); } }); }; EOF $ zip -r create_post.zip index.js $ export ACCOUNT=$(aws sts get-caller-identity | jq -r ".Account") $ export CREATE_POST=$(aws lambda create-function --region us-east-1 --function-name CreatePost --role arn:aws:iam::${ACCOUNT}:role/LambdaDynamoDBRole --runtime nodejs4.3 --handler index.handler --zip-file fileb://create_post.zip | jq -r '.FunctionArn')
$ cat << EOF > index.js var aws = require('aws-sdk'); var dynamodb = new aws.DynamoDB({}); exports.handler = (event, context, callback) => { var params = { TableName: 'Wildrydes_Posts', ScanIndexForward: false, KeyConditions: { Blog: { ComparisonOperator: 'EQ', AttributeValueList: [ { S: 'product-updates' } ], } } }; dynamodb.query(params,(err, data) => { if (!err) { var posts = []; data.Items.forEach((item) => { posts.push({ Timestamp: item.Timestamp.N, Title: item.Title.S, Body: item.Body.S }) }); context.succeed({ statusCode: '200', headers: { 'Access-Control-Allow-Origin': '*'}, body: JSON.stringify({ Posts: posts }) }); } else { context.fail(err); } }); }; EOF $ zip -r get_all_posts.zip index.js $ export GET_ALL_POSTS=$(aws lambda create-function --region us-east-1 --function-name GetAllPosts --role arn:aws:iam::${ACCOUNT}:role/LambdaDynamoDBRole --runtime nodejs4.3 --handler index.handler --zip-file fileb://get_all_posts.zip | jq -r '.FunctionArn')
Lambda連携とCognito user pool認証を設定したAPI Gateway作成
リソース作成
- Wildrydes APIはすでに作成されているので、そこにpostsリソースを作成します。
$ export API=$(aws apigateway get-rest-apis | jq -r '.items | map(select(.name == "Wildrydes"))' | jq -r '.[].id') $ export ROOT_ID=$(aws apigateway get-resources --rest-api-id ${API} | jq -r '.items | map(select(.path == "\/"))' | jq -r '.[].id') $ export RESOURCE=$(aws apigateway create-resource --rest-api-id ${API} --parent-id ${ROOT_ID} --path-part posts | jq -r '.id')
OPTIONSメソッド作成
- CORSを有効化するためにemailsリソースにOPTIONSメソッドを作成します。
$ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --authorization-type NONE --no-api-key-required --request-parameters '{}' $ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --integration-http-method POST --type MOCK --request-templates '{ "application/json": "{\"statusCode\": 200}" }' --passthrough-behavior WHEN_NO_MATCH $ aws apigateway put-method-response --rest-api-id $API --resource-id $RESOURCE --http-method OPTIONS --status-code 200 --response-models '{"application/json": "Empty"}' --response-parameters "method.response.header.Access-Control-Allow-Headers=false,method.response.header.Access-Control-Allow-Methods=false,method.response.header.Access-Control-Allow-Origin=false" $ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method OPTIONS --status-code 200 --response-templates '{"application/json": ""}' --response-parameters '{"method.response.header.Access-Control-Allow-Headers": "'"'"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"'"'" , "method.response.header.Access-Control-Allow-Methods": "'"'"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"'"'", "method.response.header.Access-Control-Allow-Origin": "'"'"'*'"'"'"}'
POSTメソッド作成
- postsリソースに対するPOSTメソッドを作成します。
- 認証の設定でuser poolを設定したAuthorizerを設定します。
- Lambda のCreatePost関数を POSTメソッドの送信先に設定します。
$ export AUTHORIZER=$(aws apigateway get-authorizers --rest-api-id ${API} | jq -r '.items | map(select(.name == "Wildrydes_Admin"))' | jq -r '.[].id') $ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --authorization-type COGNITO_USER_POOLS --authorizer-id ${AUTHORIZER} --no-api-key-required $ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --integration-http-method POST --type AWS_PROXY --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${CREATE_POST}/invocations" --content-handling CONVERT_TO_TEXT $ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --status-code 200 --response-templates '{"application/json": ""}' $ aws apigateway put-method-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method POST --status-code 200 --response-models '{"application/json": "Empty"}' $ aws lambda add-permission --function-name CreatePost --statement-id prod --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:us-east-1:${ACCOUNT}:${API}/*/POST/posts"
GETメソッド作成
$ aws apigateway put-method --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --authorization-type "NONE" --no-api-key-required $ aws apigateway put-integration --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --integration-http-method POST --type AWS_PROXY --uri "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${GET_ALL_POSTS}/invocations" --content-handling CONVERT_TO_TEXT $ aws apigateway put-integration-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --status-code 200 --response-templates '{"application/json": ""}' $ aws apigateway put-method-response --rest-api-id ${API} --resource-id ${RESOURCE} --http-method GET --status-code 200 --response-models '{"application/json": "Empty"}' $ aws lambda add-permission --function-name GetAllPosts --statement-id prod --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:us-east-1:${ACCOUNT}:${API}/*/GET/posts" $ aws apigateway create-deployment --rest-api-id ${API} --stage-name prod
ワークショップ用に用意されたlab4のファイルをS3にsync
- アプリコードは用意されたものをsyncします。
$ aws s3 sync lab4/ s3://${BUCKET_NAME} $ open https://$(cat cloudfront.json | jq -r '.Distribution.DomainName')/admin
動作確認
- ログインしてNew Blog Postに進みます。
- TitleとBodyを記入してPOSTボタンをクリックします。
- ブログページにリダイレクトされ、作成した投稿が表示されていることを確認します。