シェルスクリプト(bash)で aws cli を使用する際のデバッグで、実際の処理を実行せずに渡される引数を出力したいと思いました。 開発中に使用したいだけなので極力シンプルにしたいです。
手法検討
シェルスクリプト自体を書き換えるのが楽といえば楽ですが、ソース変更は避けたいです。 Dockerでコンテナ上で動作させる案もありますが、極力 bash だけで完結させたいと思います。
そこで、テストフレームワークで mock 化するのが良いのではと思いました。 bash のテストフレームワークはいくつか候補があります。以下に情報が纏まっており助かりました。
使ってみた感じだと、機能充実している ShellSpec、次点で bats が使い勝手が良さそうです。 とここまで書きながら、結果今回は使用していません。
今回の目的はテスト(aws cli の実行結果の検証) ではなく、実行されるコマンドの内容を知るというシンプルな物です。 echo コマンドに渡せば解決できるので、フレームワークを使うまでもなく最終的には以下の形になりました。
echo-awscli.sh
bashスクリプト内のaws cli のコマンドを出力します。
Output aws cli command in bash script
使い方
対象のシェルスクリプトおよび引数を指定して、 echo-awscli.sh
を実行します。
./echo-awscli.sh scriptPath [arg1 arg2 ...]
例1: シンプルなスクリプト
- ECS Cluster Name を引数に ECS Service 一覧を出力する
- 引数が未指定の場合は Usage を出力し異常終了
#!/usr/bin/env bash ### example-aws-cli.sh set -e USAGE(){ echo "Usage: ${0} clusterName" exit 1 } CLUSTER="${1}" [ -z "${CLUSTER:-}" ] && USAGE aws ecs list-services \ --cluster "${CLUSTER}" \ --query 'serviceArns[]' \ --output json exit $?
実行結果
引数未指定
$ ./echo-awscli.sh example-aws-cli.sh Usage: ./echo-awscli.sh clusterName StatusCode: 1
引数指定
$ ./echo-awscli.sh example-aws-cli.sh TEST-CLUSTER aws ecs list-services --cluster TEST-CLUSTER --query serviceArns[] --output json StatusCode: 0
例2: 取得結果を使って後続実行するスクリプト
list や describe の結果を変数に代入し、後続処理で更新を行うような場合は取得結果を模す必要があります。
- ECS Cluster Name を引数に ECS Service の ARN を取得する
- 取得した ARN を使用し、ECS Service の詳細情報を出力する
#!/usr/bin/env bash ### example-aws-cli2.sh set -e USAGE(){ echo "Usage: ${0} clusterName" exit 1 } CLUSTER="${1}" [ -z "${CLUSTER:-}" ] && exit # Get ECS service arns SERVICE_ARNS=$(aws ecs list-services \ --cluster "${CLUSTER}" \ --query 'serviceArns[]' \ --output text) aws ecs describe-services \ --cluster "${CLUSTER}" \ --services ${SERVICE_ARNS[@]} \ --query "sort_by(services[].{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount }, &serviceName)" \ --output table
取得結果を追加
--- echo-awscli.sh.org 2022-07-03 11:30:02.000000000 +0900 +++ echo-awscli.sh 2022-07-03 11:43:02.000000000 +0900 @@ -9,6 +9,7 @@ case $@ in # e.g. # "<command> <subcommand>"* ) echo "<expected output>" ;; + "ecs list-services"* ) echo "arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-01 arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-02" ;; * ) echo "aws $*" ;; esac }
実行結果
$ ./echo-awscli.sh example-aws-cli2.sh TEST-CLUSTER aws ecs describe-services --cluster TEST-CLUSTER --services arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-01 arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-02 --query sort_by(services[].{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount }, &serviceName) --output table StatusCode: 0
例3: 関数が含まれるスクリプト
例2を少し拡張し、関数がある場合での動作を確認します。
- ECS Cluster Name を引数に ECS Service の ARN を取得する
- 取得した ARN を使用し、ECS Service の詳細情報を出力する
- 取得した ARN を使用し、ECS Service の desired count を更新する
#!/usr/bin/env bash ### example-aws-cli3.sh set -e USAGE(){ echo "Usage: ${0} clusterName [desiredCount]" exit 1 } CLUSTER="${1}" [ -z "${CLUSTER:-}" ] && USAGE DESIRED_COUNT="${2:-0}" # functions function describe_services() { aws ecs describe-services \ --cluster "${CLUSTER}" \ --services ${SERVICE_ARNS[@]} \ --query "sort_by(services[].{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount }, &serviceName)" \ --output table } function update_desired_count() { SERVICE="${1##*/}" echo "# Update ECS Service: ${SERVICE}" aws ecs update-service \ --cluster "${CLUSTER}" \ --service "${SERVICE}" \ --desired-count "${DESIRED_COUNT}" \ --query 'service.{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount }' \ --output json } # Get ECS service arns SERVICE_ARNS=$(aws ecs list-services \ --cluster "${CLUSTER}" \ --query 'serviceArns[]' \ --output text) # Output ECS service details describe_services # Update desired count for SERVICE_ARN in ${SERVICE_ARNS[@]}; do echo "### ${SERVICE_ARN}" update_desired_count ${SERVICE_ARN} done
実行結果
$ ./echo-awscli.sh example-aws-cli3.sh TEST-CLUSTER 3 aws ecs describe-services --cluster TEST-CLUSTER --services arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-01 arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-02 --query sort_by(services[].{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount }, &serviceName) --output table ### arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-01 # Update ECS Service: demo-service-01 aws ecs update-service --cluster TEST-CLUSTER --service demo-service-01 --desired-count 3 --query service.{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount } --output json ### arn:aws:ecs:us-east-1:123456789012:service/demo-cluster/demo-service-02 # Update ECS Service: demo-service-02 aws ecs update-service --cluster TEST-CLUSTER --service demo-service-02 --desired-count 3 --query service.{ serviceName:serviceName, desiredCount:desiredCount, runningCount:runningCount, pendingCount:pendingCount } --output json StatusCode: 0
例4) [NG] aws コマンドを変数化しているスクリプト
あまり無いと思いますが、aws コマンド自体を変数に埋め込んで実行している場合は上手く動作しません。
- no-cli-pager オプションを付与して、 ECS Cluster 一覧を出力する
#!/usr/bin/env bash ### example-aws-cli4.sh set -e CMD="aws --no-cli-pager " ${CMD} ecs list-clusters \ --query 'serviceArns[]' \ --output json exit $?
実行結果
NG
alias が効かず、実際に実行されます。
$ ./echo-awscli.sh example-aws-cli4.sh Unable to locate credentials. You can configure credentials by running "aws configure". StatusCode: 253
OK
aws
部分の変数埋め込みをやめれば動作します。
% diff example-aws-cli4.sh.org example-aws-cli4.sh 4,5c4,5 < CMD="aws --no-cli-pager " < ${CMD} ecs list-clusters \ --- > OPT="--no-cli-pager " > aws ${OPT} ecs list-clusters \
$ ./echo-awscli.sh example-aws-cli4.sh aws --no-cli-pager ecs list-clusters --query serviceArns[] --output json StatusCode: 0
例5) getopts を使用するスクリプト
- ECS Cluster Name、Service Name を引数に TASK 一覧を出力する
#!/bin/bash ### example-aws-cli5.sh function USAGE() { cat <<_EOF Usage: ${0} -c ECSClusterName -s ECSServiceName [-r "region"] [-p "profile"] _EOF exit 1 } while getopts "c:s:r:p:h" opts do case $opts in c) CLUSTER=${OPTARG} ;; s) SERVICE=${OPTARG} ;; r) REGION="--region ${OPTARG}" ;; p) PROFILE="--profile ${OPTARG}" ;; :|\?|h) USAGE ;; esac done [ -z "${CLUSTER}" ] && USAGE [ -z "${SERVICE}" ] && USAGE aws --no-cli-pager ecs list-tasks ${REGION} ${PROFILE} \ --cluster ${CLUSTER} \ --service ${SERVICE} \ --query 'taskArns[]' --output text exit $?
実行結果
引数未指定
$ ./echo-awscli.sh example-aws-cli5.sh Usage: ./echo-awscli.sh -c ECSClusterName -s ECSServiceName [-r "region"] [-p "profile"] StatusCode: 1
引数指定
$ ./echo-awscli.sh example-aws-cli5.sh -c TEST-CLUSTER -s TEST-SERVICE -r ap-northeast-1 aws --no-cli-pager ecs list-tasks --region ap-northeast-1 --cluster TEST-CLUSTER --service TEST-SERVICE --query taskArns[] --output text StatusCode: 0