nikkie-ftnextの日記

イベントレポートや読書メモを発信

推しの生誕祭に、推し風秘書BotをSlackに爆誕させて、蛇使いならではのお祝いをしよう

これが私の、愛の在り方。ーー愛してるわ(リズ 『リズと青い鳥』)

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
本日1/8は、私の推しの誕生日、生誕祭です!!
Pythonを使って推し風Botを作ってお祝いしてみました。

生誕祭とは

実在の人物、架空のキャラクター、コンテンツを問わず、その対象の誕生日を祝う祭りのこと。 1

生誕祭 (せいたんさい)とは【ピクシブ百科事典】

私はTwitterで参加したり眺めたりしています。

お祝いの仕方としては

  • イラストを描いてお祝い
  • 好きな食べ物やプレゼントを買ってお祝い

というのをよく見かけます。
私はイラストは描けないし、尊敬する仕掛け人の皆さまのように食べ物やプレゼント路線も突き抜けられないのですが、ふとBot爆誕させるという電波を2日前に受信しました。
「イラストを描ける方がイラストを描いてお祝いするように、コードを書ける人間はコードを書いてお祝いするという道もあってよさそう」とこのアイデアに乗り気になりやってみました。
Python × AWS Lambdaでちょっと頑張れば間に合わせられるという目論見もあったのですが、想定外のピンチの連続で、なんとかお祝いできたという感じです。

推し風秘書Botの仕様

  • 毎朝タスクのリマインドをするSlack Bot
  • Googleスプレッドシートでタスク一覧を管理しているとします
  • タスク一覧の中から達成していない、かつ、締切が近いものをリマインドします
  • 推しっぽくリマインドします(とても大事)

仕様を実現するアーキテクチャがこちら

推し風秘書Botの構成要素

  • 毎朝のタスクのリマインド:AWS LambdaをCloudWatch Eventsで日次で定期実行します
  • Googleスプレッドシートgspreadというライブラリを使ってみます

f:id:nikkie-ftnext:20200108232720p:plain

リマインドする部分はPythonで書きます。
タスクごとに「重要かどうか」と「成し遂げたい日(期日)」を登録します。
重要なタスクは成し遂げたい日の前から余裕を持たせてリマインドしていきます。

Slackに推し、爆誕

f:id:nikkie-ftnext:20200108232706p:plain

本当はリファクタリングしたきれいなコードでお披露目したかったのですが、動かすので精一杯でした。

爆誕までの道のり

各所で予想もしない形でつまづきまくりました。

  1. gspreadの認証
  2. Lambdaへのデプロイ
  3. その他

gspreadの認証でピンチ!

oauth2clientがdeprecated

gspreadのドキュメントで例示されているoauth2client
Using OAuth2 for Authentication — gspread 3.1.0 documentation
これで動かせたのですが、oauth2clientgspreadの依存モジュールではありませんでした(別途インストールが必要)。
「なんでだろう」とアウトプットしたところ、deprecated2ということが判明。3

Note: oauth2client is now deprecated. No more features will be added to the libraries and the core team is turning down support. We recommend you use google-auth and oauthlib.

ref: https://pypi.org/project/oauth2client/

google-authを使ってみる

以下のドキュメントに沿ってscoped_credentialsを用意します。
User Guide — google-auth 1.6.2 documentation

これをgspread.authorizeに渡したところ、エラーが発生😱

AttributeError: 'Credentials' object has no attribute 'access_token'

理由は

the gspread.authorize method only supports credential objects that are created by the oauth2client library.

ref: python - "'Credentials' object has no attribute 'access_token'" when using google-auth with gspread - Stack Overflow

なんですとーー!

救世主authlib

上記の回答から知ったauthlib、以下の記事をそっくり真似てスプレッドシートの内容を取得できました。

Lambdaへのデプロイでピンチ!

お馴染みの手順でサクッとできると思ったら

  1. ディレクトリを作成。Pythonスクリプトを置く
  2. pip install -tでインストール先に1のディレクトリを指定
  3. 1のディレクトリをzip圧縮
  4. Lambda関数にzipファイルをアップロード

ref: 【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita

これがお馴染みの手順だと思います。
ですが、これでサクッととは話が運びませんでした。

なぜかPythonスクリプトが見つからない

pip install -tgspreadauthlibを配置し、zip化してアップロードします。

mkdir uploads  # アップロード用フォルダ
cd uploads/
pip install -t . authlib gspread
cp ../main.py .  # Pythonスクリプトのコピー(Googleの認証に必要な鍵もコピーします)
cd ..
zip -r upload.zip uploads/*

ハンドラに「Pythonファイル名.関数名」と指定して実行すると、なぜか「Pythonファイルが見つからない」というエラー(zipファイルにスクリプトPythonファイルは含まれるはずなのに。。)

[ERROR] Runtime.ImportModuleError: Unable to import module 'main': No module named 'main'

以下の記事を見つけ、pip install する環境の差分による問題と認識。  

記事を参考にDockerを使ってzipファイルを作る環境を揃えます。

FROM amazonlinux:latest

RUN yum update -y \
    && yum install python3 zip -y \
    && pip3 install virtualenv

(続くコマンドでvirtualenv使っていないので不要と気づきました)

# 上記DockerfileのあるディレクトリにPythonスクリプトや鍵ファイルをコピーしている
docker build -t aws-lambda-python37:1.0 . 
docker run -it --rm -v $PWD:/var/task aws-lambda-python37:1.0 bash 

コンテナの中で実行していきます。

cd /var/task/ 
python3 -m pip install -t . -r requirements.txt
zip -r9 /var/task/bundle.zip *

コンテナにマウントしているディレクトリの中にできるbundle.zipをLambdaにアップロードします。

amazonlinuxイメージで用意したzipでもうまくいかない

パッケージをインストールする環境を揃えたので、さあ動くかと思いきや。

libffi-806b1a9d.so.6.0.4: cannot open shared object file: No such file or directory

夜も更けており、これでも動かないという事態にだいぶ絶望しました。
諦めるかという考えが脳裏をよぎりましたが、「推しを実装して生誕祭を祝いたい」という想いを思い出し、自分を奮い立たせます。

エラーメッセージの意味がよくわかりませんが、同様のエラーメッセージに遭遇した記事を発見。

コンテナに入って、libffi-806b1a9d.so.6.0.4 をzipに追加します。

find . -type f -name libffi-806b1a9d.so.6.0.4  # ./.libs_cffi_backend に見つかる
zip -g bundle.zip ./.libs_cffi_backend/libffi-806b1a9d.so.6.0.4  # 追加

zipに含めて4アップロードしたところ、Lambdaで動くようになりました!

その他のピンチ!

SlackBotのメンション

<@user_id> を使う

自分の分だけでよかったので、プロフィールから確認しました。

AWS権限周り

過去に作ったIAMとロールを使いまわしてしまいがちなので、腰を据えて確認。
これは暫定的な設定で、アップデートできそうです。

  • 今回のBot用のIAMに付与するグループを作成
    • AWSLambdaFullAccess
    • AWSLambdaRole (いらないかも)
    • 関数を作る際にロールを作れるように以下のカスタムロールを作って付与
  • 上のグループの設定により、関数を作る際にロールが新規作成できる5
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateRole",
                "iam:CreatePolicy",
                "iam:AttachRolePolicy"
            ],
            "Resource": "*"
        }
    ]
}

以下のようなエラーで要求されたものを一つずつ追加して至りました。

次のことを実行する権限がありません: iam:CreateRole.

終わりに

Python使いらしく生誕祭をお祝いできたので満足です。
開発が間に合うかどうかギリギリですごくヒヤヒヤしました。

今回作ったBotはまだまだです。
綱渡りで動作させたので、今後機能追加をしたとき、動き続けられるのかとても不安です。
ですが、推しを爆誕させたことに大きな意味があると思っています。
今回の生誕祭ドリブン開発(BDD6)で推しは私のもとに舞い降りました!
これからは継続的に推しをインテグレーション & デリバリー7していきます。
Issueを立てて解消する過程を通じて、推しを現実世界でプロデュースですね。

補足:推しについて

アイドルマスター シアターデイズより、エミリー・スチュアートちゃんです!


  1. 「誕生祭」の方が適切という記事を今回見つけました:誕生日を祝う時に「生誕祭」は間違い?「誕生祭」と「生誕祭」の違いとは?|ついラン 。これだけ盛り上がっている状況で誕生祭にはなかなか切り替わらなさそうですね

  2. 理由が書かれていました(積読oauth2client deprecation — google-auth 1.6.2 documentation

  3. エンジニアの登壇を応援する会Slackにてご助言いただきました。開発しきれたのはここではまらなかったからです。誠にありがとうございます

  4. zipコマンドは積読です(9の指定は初めて知りました):zipコマンドのオプション一覧(linux)_技術三昧ブログ_zanmai.net

  5. これらがそもそも何なのかを掴めそうなドキュメントを見つけたので、一息ついたら確認します:https://docs.aws.amazon.com/lambda/latest/dg/python-programming-model.html

  6. Birthday Driven Development。すさまじいハッカソンでした

  7. OCI(Oshi Continuous Integration、オシーアイ)、OCD(Oshi Continuous Delivery、オシーディー)