production.log

ピクスタ株式会社でエンジニアのマネージャーをやっている星直史のブログです。

RedashでAWS DynamoDBに対してDQLを発行しデータ取得する方法

概要

表題の通りです。 RedashでDynamoDBからデータ取得する場合DQLという、SQLライクな書き方でデータを取得することになります。
今回はDynamoのIndexを指定して絞り込みを行う際に、ちょいハマりしたので、メモとして記録します。

AWS DynamoDBの設定

テーブル構造

  • Table name: test_tables
  • Primary partition key: partition_id (Number)
  • Primary sort key: sort_id (Number)

インデックス

  • Name: partition_id-index
  • PartitionKey: partition_id (Number)

DQLの書き方

test_tablespartition_idで絞り込んでデータ抽出したい場合、DQLでは下記の書き方となります。

SELECT *
FROM test_tables
WHERE partition_id = 60515 USING partition_id-index; # USINGでindex名を指定することが重要

WHERE句で属性名だけで条件指定したいところですが、DynamoDBの特性上、インデックスを指定しなければなりません。
そのため、上記のように、partition_idはpartition_id-indexというインデックスやで〜
というように明示させる必要があります。

もし、USINGを書かないと下記エラーが発生します。
No index specified with USING <index>, but multiple possibilities for query: TABLE, partition_id-index

自前で立てたRedash(0.12.0+b2449)を最新1.0.3にupgradeする方法

概要

Redashが0.12.0の時に、AWS AMIから立ち上げたのですが、
この度、メジャーバージョンアップしたので、手動でupgradeした時のメモです。

手順もなにも、ドキュメントがあるのでその通りにやるだけですが、 コマンド実行する際に設定を変更する必要があったので、その説明をします。

手順

ドキュメントの通りwgetし、実行権限を付与。

wget https://raw.githubusercontent.com/getredash/redash/master/bin/upgrade
chmod +x upgrade

その後、処理を走らせます。

sudo ./upgrade

すると、こんなメッセージが出ます。

Before upgrading to this version, please make sure to do the following changes to your /opt/redash/.env file:
    
1. If you have local PostreSQL database, you will need to update the URL from `postgresql://redash` to `postgresql:///redash`.
2. Remove the `REDASH_STATIC_ASSETS_PATH` definition.

Make sure to complete these changes before doing the actual upgrade.

この変更をしなければ、DBが見当たらない旨のエラーが吐かれてアップグレードできないので、 /opt/redash/.envを開き、下記の通り修正します。

  1. postgresql://redashpostgresql:///redashに修正。*1
  2. REDASH_STATIC_ASSETS_PATHを消す。

まとめ

ドキュメントには書かれておらず、実行時に注意してくる感じなので、
英語読めない><
とりあえずy ><
ってやってると見落としまうので、上記修正は忘れず対応しましょう!

*1:/が一つ多くなる

【ServerlessFramework】DynamoDB Streamsでデータの更新をトリガーにLambdaを動かす方法。

概要

表題の通り、DynamoDB Streamsでデータの更新をトリガーにLambdaを動かす方法について説明します。

AWSマネジメントコンソール上でのDynamoDB Streamsの有効化

まず、AWSマネジメントコンソールでStreamsを有効化する必要があります。 DynamoDB > Tables > テーブル選択 > Overview > Stream details > ManageStreamを選択

これを選択すると、ストリームを経由して渡されるデータ構造を選択できるラジオボタンが表示されます。 それぞれの意味は下記の通りです。

  • [Keys only] — 変更された項目のキー属性のみ取得。
  • [New image] — 変更後のデータのみ取得。
  • [Old image] — 変更前のデータのみ取得。
  • [New and old images] — 変更後、変更前のデータの両方取得。

あとは作成すれば、ARNが表示されます。

serverless.ymlの設定

次にserverless.ymlに設定を書き、Lambdaと紐づける必要があります。

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - dynamodb:DescribeStream
        - dynamodb:GetRecords
        - dynamodb:GetShardIterator
        - dynamodb:ListStreams
      Resource:
        - "${AWSマネジメントコンソール上に表示されているARN}"
functions:
  dynamo_stream:
    handler: handler.dynamo_stream
    events:
      - stream:
          arn: "${AWSマネジメントコンソール上に表示されているARN}"
          batchSize: 1
          startingPosition: TRIM_HORIZON
          enabled: true

functions.dynamo_stream.events.stream内の各項目は

batchSize: 一度に処理するストリームの数を表します。1~10000まで指定できます。1以上の値を指定した場合、Lambdaの処理の中でループで処理をする必要があります。 startingPosition: 処理開始するストリームの位置を指定します。最新から取得か最古から取得かのどちらかを指定できます。LATEST | TRIM_HORIZON enabled true: 有効 / 無効を指定 true | false

また、eventの設定以外には、IAMの設定も必要となります。 上記の設定項目は最低限の設定です。これを設定しないとsls deploy -vをした時に下記エラーに見舞われます。

  Serverless Error ---------------------------------------
 
     An error occurred while provisioning your stack: DynamoUnderscorestreamUnderscoreproxyEventSourceMappingDynamodbDevitems
     - Cannot access stream AWSマネジメントコンソール上に表示されているARN
     Please ensure the role can perform the GetRecords, GetShardIterator,
     DescribeStream, and ListStreams Actions on your stream
     in IAM..
 

取得できるJSON

{
  "Records": [
    {
      "eventID": "78h9r97gawegaj7ddnga6e6w",
      "eventName": "MODIFY",
      "eventVersion": "1.1",
      "eventSource": "aws:dynamodb",
      "awsRegion": "us-west-2",
      "dynamodb": {
        "ApproximateCreationDateTime": 1492654321,
        "Keys": {
          "id": {
            "N": "123456789"
          }
        },
        "NewImage": {
          "column_a": {
            "N": "2"
          }
        },
        "OldImage": {
          "column_a": {
            "N": "1"
          }
        },
        "SequenceNumber": "405760000000000123456789",
        "SizeBytes": 123,
        "StreamViewType": "NEW_AND_OLD_IMAGES"
      },
      "eventSourceARN": "AWSマネジメントコンソール上に表示されているARN"
    }
  ]
}

しっかり新旧両方のデータが取得できていますね。

解決できなかった点

  1. ServerlessFrameworkだけで完結できない。 説明の通り、AWS マネジメントコンソールでストリームを追加し、それを元にserverless.ymlを設定しなければなりません。 deploy時に失敗するから気づけるとは思うのですが、漏れが発生する気しかしないです。

  2. 設定できるストリームが1テーブルにつき1つ 当初、レコード追加時のストリーム、Aカラムを更新したときのストリーム….といったように、細かく設定できると思っていたのですが、 試して見た結果、「何かしらの変更が加えられたらJSONに新旧両方のデータを詰めて渡すからよしなにやってね」って感じでした。 DynamoDBにカラムが新規作成されたらLambdaAを、更新の時はLambdaBを実行しようとした場合、テンプレートメソッドパターンや、プロキシパターンなり使わないと収集つかなくなりそうな印象を受けました。

まとめ

  1. AWSマネジメントコンソールでStreamsを有効化
  2. serverless.ymlに設定

この2ステップだけでDynamoDBストリームを受け取りLambdaに渡せるのはやはり便利です。 ただ、マネコンとserverless.ymlの両方に設定を加えなければならないのは漏れが発生しそうなので、もう一声といったところでしょうか。

【ServerlessFramework】S3のオブジェクト格納をトリガーにLambdaを動かす方法

概要

S3に何かしらのオブジェクトを配置したことをトリガーにLambdaを動かす設定について紹介します。 runtimeはnode.jsです。

lambdaの処理を書く

lambdaにはトリガーが正常に動いていることだけを確認すれば良いので、 console.log("hello")とだけ書いておきます。 ファイル名はhandler.jsにしておきます。

'use strict';

module.exports.hello = (event, context, callback) => {
  console.log(JSON.stringify(event, undefined, 1));
  console.log("hello");
  const response = { 
    statusCode: 200,
    body: "{\"msg\": \"success\"}"
  };  
  callback(null, response);
};

serverless.ymlの設定

functions:
  hello:
    handler: handler.hello
    events:
      - s3:
          bucket: "uploads"
          event: s3:ObjectCreated:*
          rules:
            - prefix: original-files/
    memorySize: 128 
#resources: # もしリサイズ処理をするなら、リサイズ後のデータを格納するバケットが必要
#  Resources:
#    UploadBucket:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: "lambda-result"

functions.hello.events.s3ブロックは、トリガーを検知するS3バケットの設定 & 生成時に実行されます。 そのため、serverlessを使用しないでAWSマネジメントコンソールから手動で作ったバケットを後からserverlessで操作しようとすると、下記のエラーに見舞われます。

  Serverless Error ---------------------------------------
     An error occurred while provisioning your stack: S3BucketBucketName
     - bucket-name already exists.

LambdaのTriggerだけを後から追加はできない(というか、しにくいというか、避けたい)ってことですね。

【ServerlessFramework】AWS LambdaとCognitoで作るセキュアなS3へのオブジェクト格納

概要

ユーザーがアップロードした画像データをS3に保存するケースにおいて Serverless Frameworkを使用して、AWS API Gateway 経由しLambdaで処理をするときに、 Cognitoで認証したユーザーのIAMをSTSを使用してS3にPUTするときの説明です。

f:id:watasihasitujidesu:20170417083912p:plain

今日は上記の図の四角で囲った部分の話をします。

serverless.yml

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        - "arn:aws:s3:::yukashita-image-uploads/original-files/${self:provider.stage}/*"
functions:
  auth:
    handler: auth.auth
  upload:
    handler: handler.upload
    events:
      - http:
          path: upload
          method: post
          authorizer: auth
    response:
      headers:
        Content-Type: "'application/json'"
      template: $input.path('$')
resources:
  Resources:
    UploadBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: "bucket"

iamRoleStatementsをコメントアウトしていますが、Lambda自体にIAM(認可)の必要はありません。 というのも、認証されたCognitoIdentity UserのIAMで操作をするので、 必要な権限はCognitoのIAMに設定していきます。(設定自体はCognitoのIAM設定で説明します。)

Lambda

'use strict';

const aws = require("aws-sdk");

var cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18',
    region: 'us-west-2'
});

module.exports.upload = (event, context, callback) => {
  var AWS = require('aws-sdk');
  AWS.config.region = 'us-west-2';
  
  console.log(AWS.config.credentials) // Enviroment Credentialとなる。つまり、Lambdaで設定されているCredential

  var options = { 
    params: {
      apiVersion: '2006-03-01',
      Bucket: "bucket"
    }   
  };  
  var bucket = new AWS.S3(options);

  var idToken = event.headers.Authorization; // 認証されたCognitoIdentity UserのidTokenを取得
  
  // AWS IAM STSでCognitoAuthUserに紐づいたクレデンシャルを取得。
  // CognitoIdentityCredentialsは内部的にSTSを返してくれる。
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    region: "us-west-2",
    IdentityPoolId : 'us-west-2:your-identity--pool-id',
    RoleArn: "arn:aws:iam::123456789:role/Cognito_your_poolAuth_Role", // AuthRoleであることに注意
    Logins : { 
      'cognito-idp.us-west-2.amazonaws.com/us-west-2_ABCDEFG' : idToken // idToken != accessTokenであること
    }   
  }); 

  // AWS.config.credentialsを書き換えただけでは反映はされない
  // この時点では、AWS.config.credentials.needsRefresh == trueとなる
  
  // AWS.config.credentials.getを呼ぶと、未反映の場合はAWS.config.credentials.refreshを呼び出し、反映させる
  AWS.config.credentials.get(function(err) {
    // この時点で、AWS.config.credentials.needsRefresh == falseとなる
    // Cognito Credentialとなる。
    console.log(AWS.config.credentials)
    if (err) console.log(err);

    var params = { 
      Key: ["bucket", AWS.config.credentials.identityId, "object_name.jpg"].join("/"), // AWS.config.credentials.identityIdで認証をされたCognitoIdentity Userしか触れない領域を確保する。
      ContentType: "image/jpg",
      Body: event.body
    };
    bucket.putObject(params, function(err, data) {
      if (err) {
        console.log("Error", err);
      } else {
        console.log("Success", err);
      }
    });
  });
  callback(null, event);
};

ちょっと長いのですが、注意点はコードコメントにしています。 かいつまんでポイントを説明します。

  1. Lambda起動直後はAWS.config.credentialsEnviroment Credentialとなる。つまり、Lambdaで設定されているCredentialです。この時点ではLambdaにはなんの権限がないので、AWSリソースにアクセスしようとした場合、AccessDenyになります。
  2. AWS.CognitoIdentityCredentialsでCognitoIdentity Userのクレデンシャルを取得します。また、このときに渡すTokenはidTokenです。accessTokenではありません。公式ドキュメント
  3. AWS.config.credentials = new AWS.CognitoIdentityCredentialsとしただけでは未反映の状態です。AWS.config.credentials.getで反映させる必要があります。
  4. AWS.config.credentials.get後のAWS.config.credentialsCognitoCredentialとなります。これでCognitoで設定したIAMの権限に切り替わります。

最後にCognitoのIAM設定を確認します。

CognitoのIAM設定

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::bucket/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}

Resourceで設定する内容は、LambdaのS3.putObjectの際に指定するKeyの許可について指定します。 ${cognito-identity.amazonaws.com:sub}を指定していますが、これはCognito Identity poolのIdentityIDです。
設定するIAM 変数名がものすごくわかりにくいのですが、Cognito User pool の Userに割り当てられるsubではありません。*1

このIAM変数を利用すると、s3://bucket/Cognito Identity poolのIdentityID/オブジェクトにしかアクセスできなくなります。 言い換えると、他のユーザーの情報にアクセスが不可能になります。 また、同様にDynamoDB Finegrain アクセスも同じ概念となります。

解決できなかったこと

今回はCognitoのIAMをAWSマネジメントコンソールから手で書き換えるしかありませんでした。 理由はserverless.ymlでIAMのresourceを作成できないからです。 具体的に言うと、IAM > Roles > Trust relationship の Principal > Federatedをcognito-identity.amazonaws.comに設定できません。

そのため、ここだけは手動でやらなくてはならないという悲しい状況でした。

Cognitoは設定値が紛らわしく、どれを使えば良いの?といった感じです。 今回出てきたものは、下記の通りです。

  • idToken != accessToken
  • Cognito Identity pool の IdentityID != Cognito User pool の User sub

まとめ

一言でまとめると、 Lambdaの処理中にCognito Identity で設定したIAMを使う だけだと思います。
serveless.ymlでIAMを定義できなかったり、AWS.congif.credentialsがrefreshしないと反映されなかったり、そもそもCognitoの設定値が紛らわしかったりで、散々ハマりましたが、 おかげで各サービスの理解が深まりました。

*1:ここでめちゃくちゃハマりました。

開発マネージャーのオレが実践している科学的なストレスコントロール方法

背景

直近18ヶ月の間にメンバー => リーダー => マネージャーと一気に職位があがり、 ストレスの質の違いを実感し、うまくストレスを逃していかないと消耗戦になると感じたため、 ストレススコントロールについて模索してきました。

最近、ストレスコントロール術を確立してきた感があるため、現時点で行なっている方法を紹介します。

概要

これまで「ストレスは目に見えない何か」だと考えていましたが、知らない敵に対して攻略はできないので、科学的アプローチでストレスを殴ることにしました。

大きく分けると、以下の通りです。

  • 思考を変えるメンタル的なアプローチ
  • 脳内化学物質をコントロールするアプローチ

脳内化学物質をコントロールとか、いきなり危ないこと言ってますが、話を先に進めます。

思考を変えるメンタル的なアプローチ

ストレスを理解する

まずは敵を知るために、ストレスを感じるメカニズムと、対処方法について、科学的に証明された文献を元に理解します。

そこで、この本です。

健康心理学者のケリー・マクゴニガル氏の著書です。*1
何が良いって、著者が すごく綺麗 主張の裏付けとして、必ずデータをもって証明するために実験を繰り返していることです。 基本的なスタンスとしては、「ストレスは困難を乗り越えて強くなる為の試練というように考え方を変えるとストレスホルモンの分泌が抑えられるよ」という思考方法を変えることです。

“ストレスは悪だ"というステレオタイプに対して、その常識を否定していきます。
また、読むときの心持ちとして、疑ってかかると「ただの精神論かよ」で終わるかもしれませんが、肯定的な意見を持ちつつ読むと、認知バイアスがかかることを避けながら内容を理解できると思います。

また、ストレスを感じている時に怒っている体の状況についても書かれています。
人間は危険や恐怖を感じた時に「闘争・逃走本能」という危険を緊急回避するために、アドレナリンを放出し、全力で逃げようとします。現代社会で言う危険や恐怖は、仕事で失敗をする / プレゼンをする にあたると思います。

その時に、前述の「ストレスは困難を乗り越えて強くなる為の試練」というマインドを持つと、「チャレンジ反応」と呼ばれる状態に切り替わります。
簡単に言うと、"よし、やってやるぞ!"という状態です。
この状態になると、アドレナリンは放出されますが、恐怖は感じていない、いわゆる自信がある状態になります。

このように、ストレスの正体を分析した上で、「ではXという行動や気持ちを持つと結果がYになるのでは?」という仮説のもと、実験が行われているので、非常に説得力がありました。

脳内化学物質をコントロールするアプローチ

交感神経と副交感神経のバランスをとる

ストレスを感じると交感神経が優位になると書籍が学びました。
また、本屋で周りを見渡すと、ヨガ、瞑想、マインドフルネスといったカテゴリの本が多くみられることに気づき、 交感神経と副交感神経のバランスを取る方法について学んでいきました。

副交感神経を簡単な言葉で説明すると、心を鎮めリラックスした状態のときに活発になる神経です。
交感神経とどちらが活発な方が良いかではなく、両方のバランスが取れた状態がベストです。

また、初めに、自身の仮説として、ストレスフルな仕事や、食生活、睡眠時間が不規則だったりすると、副交感神経が優位になるタイミングが少なくなり、バランスが崩れていった結果、交感神経が優位な状態が続き、心身ともに休まらないのでは?という仮説を立てました。

その仮説を元に、副交感神経と改善するための行動を調べ、脳内神経伝達物質のセロトニン / メラトニンを排出すると優位になりやすいことを学びました。

以下はそれぞれ、脳内神経伝達物質を排出や、交感神経と副交感神経のバランスを整えるための行動です。

早寝早起き

朝に太陽の光を浴びるとセロトニンが排出されます。 セロトニンは交感神経を優位にさせることで身体を覚醒させます。メラトニンは副交感神経を優位にし、眠気を誘発させます。また起床と同時にメラトニンの排出が激減し、15時間前後から再度排出されます。

  • セロトニン => 日中の活動で重要となる
  • メラトニン => 夜間の活動で重要となる

といったところです。
また、セロトニンとメラトニンの安定して排出させることと、その効用を最大限高めるために、 日の出と同時に起きるように心がけています。

結果的に朝5時~6時の間に目覚め、21時~22時前後に眠るので生活のリズムも整います。

※主題とはズレますが、22時~2時の間に排出される成長ホルモンの効用も最大限得るために22時には眠っている状態を作っています。*2

瞑想をする

詳細な効果は前述の本や他の本に譲りますが、安全が確保された場所で、精神コントロールをすることで、 脳の海馬や眼窩前頭皮質が活発になり、「記憶」「意思や感情を調節」する機能がより発達していきます。

また、呼吸のコントロールを同時に行えるので、心拍数を下げ、より睡眠導入をしやすくしています。
瞑想-呼吸のコントロールは心拍変動という後述する効果もあるため、毎晩やっています。

筋トレ

瞑想のところで触れた心拍変動についてです。
人間は息を吸うと心臓の鼓動が早くなり、息を吐くと鼓動が遅くなります。
また、リラックスしているは、呼吸による心拍変動は高い状態なります。
逆に、緊張している時は常に心拍数が高いので心拍変動は低い状態になります。*3
この心拍変動が高い状態に素早く切り替えることで、リラックス状態、つまり副交感神経を優位にしやすくできます。

そして、心拍変動は鍛えることができます。
筋トレは、単純に運動 - 休息を繰り返し、心臓の鼓動を上下させることで、心拍変動の高い状態を作り、結果的に精神面にも効果をもたらします。

また、副次的な要素ですが、筋トレのあとにプロテインを飲むと、より筋肉が育つのですが、このプロテインには、必須アミノ酸が豊富に入っています。

必須アミノ酸については次で説明しますが、脳内神経伝達物質であるセロトニンと関わりがあります。

食生活

メラトニンを分泌するためにはセロトニンが必要です。
また、セロトニンはトリプトファンを摂取することで合成されます。
トリプトファンは必須アミノ酸です。

つまり、肉を大量に食べます(白目)
これは、以前紹介した食事管理と関連があります。
具体的な食生活については過去の記事に譲りますが、基本的に同じ食生活です。

良質な油と良質な肉を食え!

銭湯(サウナ) で温冷交換浴

銭湯でサウナと水風呂に交互に入る温冷交換浴をします。

これはサウナで体温を高めると脳が
脳「アイエエエエ!!!! アツイ!! アツイニゲテ!!」
というように、先ほど説明した「闘争・逃走本能」が活躍します。交感神経が活発になるわけですね。
そして、水風呂に入ることで、その「闘争・逃走本能」を押さえ込みます。

これをすると、心拍変動が高い状態になります。また、交感神経 <=> 副交感神経を上下させることで、自律神経調節機能が活性化され、日常生活でもうまく調節が効くようになります。
また、水風呂で締めることで副交感神経が優位な状態で家路につくことができます。つまり、睡眠導入の準備の準備になります。

また、スポーツジムにはサウナが併設されているので、トレーニング後には必ず実施しています。

まとめ

まずストレスを科学的に理解し、精神面(ソフト)と脳内化学物質の排出(ハード)の両面からアプローチをしています。
精神面については、意識変えてこっ!気持ち作ってこ!
ハード面については、脳内神経伝達物質を合成するために、睡眠と食事を土台にし、各調節機能を鍛えるために瞑想と筋トレを行う。筋トレで取る食事は脳内神経伝達物質のエサとなり、サウナは自律神経と睡眠にも影響してきます。

我ながら一貫性と整合性を兼ね備えた最強のルーチンだと思っています。*4
ジム代と食事の代がかかりますが、ここ数年で最も体調が良いので、継続してみようと思っています。

*1:同著者のスタンフォードのストレスを力に変える教科書も素晴らしい書籍です。

*2:睡眠のゴールデンタイムってやつですね

*3:若干ややこしいですね

*4:側から見るとただの奇行にしか見えないっぽいのですが、自分なりに考えた結果を自分の身体で証明しています

Serverless Framework 1.10.2でAWS IAM変数(${cognito-identity.amazonaws.com:sub})を使用する場合の設定

概要

標題の通りですが、Serverless Framework 1.10.2において、serverless.ymlにAWS IAM変数(${cognito-identity.amazonaws.com:sub}など)を使用する場合、
フレームワークの挙動として${文字列}の値を設定した変数に置き換えようとする動きをするので、その挙動を変更し、IAM変数を使えるようにする方法を書きます。
※今回はcognito-identity.amazonaws.com:subに限定して書きますので、汎用的ではありません。

元のコード

provider:
  name: aws 
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "arn:aws:s3:::mybucket/${self:provider.stage}/${cognito-identity.amazonaws.com:sub}/*"

このような感じで、自分のcognito identityに紐づいたs3 bucketしか触らせないようにするIAM Roleを記述し、 sls deploy -vした場合、

  Serverless Error ---------------------------------------
 
     Invalid variable reference syntax for variable cognito-identity.amazonaws.com.
     You can only reference env vars, options, & files. You
     can check our docs for more info.

こんな感じで怒られます。 そんな変数ねーよって感じですね。

そこで、ここの処理をどうしているのか追っていったところ
variableSyntaxという名の、それっぽものがありました。

さらに調べると、providerブロックの直下にvariableSyntaxを記述すると、記述した値でオーバーライドされとのことでした。 正直正規表現で全てのパターンを網羅するの気合いがなかったので、cognitoに限定した正規表現を作りました。

修正前: variableSyntax: '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}'
修正後: variableSyntax: "\\${([^cognito*+][ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}"

単純ですね。cognito*+だった場合は対象から除外しているだけです。

修正後のコード

provider:
  name: aws 
  variableSyntax: "\\${([^cognito*+][ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}"
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "arn:aws:s3:::mybucket/${self:provider.stage}/${cognito-identity.amazonaws.com:sub}/*"

これで、deployし、AWS マネジメントコンソールのIAM > Roles > Review Policyで確認しましょう。

f:id:watasihasitujidesu:20170412113554p:plain

意図した通りの挙動になっていますね。