vague memory

うろ覚えを無くしていこうともがき苦しむ人の備忘録

シェルスクリプト内の aws cli で実行されるコマンドを出力する

シェルスクリプト(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