例から学ぶ AWS CLI の クエリ(query)活用

AWS CLIのクエリ(query)活用例です。ぜひ AWS CloudShell 上で実施して、いろいろ試してみてください。
2020.12.20

はじめに

AWSのマネジメントコンソールから AWS CLIなどコマンドを実行できるサービス、 AWS CloudShell がついにリリースされました。

手元のローカル環境に実行環境構築する必要なく、 ブラウザから AWS CLIや他スクリプトを試行できるのは素晴らしいですね。 AWS CLIの活用がさらに広がること期待しています。

さて、今回は AWS CLIで情報の取捨選択や加工に活用できる クエリ(--query) の活用例を紹介します。 (もちろん jq コマンドに慣れている方は、そちら使っていただいて構いません)

※ AWS CLI の クエリの文法は JMESPath に準拠しています。 文法の詳細や JMESPath仕様については、公式ドキュメントを参照ください。

実行環境は以下の通り。もちろん CloudShell上 で実施しました。

aws --version
# aws-cli/2.0.58 Python/3.7.3 Linux/4.14.209-160.335.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2

皆さんも CloudShellを立ち上げてコピペすればすぐに実施できます。 色々と試してみてください。

活用例

  1. [キー選択] AWSアカウントIDのみ取得
  2. [リスト参照] CloudFormation(CFn)スタックの一覧を取得
  3. [入れ子のキー選択] CloudFront ディストリビューションの設定値を取得
  4. [リスト内 各要素のキー選択] リージョン名やAZ名一覧の取得
  5. [複数キー選択] IAMユーザー一覧の取得
  6. [フィルタ] 特定サイズ以上の EBSボリューム一覧の取得
  7. [パイプ] サブネットの Nameタグ値一覧の取得
  8. [配列の平坦化] EC2インスタンス一覧の取得
  9. [関数] 特定文字列を含む名前のCFnスタックを取得、そのスタックのリソース数を取得

[キー選択] AWSアカウントIDのみ取得

sts get-caller-identity で AWS CLIを実行する 情報(アカウントIDや IAMユーザー or ロール)を取得できます。 以下のような出力です。

aws sts get-caller-identity --output json
# {
#     "UserId": "AROAXXXXXXXXXXXXXXXXX:cm-kawahara.masahiro",
#     "Account": "123456789012",
#     "Arn": "arn:aws:sts::123456789012:assumed-role/cm-kawahara.masahiro/xxx-role"
# }

AWSアカウントIDのみ取得して、スクリプトで活用すること多いと思います。 --query を使って Account キーの値を取得します。

ACCOUNT_ID=`aws sts get-caller-identity --query 'Account' --output text`
echo "this AWS Account ID is ${ACCOUNT_ID}"
# this AWS Account ID is 123456789012

※テキスト処理などに活用する場合は、出力を text にすると良いです

[リスト参照] CloudFormation(CFn)スタックの一覧を取得

cloudformation list-stacks で CloudFormation(CFn)スタックの一覧を 取得できます。

aws cloudformation list-stacks --output yaml
# StackSummaries:
# - CreationTime: '2020-11-17T03:05:24.552000+00:00'
#   DriftInformation:
#     StackDriftStatus: NOT_CHECKED
#   StackId: arn:aws:cloudformation:ap-northeast-1:123456789012:stack/some-stack/xxxxxxx-xxxx
#   StackName: hoge-te
#   StackStatus: CREATE_COMPLETE
# - CreationTime: '2020-11-17T03:02:21.905000+00:00'
#   DeletionTime: '2020-11-17T03:05:07.210000+00:00'
#   DriftInformation:
#     StackDriftStatus: NOT_CHECKED
#   (以下略)

--query 'StackSummaries[0]' を付けることで、 StackSummaries リストの先頭要素を取得できます。

aws cloudformation list-stacks --query 'StackSummaries[0]' --output yaml
# CreationTime: '2020-11-17T03:05:24.552000+00:00'
# DriftInformation:
#   StackDriftStatus: NOT_CHECKED
# StackId: arn:aws:cloudformation:ap-northeast-1:123456789012:stack/some-stack/xxxxxxx-xxxx
# StackName: hoge-te
# StackStatus: CREATE_COMPLETE

以下、ほかのリスト参照の例です。

--query 出力内容
StackSummaries[*] 全て
StackSummaries[0] 先頭要素
StackSummaries[-1] 末尾要素
StackSummaries[0:5] 先頭から 5個分の要素
StackSummaries[-6:-1] 末尾から 5個分の要素

[入れ子のキー選択] CloudFront ディストリビューションの設定値を取得

cloudfront list-distributions で CloudFront(CF)ディストリビューションの情報を取得できます。

aws cloudfront list-distributions --output json
# {
#   "DistributionList": {
#     "Items": [
#       {
#         "Id": "XXXXXXXXXXXXXX",
#         "ARN": "arn:aws:cloudfront::123456789012:distribution/XXXXXXXXXXXXXX",
#         "Status": "Deployed",
#         "LastModifiedTime": "2020-02-21T04:11:45.412000+00:00",
#         "DomainName": "xxxxxxxxxxxxxx.cloudfront.net",
#         "Aliases": {
#           "Quantity": 1,
#           "Items": [
#               "www.example.com"
#           ]
#         },
#       (以下略)

上記出力のハイライト部分 "Aliases → Items" 内のアドレス を取得してみます。 以下のような クエリです。

aws cloudfront list-distributions --output text \
--query 'DistributionList.Items[0].Aliases.Items[0]'
# www.example.com

.(ドット) で繋げていくことで、ネストされたデータを取得することができます。

[リスト内 各要素のキー選択] リージョン名やAZ名一覧の取得

ec2 describe-regions で AWSリージョン一覧を取得できます。

aws ec2 describe-regions --output json
# {
#     "Regions": [
#         {
#             "Endpoint": "ec2.eu-north-1.amazonaws.com",
#             "RegionName": "eu-north-1",
#             "OptInStatus": "opt-in-not-required"
#         },
#         {
#             "Endpoint": "ec2.ap-south-1.amazonaws.com",
#             "RegionName": "ap-south-1",
#             "OptInStatus": "opt-in-not-required"
#         },
#         (以下略)

リージョン名のみ一覧をしたいときは以下のような --query を指定します。

aws ec2 describe-regions --query 'Regions[*].RegionName' --output text
# eu-north-1	ap-south-1	eu-west-3	eu-west-2	eu-west-1	ap-northeast-2	ap-northeast-1	sa-east-1	ca-central-1	ap-southeast-1	ap-southeast-2	eu-central-1	us-east-1	us-east-2	us-west-1	us-west-2

text 出力の Tipsですが、 先程の例の 'Regions[*].RegionName' の部分を 'Regions[*].[RegionName]' のように角括弧で囲むことで、行単位の出力になります。

aws ec2 describe-regions --query 'Regions[*].[RegionName]' --output text
# eu-north-1
# ap-south-1
# eu-west-3
# eu-west-2
# eu-west-1
# (以下略)

行単位の出力にすることで、追加の処理を実施しやすい場合があります。

この出力を使って、 Availability Zone 名 一覧 を出力してみましょう。

aws ec2 describe-regions --query 'Regions[*].[RegionName]' --output text \
| while read region; do
  aws ec2 describe-availability-zones --region $region \
  --query 'AvailabilityZones[*].ZoneName' --output text
done
# eu-north-1a	eu-north-1b	eu-north-1c
# ap-south-1a	ap-south-1b	ap-south-1c
# eu-west-3a	eu-west-3b	eu-west-3c
# eu-west-2a	eu-west-2b	eu-west-2c
# eu-west-1a	eu-west-1b	eu-west-1c
# ap-northeast-2a	ap-northeast-2b	ap-northeast-2c	ap-northeast-2d
# (以下略)

[複数キー選択] IAMユーザー一覧の取得

iam list-users でIAMユーザー一覧を取得できます。

aws iam list-users --output json
#{
#    "Users": [
#        {
#            "Path": "/",
#            "UserName": "AAAA",
#            "UserId": "AIDAXXXXXXXXXXXXXXXXX",
#            "Arn": "arn:aws:iam::123456789012:user/AAAA",
#            "CreateDate": "2019-12-16T06:38:25+00:00"
#        },
#        {
#            "Path": "/",
#            "UserName": "BBBB",
#            "UserId": "AIDAYYYYYYYYYYYYYYYYY",
#            "Arn": "arn:aws:iam::123456789012:user/BBBB",
#            "CreateDate": "2020-09-24T02:21:46+00:00",
#            "PasswordLastUsed": "2020-09-24T02:32:38+00:00"
#        },
#         (以下略)

これをそのまま table 出力すると横幅が長くなり、少々見づらいです。

aws iam list-users --output table
# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# |                                                                                           ListUsers                                                                                          |
# +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
# ||                                                                                            Users                                                                                           ||
# |+---------------------------------------------------------------+----------------------------+----------------------------+-------+------------------------+---------------------------------+|
# ||                              Arn                              |        CreateDate          |     PasswordLastUsed       | Path  |        UserId          |            UserName             ||
# |+---------------------------------------------------------------+----------------------------+----------------------------+-------+------------------------+---------------------------------+|
# ||  arn:aws:iam::123456789012:user/AAAA                          |  2019-12-16T06:38:25+00:00 |                            |  /    |  AIDAXXXXXXXXXXXXXXXX  |  AAAA                           ||
# ||  arn:aws:iam::123456789012:user/BBBB                          |  2020-09-24T02:21:46+00:00 |  2020-09-24T02:32:38+00:00 |  /    |  AIDAXXXXXXXXXXXXXXXX  |  BBBB                           ||
# (以下略)

そこで ユーザー名、作成日、最後にサインインした日 のみに絞って テーブルに出力してみます。

aws iam list-users --output table \
--query 'Users[*].[UserName,CreateDate,PasswordLastUsed]'
# ----------------------------------------------------------------------------
# |                            ListUsers                                     |
# +--------------+-----------------------------+-----------------------------+
# |  AAAA        |  2019-12-16T06:38:25+00:00  |  None                       |
# |  BBBB        |  2020-09-24T02:21:46+00:00  |  2020-09-24T02:32:38+00:00  |
# |  CCCC        |  2020-01-26T11:30:52+00:00  |  None                       |
# |  test-user   |  2020-06-10T00:47:09+00:00  |  2020-06-10T01:05:21+00:00  |
# +--------------+-----------------------------+-----------------------------+

また、 [UserName, CreateDate, PasswordLastUsed] (→ リスト)の部分を {Name:UserName, CreateDate:CreateDate, LastUsed:PasswordLastUsed} (→ ハッシュ) のように {} で囲むことで列名を付けることができます。

aws iam list-users --output table \
--query 'Users[*].{Name:UserName, CreateDate:CreateDate, LastUsed:PasswordLastUsed}'
# ----------------------------------------------------------------------------
# |                            ListUsers                                     |
# +--------------+-----------------------------+-----------------------------+
# |  Name        |         CreateDate          |           LastUsed          |
# +--------------+-----------------------------+-----------------------------+
# |  AAAA        |  2019-12-16T06:38:25+00:00  |  None                       |
# |  BBBB        |  2020-09-24T02:21:46+00:00  |  2020-09-24T02:32:38+00:00  |
# |  CCCC        |  2020-01-26T11:30:52+00:00  |  None                       |
# |  test-user   |  2020-06-10T00:47:09+00:00  |  2020-06-10T01:05:21+00:00  |
# +--------------+-----------------------------+-----------------------------+

※ハッシュにすることで列の順番が保証されなくなります。 そのため、 text 出力で後続の処理で利用する場合は ハッシュ {} は使わず、 リスト [] で出力しましょう。

[フィルタ] 特定サイズ以上の EBSボリューム一覧の取得

10GiB 以上 のEBSボリュームを取得して概要をテーブル表示します。

aws ec2 describe-volumes --output table \
--query 'Volumes[?Size>=`10`].{ID:VolumeId, Type:VolumeType, Size:Size}'
# -------------------------------------------
# |             DescribeVolumes             |
# +------------------------+-------+--------+
# |           ID           | Size  | Type   |
# +------------------------+-------+--------+
# |  vol-04aaaaaaaaaaaaaaa |  50   |  gp2   |
# |  vol-04bbbbbbbbbbbbbbb |  50   |  gp2   |
# |  vol-0eccccccccccccccc |  25   |  gp2   |
# |  vol-06ddddddddddddddd |  10   |  gp2   |
# |  vol-0deeeeeeeeeeeeeee |  10   |  gp2   |
# |  vol-03fffffffffffffff |  10   |  gp2   |
# +------------------------+-------+--------+

[?(条件式)] といった書き方でフィルタ出来ます。 リテラル値は バッククォート (``) で囲む必要があります。

以下比較演算子を使えます。

  • ==X : X と等しい (数値、文字列)
  • !=X : X と等しくない (数値、文字列)
  • >=X : X 以上 (数値のみ)
  • <=X : X 以下 (数値のみ)
  • >X : X より大きい (数値のみ)
  • <X : X 未満 (数値のみ)

[パイプ] サブネットの Nameタグ値一覧の取得

サブネットの Nameタグ値一覧 を取得してみます。

aws ec2 describe-subnets --output table \
--query 'Subnets[*].{
  ID:SubnetId,
  Name:Tags[?Key==`Name`] | [0].Value
  }'
# -------------------------------------------------
# |                DescribeSubnets                |
# +---------------------------+-------------------+
# |            ID             |       Name        |
# +---------------------------+-------------------+
# |  subnet-xxxxxxxx          |  aaa-subnet       |
# |  subnet-yyyyyyyy          |  bbb-subnet       |
# |  subnet-zzzzzzzz          |  ccc-subnet       |
# +---------------------------+-------------------+

パイプ (|) を使うことで、直前の出力を使って次の処理を行うことが出来ます。

先程の例、 Tags の値は 「("Key", "Value" キーからなる)ハッシュのリスト」です。 もちろん "Name" タグにフィルタした Tags[?Key==`Name`] も「ハッシュのリスト」です。

これをパイプして、次の処理で先頭要素( [0] )の "Value" キーを選択しています。

ちなみに 以下 2つの書き方は、両方とも同じ結果が得られます。

  • Tags[?Key==`Name`] | [0].Value
  • Tags[?Key==`Name`].Value | [0]

Tags[?Key==`Name`].Value[0] は意図した出力になりません。 Value 自体は文字列である(=配列ではない) からです。

[配列の平坦化] EC2インスタンス一覧の取得

EC2インスタンス一覧(インスタンスID, Nameタグ, ステータス)をテーブル出力してみます。

aws ec2 describe-instances --output table \
--query 'Reservations[*].Instances[].{
  ID: InstanceId,
  Name: Tags[?Key==`Name`] | [0].Value,
  State: State.Name
  }'
# ---------------------------------------------------
# |                DescribeInstances                |
# +----------------------+---------------+----------+
# |          ID          |     Name      |  State   |
# +----------------------+---------------+----------+
# |  i-xxxxxxxxxxxxxxxxx |  xxx-instance |  stopped |
# |  i-yyyyyyyyyyyyyyyyy |  yyy-instance |  stopped |
# +----------------------+---------------+----------+

ここで「 'Reservations[*].Instances[] ではなく ~'Reservations[*].Instances[*] じゃダメなの?」と思われるかもしれません。

[*] は リストアクセスの章で説明したとおり リスト内全ての要素 を表すものでした。 [] を使うことで リストを平坦化した上で、全ての要素 を取得することができます。

EC2の各インスタンスの情報は 「 個々の Reservations 内にある Instances にリストとして格納されている」 ため、この平坦化が役に立ちます。

実際に以下出力例を見ると、 [*][] の違いがイメージできると思います。

### 平坦化無し
aws ec2 describe-instances --output json \
--query 'Reservations[*].Instances[*].InstanceId'
# [
#     [
#         "i-xxxx",
#         "i-yyyy"
#     ],
#     [
#         "i-zzzz"
#     ]
# ]

### 平坦化有り
aws ec2 describe-instances --output json \
--query 'Reservations[*].Instances[].InstanceId'
# [
#     "i-xxxx",
#     "i-yyyy",
#     "i-zzzz"
# ]

[関数] 特定文字列を含む名前のCFnスタックを取得、そのスタックのリソース数を取得

JMESPath にはいくつか関数が提供されています。

今回は contains(特定文字列が含まれているかどうか)length(配列の要素数を返す) を使った例を紹介します。

特定文字列 (今回は "xxx-stg" )がスタック名に含まれている CFnスタックに絞って、 それらスタックのリソース数を返すスクリプトです。

aws cloudformation describe-stacks --output text \
--query 'Stacks[?contains(StackName, `xxx-stg`)].[StackName]' \
| while read stackname; do
  echo -n "- ${stackname} : "
  aws cloudformation describe-stack-resources --output text \
    --stack-name $stackname \
    --query 'length(StackResources)'
done
# - xxx-stg-s3 : 2
# - xxx-stg-instances : 4
# - xxx-stg-network : 10

これら関数の詳細や、他に使える関数は JMESPathチュートリアルJMESPathドキュメント を参照ください。

おわりに

AWS CLIのクエリ(query) 例を紹介しました。 もちろん慣れている方は jq でも構いません。AWS CLIのクエリ --query は 「別途 他パッケージのインストール不要」、「AWS CLIの出力オプション(table や text)が利用可能」 などいくつかメリットがあると思います。

また、AWS CLI には --filters オプションがあり、簡単に情報を絞ることが可能です。 aws ec2 describe-instances help など ヘルプコマンドを実行して、 利用できる フィルタオプションを調べると捗るかもしれません。

以上、少しでもどなたかのお役に立てば幸いです。

参考