前回の記事でDockerを使ってDreddを動かすところまで書きましたが、あれから、いくつかやってみたいことが出てきました。
テストの制御はhookファイルで行いますが、前回はJavaScriptで書きました。
管理するバックエンドはJavaScripitよりもPHPやRubyの方が慣れているメンバーの方が多いという状況。
そこで、やってみたいこととして、
- このhookファイルをPHPで書き変えたい。
また、Cognitoなどの外部認証サービスを使ってトークンを取得する場合、hookファイルにあらかじめその取得 + セットする処理を記述する必要があり、
AWS SDK が使えるようにする必要があります。
LaravelをAPIとして扱っていて、特にJSのパッケージを利用していない場合、それだけのためにpackage.jsonに追加するのも不自然です。
そこで、次のやってみたいこととして、
- package.jsonを汚さずにAWS SDKを導入したい。
以上に触れてみました。
hookファイルの言語をPHPに変更する
Dockerを使わない場合
Dockerを使わずにローカルでnpmを使ってdreddを管理/実行する場合、上記のURLの手順で簡単に別言語でhookファイルを扱うことができます。
composer require ddelnano/dredd-hooks-php --dev
dredd apiary.apib http://127.0.0.1:3000 --language=vendor/bin/dredd-hooks-php --hookfiles=./hooks*.php
一方で、これがDockerを使った場合、少々面倒になります。
Dockerを使う場合
hookファイルを扱う場合、dreddのプロセスとは別プロセスでhookファイルの内容を扱うhookサーバが立ち上がります。
デフォルトではdreddが子プロセスでhookサーバを立ち上げるのですが、
dreddのデフォルトのコンテナにはphpが入っていないので、別コンテナを立ち上げて、そこでhookサーバが立ち上がるようにし、アクセスするように仕向ける必要があります。
例えば docker-compose などで一緒に立ち上げたhookサーバのコンテナの名前がphpだとすると、dredd自体が起動しているコンテナのコマンドは
dredd openapi.yml http://host.docker.internal:8000/api/ \
--language php \
-d --hooks-worker-handler-host php --hooks-worker-handler-port 61321 \
--hookfiles="openapi/dredd_cmd/hooks/*.php"
このようになります。
--hooks-worker-handler-host
--hooks-worker-handler-port
このオプションを使って、接続先を指定する必要があるのです。
ただ、本来的にはこれで動くはずなのですが、
にあるように、--language
オプションがうまく働いておらず、dummyスクリプトを挟む必要があるようです。
公式にあるように
However, hooks were not originally designed with this scenario in mind.
と、元々こうした実装を想定されてデザインされていなかったようなので、動作が安定しないと困るため、今回は別言語にすることを見送りました。
そもそも、hookファイルに書かれている処理はそこまで難しい処理を含まないため、別言語で書き直すことの恩恵も少ないのかなと思います。
package.jsonを汚さずにAWS SDKを導入したい。
AWS SDKのためだけに npm install
の工程が増えるのも煩わしいので、これを使わなくていいようにしました。
処理としては単純なのですが、Dockerファイル内にaws-sdkをinstallする工程を増やしただけです。
先述の通り、hookファイルはDreddの子プロセスとしてhookサーバが立ち上がり、それがhookファイルを読み取って処理します。
そのため、処理としては非常に単純でDreddを起動させるコンテナと同じコンテナに aws-sdk をインストールしてあげるだけです。
FROM apiaryio/dredd
RUN npm install aws-sdk
CMD ["dredd"]
あとは、このコンテナをbuildして、指定して起動するだけです。
私はめんどくさがり屋なので、上記の処理を全てコマンドにしました。
ファイルの配置は
openapi
┠ default.yml
┗ dredd/
┠ Dockerfile
┠ dredd.sh
┠ params.cfg
┠ tmp/
┗ hooks/
┗ hook.js
このようにし、 sh dredd.sh
でdreddが起動できるようにしました。
この記事は前回の続きなので、細かい点は前回の記事を参照いただけると幸いです。
以下、実装したコード
実行部分のシェルスクリプト
# openapi(OAS)のファイル名
FILE='default.yml'
# hookファイル
# *.jsとすることで複数のファイルを指定可能
HOOKFILES='*.js'
# dredd.sh があるディレクトリの名前
DREDD_DIR='dredd'
# OpenAPIのURLではなく、APIのURLを書く。
# Dockerからの通信なので、ホストマシンのlocalhostにアクセスする場合以下のように書く
# 例: API_URL='http://host.docker.internal:8000/api/'
API_URL='http://host.docker.internal:8000/api/'
IMAGE_NAME='dredd-with-aws-sdk'
SCRIPT_DIR=$(cd $(dirname $0); pwd)
source ${SCRIPT_DIR}/params.cfg
HOOKFILES="/openapi/$DREDD_DIR/hooks/$HOOKFILES"
# OASが分割されている場合 dredd は利用できないので
# OASが分割されている場合mergeする。
docker run -it --rm -v $SCRIPT_DIR/..:/app \
properdom/swagger-merger swagger-merger \
-i app/$FILE \
-o app/$DREDD_DIR/tmp/merged.yml
# Imageがビルドされていなければビルドする
image=$(docker images | grep $IMAGE_NAME | cut -d' ' -f1 | grep -x $IMAGE_NAME)
if [ "$image" != "$IMAGE_NAME" ]; then
docker build ${SCRIPT_DIR}/. -t $IMAGE_NAME
fi
# APIがOAS通りのレスポンスを返しているか確認する。
# 出力が長いので、詳細は ./dredd/dredd_result.txt に出力する。
docker run -it --rm -v $SCRIPT_DIR/..:/openapi \
--add-host=host.docker.internal:host-gateway \
$IMAGE_NAME sh -c \
"dredd openapi/$DREDD_DIR/tmp/merged.yml $API_URL \
-d --hookfiles=$HOOKFILES --no-color | \
tee openapi/$DREDD_DIR/tmp/dredd_result.txt | \
grep -e 'pass\:' -e 'fail\:' -e 'complete\:'"
echo "result in"
echo openapi/$DREDD_DIR/tmp/dredd_result.txt
Cognitoのトークンを使う場合のhookファイルの書き方:例
例えばCognitoのトークンを取ってきて、それを元に認証をしてテストをしたい場合以下のように書きます。
var hooks = require('hooks');
var stash = {};
const AWS = require('aws-sdk');
async function getCognitoJWT() {
AWS.config.update({
region: 'ap-northeast-1',
});
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});
return await cognitoidentityserviceprovider.initiateAuth({
ClientId: 'クライアントID',
AuthFlow: 'USER_PASSWORD_AUTH',
AuthParameters: {
USERNAME: 'ユーザ名',
PASSWORD: 'パスワード'
}
}).promise()
};
// テストの一番初めに起動する処理
// cognito のトークンをstashに保存
hooks.beforeAll(function (transaction) {
getCognitoJWT().then(function(data){
stash['token'] = data.AuthenticationResult.IdToken;
console.log(stash['token']);
}, function(err){
console.log(err);
});
});
hooks.beforeEach(function (transaction) {
// skipするリクエストを指定
if (
transaction.name.match(/auth\/(login|refresh)/) != null ||
transaction.expected.statusCode == "429"
) {
transaction.skip = true;
};
// 401以外はトークンをつける
if (stash['token'] != undefined) {
if (transaction.expected.statusCode != "401") {
transaction.request.headers['Authorization'] = "Bearer " + stash['token'];
};
};
});
// 各リクエスト固有の処理
hooks.before("/hc > Health-Check Endpoint. > 200 > text/html", function (transaction) {
transaction.skip = true;
});
※固有のリクエストに処理を記述する場合 hooks.before(リクエスト名, 処理)
という文法なのですが、リクエスト名は
https://github.com/itsuki-n22/dredd_cmd/blob/main/dredd-names.sh
を参考に取得してもらえると嬉しいです。