AWS CLI で取得した情報をシェルスクリプトで使うときの Tips

はじめに

AWS 上のリソースの操作を自動化したいとき、ちょっとしたことなら AWS CLIシェルスクリプトで書くのが一番お手軽だと思います。ただ、きちんとソースコード管理されるようなスクリプトであれば、例えば EC2 の Instance ID のような自動採番値はスクリプトに仕込みたくないので、list や describe 系のコマンドで情報を取得したうえでゴニョゴニョしたくなります。

これらの参照系コマンドは実にたくさんの情報を返してくるので、欲しい情報だけ抜き出さないといけませんが、そのあたりのやり方を書いておきます。

基本: --query オプション

AWS CLI の出力から抽出・加工する方法として、 --query オプション を使う方法と、JSON 出力して jq で加工する方法をよく見かけますが、jq はデフォルトインストールされていない環境も多いので、個人的には --query を好んで用います。

例えば、 Name タグが hoge-instanceインスタンスIDを取りたい場合を考えます。以下のコマンドを叩くと、インスタンスの細かい情報が取得できます。

$ aws ec2 describe-instances \
    --filter "Name=tag:Name,Values=hoge-instance" --output json
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 14,
                    "ImageId": "ami-gej3kjfo9",
                    "InstanceId": "i-0f4f44a816fexample",
                    "InstanceType": "c4.xlarge",

...

}

ここから InstanceId だけ取り出したいので、出力の階層構造を踏まえ、以下のように --query を設定します。

$ aws ec2 describe-instances \
    --filter 'Name=tag:Name,Values=hoge-instance' \
    --query 'Reservations[0].Instances[0].InstanceId' --output text

i-0f4f44a816fexample

--output text としていますが、 json のままだと、出力に引用符が残ります。 --output の設定値を変えても、クエリの抽出結果には影響しません。

シェルスクリプト内では、こうして必要な情報を抽出できるコマンドを書き、 $() で変数に代入して扱う形が多くなると思います。

一度のコマンドで複数の値を取りたい場合

一度のコマンド実行結果から複数の値を取りたい場合は、 set コマンドが便利です。例えば、上記の結果から InstanceIdImageId を取りたいというような場合は以下のように書きます。

set $(aws ec2 describe-instances \
    --filter 'Name=tag:Name,Values=hoge-instance' \
    --query 'Reservations[0].Instances[0].[InstanceId,ImageId]' \
    --output text)

INSTANCE_ID=${1}
IMAGE_ID=${2}

query で [InstanceId, ImageId] のように配列に括るようにして text 形式で出力すると、各値がスペース区切りで出力されます。これを set に与えることで位置パラメータ $1, $2, ... に代入されて、個別に取り出すことができるようになります。

query の JMESPath ?演算子 を組み合わせると、順不同で帰ってくるリストから、特定の条件にマッチするものを複数一度に取得することができます。以下の例は、 ALB に複数設定されたリスナのリストから、http と https の各リスナの ARN を抽出する例です。

set $(aws elbv2 describe-listeners --load-balancer-arn $load_balancer_arn \
      --query '[Listeners[?Port==`80`].ListenerArn,Listeners[?Port==`443`].ListenerArn]' \
      --output text)

local listener_arn_http=${1}
local listener_arn_https=${2}

カンマ区切りリストとして取得する

CloudFormation などは、複数値をとるパラメータの形式として、カンマ区切りリストが使えます。このようなカンマ区切りリストを手軽に作る方法です。

例えば、ある ECS Service のタスクが展開される Subnet のリストを取りたいときは以下のようなコマンドになります。

$ aws ecs describe-services --cluster the-cluster --services the-service \
       --query 'services[0].networkConfiguration.awsvpcConfiguration.subnets' \
       --output text

subnet-xxxxxxxx subnet-yyyyyyyy

これをカンマ区切りリストにするには printf コマンドが使えます。

$ printf '%s,' \
   $(aws ecs describe-services --cluster the-cluster --services the-service \
       --query 'services[0].networkConfiguration.awsvpcConfiguration.subnets' \
       --output text)

subnet-xxxxxxxx,subnet-yyyyyyyy,

ただしこれでは、末尾にもカンマが付加されてしまうため、変数展開を使って末尾のカンマを取り除きます。 スクリプトではこんな感じ。

SUBNET_LIST=$(printf '%s,' \
   $(aws ecs describe-services --cluster the-cluster --services the-service \
       --query 'services[0].networkConfiguration.awsvpcConfiguration.subnets' \
       --output text))

echo ${SUBNET_LIST%,}

# 出力結果
# subnet-xxxxxxxx,subnet-yyyyyyyy

その他細かいネタ

よく必要になる AWSアカウントID を取得するスニペットです。

ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)

おわりに

だいたいのケースは、ここに書いたように、AWS CLI とシェルの組み込みコマンドで何とかなってしまうので、生の ID をスクリプトに埋め込まないようにしましょう。

( あんまり複雑になるようなら他のプログラミング言語SDKを使ったほうがいいと思いますが )