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まわり頑張ってみる予定です。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark