GitHub ActionsでLocalStackを動かしてみた
本記事は電通デジタルアドベントカレンダー2021 20日目の記事になります。
はじめに
AWSに依存した実装箇所をテストする際、皆さんはどのように行なっているでしょうか?多くの場合 AWS SDK などでMockを用意する、自前でStubを用意されているかと思います。そしてこのテストがGitHub Actionsで走ってくれると嬉しいですよね。
電通デジタル開発部の長内です。
今回はLocalStackとGitHub Actionsを使って極々単純なテストを書いてみます。
LocalStackとLocalStack Proの紹介
LocalStackのリポジトリには以下のように書かれています。
用途としては文面通りローカルマシンやCI環境で活用可能であることが読み取れるかと思います。ただしいくつかの機能については有償のPro版のみで解放されており、使いたい機能が無償版で問題ないのかPro版でなければ使えないのかについては確認が必要です。詳しくは公式サイトをご参照ください。
LocalStackをGitHub Actionsで動かしてみる
挙動確認のための実行
まずはLocalStackが正常にGitHub Actionsで動くのか知りたいため、公式ドキュメントの中の LocalStack in CI / GitHub Actions のExampleを用いて動作確認してみます。さらにここではPro版の機能が使えるかどうか確認したいので DynamoDB のバックアップの実行も指定しています(本記事のPro版の出番はここまでです)。Pro版含むLocalStackの機能の対応状況については専用のページが用意されているので参照してみてください。なおPro版の動作のため今回はAPI Keyをリポジトリのsecretsに含んでいますが、その手順も前述のLocalStack in CI / GitHub Actions に含まれているので確認してみてください。
name: CI
on:
pull_request:
branches:
- main
paths:
- 'sandbox/**'
jobs:
backend-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: sandbox
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- uses: actions/setup-python@v2
with:
python-version: "3"
- name: LocalStack Test
env:
LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }}
run: |
pip install localstack awscli-local[ver1]
docker pull localstack/localstack
DOCKER_FLAGS='-d' localstack start
echo "Waiting for LocalStack startup..."
for i in {1..45}; do if [ `docker logs localstack_main | grep 'Ready.'` ]; then break; fi; sleep 1; done
echo "Startup complete"
- name: Run some Tests against LocalStack
run: |
awslocal s3 mb s3://sandbox
awslocal s3 ls
awslocal dynamodb create-table --table-name Sandbox --attribute-definitions AttributeName=Id,AttributeType=S AttributeName=Data,AttributeType=S --key-schema AttributeName=Id,KeyType=HASH AttributeName=Data,KeyType=RANGE --billing-mode PAY_PER_REQUEST
awslocal dynamodb create-backup --table-name Sandbox --backup-name SandboxBackup
echo "Test Execution complete!"
GitHub ActionsでLocalStack Proを起動すると、ローカルマシンでの動作と同様にPro版で動作していることを示すログが出力されます(以下のログのSuccessfully activated API key の箇所)。Pro版で動作させたい場合はこのログの出力有無を確認すると良いでしょう。なお当然ですがPro版の機能を使っているにも関わらずLocalStackのAPI Keyを設定していない場合はエラーが出力されます。
Status: Downloaded newer image for localstack/localstack:latest
docker.io/localstack/localstack:latest
__ _______ __ __
/ / ____ _________ _/ / ___// /_____ ______/ /__
/ / / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
/ /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
/_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|
💻 LocalStack CLI 0.13.0
[08:58:10] starting LocalStack in Docker mode 🐳 localstack.py:106
──────────────── LocalStack Runtime Log (press CTRL-C to quit) ─────────────────
2021-11-17T08:58:12:INFO:localstack_ext.bootstrap.licensing: Successfully activated API key
['docker', 'run', '-e', 'LOCALSTACK_API_KEY=***', '-e', 'DEFAULT_REGION=us-east-1', '-e', 'LOCALSTACK_HOSTNAME=localhost', '-e', 'TEST_AWS_ACCOUNT_ID=000000000000', '-e', 'DOCKER_HOST=unix:///var/run/docker.sock', '-e', 'HOST_TMP_FOLDER=/tmp/localstack', '-d', '--rm', '--privileged', '--name', 'localstack_main', '-p', '127.0.0.1:4566:4566', '-p', '127.0.0.1:4571:4571', '-p', '127.0.0.1:12121:12121', '-v', '/tmp/localstack:/tmp/localstack', '-v', '/var/run/docker.sock:/var/run/docker.sock', 'localstack/localstack']
Waiting for LocalStack startup...
Startup complete
このGitHub Actionsワークフローが実行された結果は以下の通りです。LocalStack Proの機能であるDynamoDBのバックアップも正常に行われていますね。
awslocal s3 mb s3://sandbox
make_bucket: sandbox
2021-11-17 09:53:22 sandbox
{
"TableDescription": {
"AttributeDefinitions": [
{
"AttributeName": "Id",
"AttributeType": "S"
},
{
"AttributeName": "Data",
"AttributeType": "S"
}
],
"TableName": "Sandbox",
"KeySchema": [
{
"AttributeName": "Id",
"KeyType": "HASH"
},
{
"AttributeName": "Data",
"KeyType": "RANGE"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": 1637142805.25,
"ProvisionedThroughput": {
"LastIncreaseDateTime": 0.0,
"LastDecreaseDateTime": 0.0,
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/Sandbox",
"TableId": "2257a574-e1e0-4293-b5dc-4a498f150a30",
"BillingModeSummary": {
"BillingMode": "PAY_PER_REQUEST",
"LastUpdateToPayPerRequestDateTime": 1637142805.25
}
}
}
{
"ConsumedCapacity": {
"TableName": "Sandbox",
"CapacityUnits": 1.0
}
}
{
"BackupDetails": {
"BackupArn": "arn:aws:dynamodb:us-east-1:000000000000:table/Sandbox/backup/SandboxBackup",
"BackupName": "SandboxBackup"
}
}
DynamoDB Backup complete!
{
"TableNames": [
"Sandbox"
]
}
{
"BackupSummaries": [
{
"TableName": "Sandbox",
"BackupArn": "arn:aws:dynamodb:us-east-1:000000000000:table/Sandbox/backup/SandboxBackup",
"BackupName": "SandboxBackup"
}
]
}
Test Execution complete!
S3のBucket作成をテストしてみる
LocalStack ProがGitHub Actionsで動作することは確認できました。続いてS3のBucketを作成する関数に対して簡単なテストを行ってみます。
今回は以下のようなサンプル関数を用意しました。
ファイル名はsample.pyとして保存します。
import os
import boto3
def create_bucket(bucket_name):
session = boto3.Session(
aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY'),
)
s3_client = session.client('s3', endpoint_url=os.environ.get('S3_ENDPOINT'))
result = s3_client.create_bucket(Bucket=bucket_name)
return result
この関数に対するテストは正常なバケット名を渡した時と異常なバケット名を渡した時を想定してみます。当然テストシナリオとしては雑ですが、GitHub Actionsで動くかどうかを確認するためのテストなのでここではその雑さを一旦許容します。
こちらのファイル名はtest_sample.pyとして保存します。
import unittest
import botocore.exceptions
from sample import create_bucket
class TestS3Bucket(unittest.TestCase):
def test_create_bucket(self):
test_bucket_name = 'test-sample-bucket'
result = create_bucket(test_bucket_name)
http_status_code = result['ResponseMetadata']['HTTPStatusCode']
location = result['Location']
self.assertEqual(http_status_code, 200)
self.assertEqual(location, '/' + test_bucket_name)
def test_exception_create_bucket(self):
test_bucket_name = 'test_sample_bucket'
with self.assertRaises(botocore.exceptions.ClientError) as e:
result = create_bucket(test_bucket_name)
if __name__ == '__main__':
unittest.main()
なおこの時のファイル構成は以下のようになっています。
__init__.pyは空のままで構いません。
.
├── __init__.py
├── sample.py
└── test_sample.py
そしてこれらをGitHub Actionsで動かすためのworkflowファイルのJobは以下のようにします。
jobs:
example-job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- name: Start LocalStack
env:
SERVICES: s3
run: |
pip install localstack awscli-local[ver1]
docker pull localstack/localstack
DOCKER_FLAGS='-d' localstack start
echo "Waiting for LocalStack startup..."
for i in {1..45}; do if [ `docker logs localstack_main | grep 'Ready.'` ]; then break; fi; sleep 1; done
echo "Startup complete"
- name: Run Sandbox Test
env:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
S3_ENDPOINT: http://localhost:4566
run: |
python -m unittest test_sample.py
実行結果は以下のようになりました。
無事成功していますね。
おわりに
LocalStackをGitHub Actionsで動作させ、テストに組み込めることが確認できました。用途としては例えば今回のようにS3へ直接バケットを作成ような実装をテストしたり、DynamoDBやAthenaなどからスタブデータを取得するケースなどに使えるかもしれません。ただLocalStackに引っ張られる形でGitHub ActionsのWorkflowが完了するまでの時間がかなりかかるようになってしまったため、実業務で採用するにはその点を許容できるのか等を議論する必要があると感じています。