Alexa SkillをCloudFormation(SAM)で管理する
re:inventでしれっと発表された、Alexa::Ask::Skillが気になって仕方なかったので挑戦して来ました。 事前準備 まずS3にSkill PackageのZIPファイルを用意する必要があります。作り方につ […]
目次
re:inventでしれっと発表された、Alexa::Ask::Skill
が気になって仕方なかったので挑戦して来ました。
事前準備
まずS3にSkill PackageのZIPファイルを用意する必要があります。作り方については以前記事を書いていますので、そちらを参考にしてください。
これをS3にアップロードしておきましょう。
Lambdaのソースコードを用意する
今回のディレクトリ構成です。Lambdaのコードをルートディレクトリに置いていますが、特に理由はありませんので好みで変えてください。
$ tree -I node_modules
.
├── .envrc
├── deploy.sh
├── index.js
├── package-lock.json
├── package.json
└── template.yml
0 directories, 6 files
環境変数で以下を使います。実運用時にはSSMへ置いておくとよいでしょう。
export S3_BUCKET=YOUR_S3_BUCKET_NAME
export ClientId=LWA_CLIENT_ID
export ClientSecret=LWA_CLIENT_SECRET
export RefreshToken="Atzr|LWA_REFRESH_TOKEN"
export VendorId=VENDOR_ID
export SkillPackageSourceBucketKey=skillpackage.zip
export SkillPackageSourceBucketName=YOUR_S3_BUCKET_NAME
Lambdaのコードは以下のようにしました。最小構成です。
/* eslint-disable func-names */
/* eslint-disable no-console */
const Alexa = require('ask-sdk-core');
const LaunchRequestHandler = {
canHandle: (handlerInput) => true,
handle(handlerInput) {
const speechText = 'Welcome to the Alexa Skills Kit, you can say hello!';
return handlerInput.responseBuilder.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
},
};
const ErrorHandler = {
canHandle: (handlerInput) => true,
handle(handlerInput, error) {
console.log(`Error handled: $ {
error.message
}`);
return handlerInput.responseBuilder.speak('Sorry, I can\'t understand the command. Please say again.')
.reprompt('Sorry, I can\'t understand the command. Please say again.')
.getResponse();
},
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder.addRequestHandlers(LaunchRequestHandler)
.addErrorHandlers(ErrorHandler)
.lambda();
CloudFormation Templateの作成
いよいよテンプレートを作りましょう。Lambdaをデプロイするので、SAMを使います。
---
AWSTemplateFormatVersion: '2010-09-09'
Description: example alexa skills
Transform: 'AWS::Serverless-2016-10-31'
Parameters:
ClientId:
Description: The client ID of the application registered for Login with Amazon (LWA). This information can be found on the Amazon developer portal?s LWA page.
Type: String
ClientSecret:
Description: The client secret of the application registered for Login with Amazon (LWA). This information can be found on the Amazon developer portal?s LWA page.
NoEcho: true
Type: String
RefreshToken:
Description: The refresh token used to request new access tokens from LWA.
NoEcho: true
Type: String
SkillPackageSourceBucketKey:
Description: The location and name of the .zip file that contains the skill package.
Type: String
SkillPackageSourceBucketName:
Description: The name of the Amazon S3 bucket where the .zip file that contains the skill package is stored.
Type: String
VendorId:
Description: The unique identifier of an Amazon digital vendor account.
Type: String
TestingInstructions:
Description: Testing instructions about your skill for review
Type: String
SkillSummary:
Type: String
SkillDescription:
Type: String
SkillName:
Type: String
Resources:
AlexaSkillsKitServiceRole:
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref 'VendorId'
Effect: Allow
Principal:
Service: alexa-appkit.amazon.com
Version: 2012-10-17
Policies:
- PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Sub 'arn:aws:s3:::${SkillPackageSourceBucketName}/${SkillPackageSourceBucketKey}'
Version: 2012-10-17
PolicyName: !Sub 'MyExample-AlexaSkillsKitServiceRolePolicy'
RoleName: !Sub 'MyExample-AlexaSkillsKit'
Type: AWS::IAM::Role
SkillFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8.10
Events:
AlexaSkillEvent:
Type: AlexaSkill
AlexaSkill:
DependsOn:
- AlexaSkillsKitServiceRole
Type: "Alexa::ASK::Skill"
Properties:
SkillPackage:
S3Bucket: !Ref 'SkillPackageSourceBucketName'
S3BucketRole: !GetAtt 'AlexaSkillsKitServiceRole.Arn'
S3Key: !Ref 'SkillPackageSourceBucketKey'
Overrides:
Manifest:
publishingInformation:
locales:
ja-JP:
name: !Ref SkillName
description: !Ref SkillDescription
summary: !Ref SkillSummary
testingInstructions: !Ref TestingInstructions
apis:
custom:
endpoint:
uri: !GetAtt SkillFunction.Arn
AuthenticationConfiguration:
ClientId: !Ref 'ClientId'
ClientSecret: !Ref 'ClientSecret'
RefreshToken: !Ref 'RefreshToken'
VendorId: !Ref 'VendorId'
Outputs:
skillId:
Description: Your Alexa Skill ID
Value: !Ref AlexaSkill
LambdaFunctionArn:
Description: ARN of your Lambda Function
Value: !GetAtt SkillFunction.Arn
こいつで作成しているのは以下の3つです。
- Alexa側でS3のSkill PackageファイルをDLするためSTSを提供するIAMロール
- Alexaが使用するLambdaファンクション
- Alexaスキルそのもの
基本的にはSkill PackageのZIPファイルにある内容で対話モデルとスキル情報が作成されますが、SkillPackage.Overrides:
でmanifestのみ上書き可能です。上記サンプルではスキル名や説明文をCloudFormationのパラメーターで上書きしようとしています。
CloudFormationでデプロイする
最後にデプロイしましょう。build.sh
に以下のようなコマンドを入れておくと便利です。
#!/usr/bin/env bash
# ビルドパッケージ作成
aws cloudformation package --template-file ./template.yml --output-template-file template-output.yml --s3-bucket $S3_BUCKET
# デプロイ
aws cloudformation deploy \
--template-file ./template-output.yml --stack-name alexa-cfn --capabilities CAPABILITY_NAMED_IAM --region $AWS_REGION \
--parameter-overrides \
ClientId=$ClientId \
ClientSecret=$ClientSecret \
RefreshToken=$RefreshToken \
SkillPackageSourceBucketKey=$SkillPackageSourceBucketKey \
SkillPackageSourceBucketName=$SkillPackageSourceBucketName \
VendorId=$VendorId \
TestingInstructions='テスト' \
SkillSummary='これはCloudFormationテストです' \
SkillDescription='これはCloudFormationテストです' \
SkillName='テストスキル'
# Skill ID / Lambda ARNの確認
aws cloudformation describe-stacks --stack-name alexa-cfn | jq .Stacks[0].Outputs
あとはこれを実行してやればOKです。
トラブルシューティング
やっていてハマったのはだいたい以下の点です。
- LWAのtoken取得に失敗してスタック作成失敗
- IAMロール作り忘れてS3のzipファイルにアクセスできなくなる
Alexa::Ask::Skill
をDependsOnさせわすれてやはりS3にアクセスできなくなる
この辺りでめげた人はCodeStar推奨です。
Overwrideについて
いろいろ試してみましたが、基本的にはあまり使わない方が良さそうです。というのもexamplePhrasesを入れ替えようとしたときに、以下のエラーが出て来ました。
Skill Update failed. Error: publishingInformation.locales.ja-JP.examplePhrases – array is too long: must have at most 3 elements but instance has 6 elements
「3つしか許可していないのに、6つある」と言われているということは、置換ではなく追加されている様子です。そしてexamplePharsesを変えれないのにスキル名や説明文をここでoverwrideしてもレビューで引っかかるでしょう。
ということで、Skill情報についてはCloudFormationで無理に管理しない方が良さそうです。
結論
CodeStarに一式あるので、大人しくそれを使いましょう。ただしスキル数が増えてくると、CodePipelineやCodeCommit / Cloud9などで課金が来るので要注意です。
たぶん使い分けとしてはこういう形になるかなと思っています。
- チームでAlexaスキルを効率的に作りたい -> CodeStar
- 個人でゴミやトリビアスキルなどの横展開系スキルを作りたい -> SAM / CloudFormation
- TypeScript / Javaが至高 -> AWS CDK
- あまりAWSのサービスに依存したくない -> Serverless Framework
- そこまでCI / CDやリソース管理にこだわりない -> ASK CLI
- AWSアカウント持ってない -> Alexa Hosted Skill
コアの実装流用してスキル作ることが多いので、もうちょっとSAM / CDKまわり頑張ってみる予定です。