てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Azure] Azure CLI が叩いてる REST API のリクエストやレスポンスを確認する方法(--debug、ログファイル有効化)

はじめに

Azure 情報さまざまなリソースをコマンドラインで操作できる Azure CLI というツールがあります。

裏では Azure REST API を叩いていたりするようです。ときどき、Azure CLI による操作がどういう API を叩いてるのか(エンドポイント、ボディ)、どういうレスポンスが返ってきているのか気になることがあります。

プロキシ的なツールを挟んでパケットキャプチャする方法もありますが、もっと手軽に確認する方法ないかなと思って調べてみると、Azure CLI 側のデバッグ的な機能でできるようでした。

調べた限り --debug オプションによる方法と、ログファイルの有効化の 2つの方法がありました。

それぞれ試してみた結果をまとめます。

  • 環境
    • azure-cli 2.43.0

方法1: --debug オプションの利用

az hogehoge コマンドに --debug オプションを付けると、さまざまなデバッグ情報が表示されその中に、リクエストやレスポンスの情報がありました。

--debug 使用例

以下のコマンドは、既存のネットワークセキュリティグループ nsg01 に対して、description を設定する例です。表面上はマージ的な操作です。例によって --debug もつけます。

az network nsg rule update -g sakana --nsg-name nsg01 \
    -n rule999 --description desc999 --debug

(参考: az network nsg rule のリファレンス

以下、実行して表示されたログを抜粋して、部分ごとに考察します。

(GET のリクエスト、レスポンス、PUT のリクエスト、レスポンス)

GET のリクエス

以下のように、ネットワークセキュリティグループ nsg01 のルール rule999 の情報を GET する様子を確認しました。

cli.azure.cli.core.sdk.policies: Request URL: 'https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999?api-version=2022-01-01'
cli.azure.cli.core.sdk.policies: Request method: 'GET'
cli.azure.cli.core.sdk.policies: Request headers:
cli.azure.cli.core.sdk.policies:     'Accept': 'application/json'
cli.azure.cli.core.sdk.policies:     'x-ms-client-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'CommandName': 'network nsg rule update'
cli.azure.cli.core.sdk.policies:     'ParameterSetName': '-g --nsg-name -n --description --debug'
cli.azure.cli.core.sdk.policies:     'User-Agent': 'AZURECLI/2.43.0 (PIP) (AAZ) azsdk-python-core/1.26.2 Python/3.9.16 (macOS-12.6-x86_64-i386-64bit)'
cli.azure.cli.core.sdk.policies:     'Authorization': '*****'
cli.azure.cli.core.sdk.policies: Request body:
cli.azure.cli.core.sdk.policies: This request has no body
urllib3.connectionpool: Starting new HTTPS connection (1): management.azure.com:443

なぜ 事前に GET するかの考察

対応する API リファレンスは Security Rules - Get

セキュリティルールに関するリファレンスを確認すると、特定の設定項目のみをマージ、つまり PATCH する方法はありませんでした。できるのは PUTです。

PUT するには設定差分の情報だけでなく、該当ルールの情報一式(directionpriority など)が必要です。そうしないと、API 使用上必須のパラメーターが不足して設定できなかったり(400 Bad Request)、設定がデフォルトに戻ったりしてしまいます。

一方で、az network nsg rule update コマンドとしては、キーとなる情報(-g--nsg-name-n )以外は、設定差分(今回は --description )しか指定していません。そのため、他の必要な情報一式を取得するために事前に GET しているのかなと思いました。

ヘッダーについての補足

リクエストヘッダーについては、あくまで Azure CLI 側が付加するものだけが表示されてるようでした。プロキシツールを併用してパケットキャプチャしたところ、他にもたとええば以下のような基本的なヘッダーも付加されてることを確認しました。

Host: management.azure.com
Connection: keep-alive

なお、'Authorization': '*****' は、ブログ用にマスクしたのではなく、もともとマスクされて表示されました。

GET に対するレスポンス

先ほどの GET するリクエストに続いて、以下のレスポンスのログが表示されました。Response content のところで、レスポンスのボディがあります。

urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999?api-version=2022-01-01 HTTP/1.1" 200 None
cli.azure.cli.core.sdk.policies: Response status: 200
cli.azure.cli.core.sdk.policies: Response headers:
cli.azure.cli.core.sdk.policies:     'Cache-Control': 'no-cache'
cli.azure.cli.core.sdk.policies:     'Pragma': 'no-cache'
cli.azure.cli.core.sdk.policies:     'Transfer-Encoding': 'chunked'
cli.azure.cli.core.sdk.policies:     'Content-Type': 'application/json; charset=utf-8'
cli.azure.cli.core.sdk.policies:     'Content-Encoding': 'gzip'
cli.azure.cli.core.sdk.policies:     'Expires': '-1'
cli.azure.cli.core.sdk.policies:     'ETag': 'W/"XXXXXXXX-xxxx-xxxx-xxxx-xxxxxxxxxxxx"'
cli.azure.cli.core.sdk.policies:     'Vary': 'Accept-Encoding'
cli.azure.cli.core.sdk.policies:     'x-ms-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'x-ms-correlation-request-id': 'xxxxxxxx-XXXX-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'x-ms-arm-service-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
cli.azure.cli.core.sdk.policies:     'Server': 'Microsoft-HTTPAPI/2.0, Microsoft-HTTPAPI/2.0'
cli.azure.cli.core.sdk.policies:     'x-ms-ratelimit-remaining-subscription-reads': '11999'
cli.azure.cli.core.sdk.policies:     'x-ms-routing-request-id': 'JAPANEAST:20230108T132147Z:xxxxxxxx-XXXX-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'X-Content-Type-Options': 'nosniff'
cli.azure.cli.core.sdk.policies:     'Date': 'Sun, 08 Jan 2023 13:21:46 GMT'
cli.azure.cli.core.sdk.policies: Response content:
cli.azure.cli.core.sdk.policies: {
  "name": "rule999",
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999",
  "etag": "W/\"XXXXXXXX-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"",
  "type": "Microsoft.Network/networkSecurityGroups/securityRules",
  "properties": {
    "provisioningState": "Succeeded",
    "protocol": "Tcp",
    "sourcePortRange": "*",
    "destinationPortRange": "443",
    "sourceAddressPrefix": "203.0.113.0/24",
    "destinationAddressPrefix": "*",
    "access": "Allow",
    "priority": 999,
    "direction": "Inbound",
    "sourcePortRanges": [],
    "destinationPortRanges": [],
    "sourceAddressPrefixes": [],
    "destinationAddressPrefixes": []
  }
}

update 前の設定情報なので、まだ description はありません。

PUT のリクエス

いよいよ、実際の設定変更のための PUT です。

cli.azure.cli.core.sdk.policies: Request URL: 'https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999?api-version=2022-01-01'
cli.azure.cli.core.sdk.policies: Request method: 'PUT'
cli.azure.cli.core.sdk.policies: Request headers:
cli.azure.cli.core.sdk.policies:     'Content-Type': 'application/json'
cli.azure.cli.core.sdk.policies:     'Accept': 'application/json'
cli.azure.cli.core.sdk.policies:     'Content-Length': '496'
cli.azure.cli.core.sdk.policies:     'x-ms-client-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'CommandName': 'network nsg rule update'
cli.azure.cli.core.sdk.policies:     'ParameterSetName': '-g --nsg-name -n --description --debug'
cli.azure.cli.core.sdk.policies:     'User-Agent': 'AZURECLI/2.43.0 (PIP) (AAZ) azsdk-python-core/1.26.2 Python/3.9.16 (macOS-12.6-x86_64-i386-64bit)'
cli.azure.cli.core.sdk.policies:     'Authorization': '*****'
cli.azure.cli.core.sdk.policies: Request body:
cli.azure.cli.core.sdk.policies: {"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999", "name": "rule999", "properties": {"access": "Allow", "description": "desc999", "destinationAddressPrefix": "*", "destinationPortRange": "443", "direction": "Inbound", "priority": 999, "protocol": "Tcp", "sourceAddressPrefix": "203.0.113.0/24", "sourcePortRange": "*"}, "type": "Microsoft.Network/networkSecurityGroups/securityRules"}

対応する API リファレンスは Security Rules - Create Or Update

Request body のところで設定値が確認できます。わかりやすいようにここだけ整形して再掲します。

{
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999",
  "name": "rule999",
  "properties": {
    "access": "Allow",
    "description": "desc999",
    "destinationAddressPrefix": "*",
    "destinationPortRange": "443",
    "direction": "Inbound",
    "priority": 999,
    "protocol": "Tcp",
    "sourceAddressPrefix": "203.0.113.0/24",
    "sourcePortRange": "*"
  },
  "type": "Microsoft.Network/networkSecurityGroups/securityRules"
}

事前に GET した情報に対して、az network nsg rule update コマンドの --description で指定した description をマージしたような内容になっています。

PUT に対するレスポンス

続いて、先ほどの PUT のに対するレスポンスが表示されました。

urllib3.connectionpool: https://management.azure.com:443 "PUT /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999?api-version=2022-01-01 HTTP/1.1" 200 None
cli.azure.cli.core.sdk.policies: Response status: 200
cli.azure.cli.core.sdk.policies: Response headers:
cli.azure.cli.core.sdk.policies:     'Cache-Control': 'no-cache'
cli.azure.cli.core.sdk.policies:     'Pragma': 'no-cache'
cli.azure.cli.core.sdk.policies:     'Transfer-Encoding': 'chunked'
cli.azure.cli.core.sdk.policies:     'Content-Type': 'application/json; charset=utf-8'
cli.azure.cli.core.sdk.policies:     'Content-Encoding': 'gzip'
cli.azure.cli.core.sdk.policies:     'Expires': '-1'
cli.azure.cli.core.sdk.policies:     'Retry-After': '10'
cli.azure.cli.core.sdk.policies:     'Vary': 'Accept-Encoding'
cli.azure.cli.core.sdk.policies:     'x-ms-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'Azure-AsyncOperation': 'https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Network/locations/japaneast/operations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?api-version=2022-01-01'
cli.azure.cli.core.sdk.policies:     'x-ms-correlation-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'Azure-AsyncNotification': 'Enabled'
cli.azure.cli.core.sdk.policies:     'x-ms-arm-service-request-id': 'xxxxxxxx-xxxx-xxxx-XXXX-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
cli.azure.cli.core.sdk.policies:     'Server': 'Microsoft-HTTPAPI/2.0, Microsoft-HTTPAPI/2.0'
cli.azure.cli.core.sdk.policies:     'x-ms-ratelimit-remaining-subscription-writes': '1199'
cli.azure.cli.core.sdk.policies:     'x-ms-routing-request-id': 'JAPANEAST:20230108T132147Z:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
cli.azure.cli.core.sdk.policies:     'X-Content-Type-Options': 'nosniff'
cli.azure.cli.core.sdk.policies:     'Date': 'Sun, 08 Jan 2023 13:21:46 GMT'
cli.azure.cli.core.sdk.policies: Response content:
cli.azure.cli.core.sdk.policies: {
  "name": "rule999",
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999",
  "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"",
  "type": "Microsoft.Network/networkSecurityGroups/securityRules",
  "properties": {
    "provisioningState": "Updating",
    "description": "desc999",
    "protocol": "Tcp",
    "sourcePortRange": "*",
    "destinationPortRange": "443",
    "sourceAddressPrefix": "203.0.113.0/24",
    "destinationAddressPrefix": "*",
    "access": "Allow",
    "priority": 999,
    "direction": "Inbound",
    "sourcePortRanges": [],
    "destinationPortRanges": [],
    "sourceAddressPrefixes": [],
    "destinationAddressPrefixes": []
  }
}

レスポンスコードは 200 でした。リファレンスによると 200 が更新、201 が作成とのことです。今回は既存のルールに description を設定するという更新なので 200 なのでしょう。

レスポンスボディには、更新後の結果が返ってきました。description も設定されています。

その他のリクエスト(処理の完了待ち)

ここまでのログでは省略しましたが、設定が完了したかどうか調べるようなリクエストもされていました。

--no-wait オプションをつけると、このリクエストをしなくなりました。

ここまでで、--debug オプションによるリクエストの内容を確認できました。

方法2: ログファイルの有効化

次は、az コマンドのオプションではなく、設定ファイルでログファイルを有効にして、そのファイルを確認する方法です。

ひと手間かかりますが、通常の出力とデバッグ出力を分けられる点では便利です。

設定の準備

デフォルトでは、az コマンドのログ情報はファイルに出力されません。出力させるには構成ファイル(デフォルト ~/.azure/config)での仕込みが必要です。

~/.azure/config:

[logging]
enable_log_file = true

上記の設定で、ファイル出力が有効になります。出力先ファイルはデフォルトでは ~/.azure/logs/ ディレクトリの az.log でした。ディレクトリは [logging] セクションの log_dir という設定項目で変更できます。

構成ファイル内の全項目はドキュメントを参照してください。

ログファイル有効化状態での実行例

前述のログファイル出力を有効化した状態で、az コマンドを実行します。

内容は --debug オプション利用時と同じく、既存のネットワークセキュリティグループ nsg01 に対して、description を設定する例です。

違いは --debug オプションを指定しないことです。

az network nsg rule update -g sakana --nsg-name nsg01 \
    -n rule999 --description desc999

実行結果の確認

コマンドの実行結果自体は通常と同じです。

~/.azure/logs/az.log を確認するとログが書き込まれていました。リクエストやレスポンスの内容の表示は --debug オプション利用時とおなじような感じなので、説明は省略します。時刻も載ってるところが特徴かなと思います。

...(略)...
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies : Request URL: 'https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/sakana/providers/Microsoft.Network/networkSecurityGroups/nsg01/securityRules/rule999?api-version=2022-01-01'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies : Request method: 'GET'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies : Request headers:
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'Accept': 'application/json'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'x-ms-client-request-id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'CommandName': 'network nsg rule update'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'ParameterSetName': '-g --nsg-name -n --description'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'User-Agent': 'AZURECLI/2.43.0 (PIP) (AAZ) azsdk-python-core/1.26.2 Python/3.9.16 (macOS-12.6-x86_64-i386-64bit)'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies :     'Authorization': '*****'
2419 : 2023-01-09 20:09:04,748 : DEBUG : cli.azure.cli.core.sdk.policies : Request body:
2419 : 2023-01-09 20:09:04,749 : DEBUG : cli.azure.cli.core.sdk.policies : This request has no body
...(略)...

おわりに

Azure CLI が裏で叩いてる REST API のリクエストとレスポンスを確認する方法として、

  • --debug オプションの利用
  • ログファイルの有効化

を試してみました。

Azure CLI を使っていて、これどうやってだろう?というときになったときに使っていきたいと思います。

[Ansible] aws_ec2 インベントプラグインで CloudFormation のスタック名でグループ化やフィルターする

この記事は、Ansible Advent Calendar 2022 (Adventar 版) の 24日目の記事です。

はじめに

Ansible には AWS の EC2 インスタンスの情報を取得し、動的なインベントリとして扱える amazon.aws.aws_ec2_inventory というインベントリプラグインがあります。

スタック名でグループ化やフィルターするのは、個人的にまぁまぁ使う割には指定の仕方を忘れるので、記事として書いておきます。

CloudFormation で作られたリソースには aws:cloudformation:stack-name タグの値に スタック名 が設定されます。これを利用してフィルターします。

スタック名でグループ化する

動的にグループを作れるkeyed_groups オプションを利用します。

---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1    # この例では東京リージョン
keyed_groups:
  - key: tags["aws:cloudformation:stack-name"]    # ポイント
hostnames:
  - tag:Name

ansible-inventory を使ってグループ関係を表示して確認します。

$ ansible-inventory -i inventory_aws_ec2.yml --graph
@all:
  |--@_test_stack02:
  |  |--User1_CSR1
  |  |--User1_CSR2
  |  |--User1_controller
  |--@_test_stack02:
  |  |--User2_CSR1
  |  |--User2_CSR2
  |  |--User2_controller
  |--@aws_ec2:
  ...(略)...

スタック名 test-stack01test-stack02 によってグループ化された結果が表示されました。スタック名に含まれる - は、グループの名前に使うときは _ に変換されます。

また、上記の結果のように keyed_groups で作ったグループ名は、デフォルトで _ が先頭に付加されます。これは、デフォルトの prefix である "" (空文字)と、デフォルトの separator である _ が結合した結果です。

もし、何も先頭に付加させたくない場合は、以下のように指定します(抜粋)。

keyed_groups:
  - key: tags["aws:cloudformation:stack-name"]
    separator: ""     # 追加ポイント

スタック名でフィルターする

以下のサンプルでは、CloudFormation のスタック名 test-stack01 によって作られた EC2 インスタンスをフィルターする例です。

---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1    # この例では東京リージョン
include_filters:
  - tag:aws:cloudformation:stack-name: test-stack01    # ポイント
hostnames:
  - tag:Name

"test*"*stack* なども指定できます。

補足

ファイル名は、aws_ec2.yml か、aws_ec2.yaml で終わる必要があります。

参考

tekunabe.hatenablog.jp

[Ansible] 2022年の Ansible 関連リリースまとめ

この記事は Ansible Advent Calendar 2022 23日目の記事です。

はじめに

2022年もAnsible関連で様々なリリースがありました。ansible-core や AAP も予定通りバージョンアップされてきました。

ansible-rulebook(developer preview)もインパクトがありました。ネットワーク的には paramiko から ansible-pylibssh へ以降するの流れがでてきました。

個人的にポイントだと思ったものをまとめます。

2021年版はこちら

2022年3月

ansible-lint 6.0.0 リリース

  • ansible 2.9 のサポート終了
  • Python 3.6、3.7 のサポート終了
  • 設定ファイル .config/ansible-lint.yml を読み込むように
  • 検出だけなく一部修正も

changelog

Release v6.0.0 · ansible/ansible-lint · GitHub

2022年4月

ansible-navigator 2.0.0 リリース

changlog

Release v2.0.0 · ansible/ansible-navigator · GitHub

netcommon 3.0.0 リリース

  • デフォルトで paramiko より anisble-pylibssh を優先して利用するように
  • direct execution (import_modules: true)がデフォルトで有効に

changlog

ansible.netcommon/CHANGELOG.rst at main · ansible-collections/ansible.netcommon · GitHub

2022年5月

AAP 2.2 リリース

  • Automation mesh のトポロジーを画面上で表示可能に
  • RHEL 9 サポート
  • Automation Controller は 2.3

公式ブログ

What's new in Ansible Automation Platform 2.2

リリースノート

Red Hat Ansible Automation Platform Release Notes Red Hat Ansible Automation Platform 2.2 | Red Hat Customer Portal

ansible 2.9 の upstream support 終了

As of today, upstream Ansible 2.9 is officially end of life.

Ansible 2.9 Upstream Life Cycle Announcement

2022年6月

ansible-core 2.13.0 リリース

  • マネージドノード側で Python 2.6 のサポート終了 (参考)

changelog

ansible/CHANGELOG-v2.13.rst at stable-2.13 · ansible/ansible · GitHub

Porting guide

Ansible-core 2.13 Porting Guide — Ansible Core Documentation

ansible 6 リリース

changelog

ansible-build-data/CHANGELOG-v6.rst at main · ansible-community/ansible-build-data · GitHub

Porting guide

Ansible 6 Porting Guide — Ansible Documentation

同梱コレクション

ansible-build-data/ansible-6.0.0.deps at main · ansible-community/ansible-build-data · GitHub

2022年9月

ansible-pylibssh 1.0.0 リリース

  • Ansible から libssh を利用する場合に必要

GitHub - ansible/pylibssh: Python bindings specific to Ansible use case for libssh https://www.libssh.org/]

関連

[Ansible] 「ansible-pylibssh not installed, falling back to paramiko」という警告の意味と対処 - てくなべ (tekunabe)

[Ansible] paramiko と ansible-pylibssh のどっちが使われたか確認する方法 - てくなべ (tekunabe)

[Ansible] 「ansible-pylibssh not installed, falling back to paramiko」という警告の意味と対処 - てくなべ (tekunabe)

2022年10月

ansible-rulebook リリース (developer preview

Event-Driven Ansible を実現する ansible-rulebook

公式ブログ Ansible Blog | Ansible.com | Event Driven Automation

関連 ansible-rulebook カテゴリーの記事一覧 - てくなべ (tekunabe)

cisoc.ios どのネットワーク系コレクション 4.0.0 リリース

  • local コネクションプラグインprovider オプションの組み合わせによる接続情報の古い指定方法は廃止

changelog (cisco.ios) cisco.ios/CHANGELOG.rst at main · ansible-collections/cisco.ios · GitHub

ansible.netcommon コレクション 4.0.0 リリース

  • deprecated だった古いモジュールがいよいよ削除

changelog

ansible.netcommon/CHANGELOG.rst at main · ansible-collections/ansible.netcommon · GitHub

AWX 21.7.0 リリース

changelog

Release 21.7.0 · ansible/awx · GitHub

ansible.snmp コレクション 1.0.0 リリース

  • getsetwalk モジュール

Ansible Galaxy

2022年11月

ansible-core 2.14.0 リリース

changelog

ansible/CHANGELOG-v2.14.rst at stable-2.14 · ansible/ansible · GitHub

Porting guide

Ansible-core 2.14 Porting Guide — Ansible Core Documentation

ansible 7 リリース

changelog

ansible-build-data/CHANGELOG-v7.rst at main · ansible-community/ansible-build-data · GitHub

Porting guide

Ansible 7 Porting Guide — Ansible Documentation

同梱コレクション

ansible-build-data/ansible-7.0.0.deps at main · ansible-community/ansible-build-data · GitHub

AAP 2.3 リリース

  • Automation Controller は 4.3
  • プレビュー状態の Event-Driven Ansible
  • Validate Contents

公式ブログ

What's new in Ansible Automation Platform 2.3

What’s new in Ansible Automation Platform 2.3 | Red Hat Developer

リリースノート

Red Hat Ansible Automation Platform Release Notes Red Hat Ansible Automation Platform 2.2 | Red Hat Customer Portal

ドキュメント

Product Documentation for Red Hat Ansible Automation Platform 2.3 | Red Hat Customer Portal

2022年12月

AAP on AWS

AnsilbleFest 2022 で発表があった、AAP on AWS が利用可能に。

Red Hat Ansible Automation Platform now available in AWS Marketplace

おわりに

2022年の Ansible 関連の気になったリリースをまとめました。 ansible-core / ansible については、半年ごとのリリースが馴染んできたように思います。

2023年はどんなリリースがあるでしょうか。ansible-rulebook の動きが一番気になっています。

[Ansible] ansible-galaxy collection install がオフライン環境で少し便利になった(ansible-core 2.13.5 以上)

この記事は、Ansible Advent Calendar 2022 (Adventar 版) の 23日目の記事です。

はじめに

コレクションをインストールするための ansible-galaxy collection install というコマンドがあります。

コレクション名(cisco.ios など)を指定して、オンラインでインストールするのがよくある使い方です。コレクション名の代わりに、予めコレクションを tar.gz でダウンロードしておき、そのファイル名を指定すると、オフライン環境でもコレクションをインストールできます。

ところが ansible-core 2.13.4 までは、依存コレクションがあるコレクションは扱いが不便な面がありました。具体的には、すでに依存コレクションがインストールされている状態でも、Ansible Galaxy に接続を試みてしまう点です (ansible.cfg などの設定による)。これを避けるには、インストール済みのコレクションの分まで tar.gz を用意する必要がありました。

ありがたいことに、ansible-core 2.13.5 でこの点が改善されました。

ansible-galaxy - remove extra server api call during dependency resolution for requirements and dependencies that are already satisfied (#77443).

この改善された挙動を、比較しながら確かめてみます。

前提環境

以下の3つのコレクションがすでにインストールされている状態から始めます。

% ansible-galaxy collection list

# /Users/sakana/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
ansible.netcommon 4.1.0  
ansible.utils     2.8.0  
cisco.ios         4.0.0  

cisco.iosansible.netcommon に依存していて、さらに ansible.netcommonansible.utils に依存しています。

ansible.cfg 上は、Galaxy 接続の設定はデフォルトのままなので、Ansible Galaxy に接続するようになっています。

オフラインでコレクションを追加インストールする

今回は、arista.eos コレクション 6.0.0 をオフラインで追加インストールすることにます。arista.eos コレクションも cisco.ios と同様に、ansible.netcommon コレクションに依存していて、さらに ansible.netcommonansible.utils に依存しています。

tar.gz をダウンロード

オフラインインストールなので、あらかじめ tar.gz ファイルをダウンロードして用意します。ダウンロードは、インターネット接続性がある環境で以下のいずれかの方法でしておきます。

ansible-core 2.13.4 の場合

まずは、少し不便だった ansible-core 2.13.4 での場合です。ansible-galaxy collection install arista-eos-6.0.0.tar.gz でインストールを試みます。

$ ansible-galaxy collection install arista-eos-6.0.0.tar.gz

Starting galaxy collection install process
Process install dependency map
[WARNING]: Skipping Galaxy server https://galaxy.ansible.com/api/. Got an unexpected error when getting
available versions of collection ansible.netcommon: Unknown error when attempting to call Galaxy at
'https://galaxy.ansible.com/api/v2/collections/ansible/netcommon/': <urlopen error [Errno 8] nodename nor
servname provided, or not known>
ERROR! Unknown error when attempting to call Galaxy at 'https://galaxy.ansible.com/api/v2/collections/ansible/netcommon/': <urlopen error [Errno 8] nodename nor servname provided, or not known>

接続性がないため、https://galaxy.ansible.com/api/v2/collections/ansible/netcommon/ への接続エラーが発生しました。

このように、ansible.netcommon はすでにインストールされいるにも関わらず、Ansible Galaxy への接続を試みてしまいます。オフライン環境では困ったことになります。

この現象を避けるには、依存コレクションもセットで ansible-galaxy collection install で指定する方法があります。

$ ansible-galaxy collection install arista-eos-6.0.0.tar.gz ansible-netcommon-4.1.0.tar.gz ansible-utils-2.8.0.tar.gz 
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Installing 'arista.eos:6.0.0' to '/Users/sakana/.ansible/collections/ansible_collections/arista/eos'
arista.eos:6.0.0 was installed successfully
Installing 'ansible.netcommon:4.1.0' to '/Users/sakana/.ansible/collections/ansible_collections/ansible/netcommon'
ansible.netcommon:4.1.0 was installed successfully
Installing 'ansible.utils:2.8.0' to '/Users/sakana/.ansible/collections/ansible_collections/ansible/utils'
ansible.utils:2.8.0 was installed successfully

ただ、すでにインストールされている依存コレクション(今回は ansible.netcommonansible.utils)がすでにインストールされているにも関わらず、依存コレクション分の tar.gz を用意しておくという手間がありました。

ansible-core 2.13.5 の場合 (ansible-core 2.14.0 以上も)

先述の手間がなくなった、ansible-core 2.13.5 の場合をたしめします(ansible-core 2.14.0 以上でも同じです)。

まず、状態を戻すため、 arista.eos コレクションを削除します。

rm -fr ~/.ansible/collections/ansible_collections/arista/eos

続いて arista-eos-6.0.0.tar.gz だけ指定してインストールします。

$ ansible-galaxy collection install arista-eos-6.0.0.tar.gz

Starting galaxy collection install process
Process install dependency map
Starting collection install process
Installing 'arista.eos:6.0.0' to '/Users/sakana/.ansible/collections/ansible_collections/arista/eos'
arista.eos:6.0.0 was installed successfully
'ansible.netcommon:4.1.0' is already installed, skipping.
'ansible.utils:2.8.0' is already installed, skipping.

今度は、不要な Ansible Galaxy への接続がなくなり、依存コレクションがすでにインストールされていることも検出され、無事に arista.eos がインストールできました。

$ ansible-galaxy collection list

# /Users/sakana/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
ansible.netcommon 4.1.0  
ansible.utils     2.8.0  
arista.eos        6.0.0  
cisco.ios         4.0.0  

changelog を見ると、この改善は Bug fix という扱いでした。

ansible-core 2.14.0 はよりオフラインを考慮した --offline オプションが追加

先程試した改善点でも、ある程度 Ansible Galaxy への接続は抑えられていましたが、例えば依存コレクションがまだインストールされていないときは、Ansible Galaxy への接続を試みます。オフライン環境では、このような接続試行も抑えたほうがより丁寧かと思います。

そんなときは、ansible-galaxy collection install に ansible-core 2.14.0 で追加された --offline オプションが便利です。

これも試してみます。

依存コレクション ansible.utils がまだインストールされていないときに --offline オプションなし(これまで通り)インストールを試みると、以下のエラーになります。接続エラーです。

$ ansible-galaxy collection install arista-eos-6.0.0.tar.gz
[WARNING]: Skipping Galaxy server https://galaxy.ansible.com/api/. Got an unexpected error when
getting available versions of collection ansible.utils: Unknown error when attempting to call
Galaxy at 'https://galaxy.ansible.com/api/v2/collections/ansible/utils/': <urlopen error [Errno
8] nodename nor servname provided, or not known>
ERROR! Unknown error when attempting to call Galaxy at 'https://galaxy.ansible.com/api/v2/collections/ansible/utils/': <urlopen error [Errno 8] nodename nor servname provided, or not known>

今度は、--offline オプションありでインストールします。

ansible-galaxy collection install  arista-eos-6.0.0.tar.gz --offline
Starting galaxy collection install process
Process install dependency map
ERROR! Failed to resolve the requested dependencies map. Could not satisfy the following requirements:
* ansible.utils:>=2.5.2 (dependency of ansible.netcommon:4.1.0)

オフラインなので、インストールできなかった事自体は同じですが、--offline オプションありのほうが以下の点で、オフライン環境にやさしい挙動になっていると感じました。

  • 接続エラーが表示されない
  • 依存コレクション名に加えて、どのコレクションが依存しているか、どのバージョンか、も表示される

なお、--offline オプションのヘルプは以下のように記載されています(ドキュメントはこちら)。

  --offline             Install collection artifacts (tarballs) without contacting any
                        distribution servers. This does not apply to collections in remote Git
                        repositories or URLs to remote tarballs.

おわりに

オフライン環境は何かと制約が大きく、できれば避けたいところですが、今回ためした改善点はいざというときに活用できそうです。

関連

2.13.5 changelog

  • ansible-galaxy - remove extra server api call during dependency resolution for requirements and dependencies that are already satisfied (#77443).

2.14.0 changelog

  • ansible-galaxy - remove extra server api call during dependency resolution for requirements and dependencies that are already satisfied (#77443).

  • ansible-galaxy collection install - add an --offline option to prevent querying distribution servers (#77443).

[Ansible] community.zabbix 内のモジュールを httpapi で使うとエラー「Invalid JSON response」となる場合の対処

この記事は、Ansible Advent Calendar 2022 (Adventar 版) の 22日目の記事です。

はじめに

community.zabbix コレクション内のモジュールは、最近のバージョンでは httpapi コネクションプラグインに対応してきたようです。

これにより、各タスクで server_urllogin_userlogin_password のような接続先や認証情報の指定をする必要がなくなります。

その代わり ansible_hostansible_useransible_password のような変数が利用できるようになります。

ちょっと試したところ、「Invalid JSON response」というが発生しました。今回の環境では ansible_zabbix_url_path を調整する必要がありました。

エラーの内容と対処をまとめます。

  • 環境
    • ansible 7.0.0
    • ansible-core 2.14.0
    • community.zabbix 1.9.0
    • Zabbix Server 6.2

試した Playbook と変数定義ファイル

Playbook(ホストを追加するだけのかんたんなもの)

---
- name: Zabbix test
  hosts: zabbix
  gather_facts: false

  tasks:
    - name: zabbix_host
      community.zabbix.zabbix_host:
        host_name: test_host
        host_groups:
          - Virtual machines
          - Linux servers

インベントリ

[zabbix]
zabbix01 ansible_host=192.168.1.9

グループ変数ファイル group_vars/zabbix.yml

---
ansible_network_os: community.zabbix.zabbix
ansible_connection: ansible.netcommon.httpapi
ansible_httpapi_port: 80        # 簡易的な検証環境
ansible_httpapi_use_ssl: false
ansible_user: dummy_admin
ansible_password: dummy_password

エラー

Playbook を実行すると以下のエラーになりました。

抜粋

The error was: ansible.module_utils.connection.ConnectionError: Invalid JSON response: File not found.

全エラー

% ansible-playbook -i inventory.ini test.yml

PLAY [Zabbix test] *******************************************************************************************************************************************************

TASK [zabbix_host] *******************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.module_utils.connection.ConnectionError: Invalid JSON response: File not found.
fatal: [zabbix01]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last):\n  File \"/Users/sakana/.ansible/tmp/ansible-local-60674wfla4vy1/ansible-tmp-1670681807.339254-60677-162818002374491/AnsiballZ_zabbix_host.py\", line 107, in <module>\n    _ansiballz_main()\n  File \"/Users/sakana/.ansible/tmp/ansible-local-60674wfla4vy1/ansible-tmp-1670681807.339254-60677-162818002374491/AnsiballZ_zabbix_host.py\", line 99, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/Users/sakana/.ansible/tmp/ansible-local-60674wfla4vy1/ansible-tmp-1670681807.339254-60677-162818002374491/AnsiballZ_zabbix_host.py\", line 47, in invoke_module\n    runpy.run_module(mod_name='ansible_collections.community.zabbix.plugins.modules.zabbix_host', init_globals=dict(_module_fqn='ansible_collections.community.zabbix.plugins.modules.zabbix_host', _modlib_path=modlib_path),\n  File \"/usr/local/Cellar/python@3.9/3.9.10/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py\", line 210, in run_module\n    return _run_module_code(code, init_globals, run_name, mod_spec)\n  File \"/usr/local/Cellar/python@3.9/3.9.10/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py\", line 97, in _run_module_code\n    _run_code(code, mod_globals, init_globals,\n  File \"/usr/local/Cellar/python@3.9/3.9.10/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py\", line 87, in _run_code\n    exec(code, run_globals)\n  File \"/var/folders/2s/h6djr0fn3773tj1zy4mg442c0000gn/T/ansible_community.zabbix.zabbix_host_payload_87ej1ch7/ansible_community.zabbix.zabbix_host_payload.zip/ansible_collections/community/zabbix/plugins/modules/zabbix_host.py\", line 1289, in <module>\n  File \"/var/folders/2s/h6djr0fn3773tj1zy4mg442c0000gn/T/ansible_community.zabbix.zabbix_host_payload_87ej1ch7/ansible_community.zabbix.zabbix_host_payload.zip/ansible_collections/community/zabbix/plugins/modules/zabbix_host.py\", line 1083, in main\n  File \"/var/folders/2s/h6djr0fn3773tj1zy4mg442c0000gn/T/ansible_community.zabbix.zabbix_host_payload_87ej1ch7/ansible_community.zabbix.zabbix_host_payload.zip/ansible_collections/community/zabbix/plugins/module_utils/base.py\", line 33, in __init__\n  File \"/var/folders/2s/h6djr0fn3773tj1zy4mg442c0000gn/T/ansible_community.zabbix.zabbix_host_payload_87ej1ch7/ansible_community.zabbix.zabbix_host_payload.zip/ansible_collections/community/zabbix/plugins/module_utils/api_request.py\", line 53, in api_version\n  File \"/var/folders/2s/h6djr0fn3773tj1zy4mg442c0000gn/T/ansible_community.zabbix.zabbix_host_payload_87ej1ch7/ansible_community.zabbix.zabbix_host_payload.zip/ansible/module_utils/connection.py\", line 200, in __rpc__\nansible.module_utils.connection.ConnectionError: Invalid JSON response: File not found.\n\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

PLAY RECAP ***************************************************************************************************************************************************************
zabbix01                   : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

レスポンスが正しい JSON ではないようで、何でだろうとしばらく悩んでいました。調べてみると、Ansible からは http://IPアドレス/zabbix/api_jsonrpc.php にリクエストしているのに対して、今回 docker で構築した Zabbix の場合、正しいリクエスト先は http://IPアドレス/api_jsonrpc.php でした(/zabbix が不要)。

モジュール側のデフォルトの挙動は、よくある環境に合わせているようです。

今回の場合は、404 の HTML が返ってきていました。なので 「Invalid JSON response」と判断されていたようです。

対策

ansible_zabbix_url_path という変数で、パスを調整できる事がわかりました。

ということで、group/zabbix.yml に以下を追加して Playbook を実行したら正常にできました。

ansible_zabbix_url_path: ""

関連

github.com

github.com

[Ansible] URLの文字列をチェックするテストプラグインをためしてた

この記事は、Ansible Advent Calendar 2022 (Adventar 版) の 21日目の記事です。

はじめに

ansible-core 2.14.0 で、URL、URI、URN の文字列をチェックするテストプラグインが追加されました。

使用例は、それぞれのドキュメントページの Examples にいくつか掲載されています。

この中で、一番使用頻度の高そうな url テストプラグインを試してみます。

  • 動作確認環境
    • ansible-core 2.14.0

おためし

公式の Examples

まずは、url テストプラグインの Examplesに掲載されているものを試してみました。

true / false の結果をコメントで付記します。(uriと混在していますね)

---
- hosts: localhost
  gather_facts: false
  connection: local

  tasks:
    - name: debug url test
      debug:
        msg: 
          # true
          - "{{ 'http://example.com' is url }}"
          # true
          - "{{ 'mailto://nowone@example.com' is not uri(schemes=['ftp', 'ftps', 'sftp', 'file']) }}"
          # false
          - "{{ 'mailto://nowone@example.com' is not uri }}"
          # true
          - "{{ 'ftp://admin:secret@example.com/path/to/myfile.yml' is url }}"

少し処理を追ってみる

処理が書いてる plugins/test/uri.pyを少し見ていました。ここには urluriurn の 3つのテストプラグインがまとめて書かれています。

url テストプラグインの処理は、最初に is_uri つまり uri テストプラグイン相当の処理がかかるようです。schemes のチェックは is_uri 内で行われています。

その後 Pythonurllib.parse.urlparseが通るかを調べています。

http://https:// のみに絞りたい場合

is url のように schemes 未指定だと、いろんなスキーマが許可されます。

例えば、以下は true です。

"{{ `'mailto://nowone@example.com' is url }}"

もし http://https:// のみに絞りたいときはありそうだなと思っていくつかのパターンを試しました。

---
- hosts: localhost
  gather_facts: false
  connection: local

  tasks:
    - name: debug url test
      ansible.builtin.debug:
        msg: 
          # true
          - "{{ 'http://example.com' is url(schemes=['http']) }}"
          # false
          - "{{ 'https://example.com' is url(schemes=['http']) }}"

          # false
          - "{{ 'http://example.com' is url(schemes=['https']) }}"
          # true
          - "{{ 'https://example.com' is url(schemes=['https']) }}"

          # true
          - "{{ 'http://example.com' is url(schemes=['http', 'https']) }}"
          # true
          - "{{ 'https://example.com' is url(schemes=['http', 'https']) }}"
          # true
          - "{{ 'https://example.com:8080' is url(schemes=['http', 'https']) }}"

          # false (惜しい)
          - "{{ 'https//example.com' is url(schemes=['http', 'https']) }}"
          # false (惜しい)
          - "{{ 'https:/example.com' is url(schemes=['http', 'https']) }}"

          # false
          - "{{ 'mailto://nowone@example.com' is url(schemes=['http', 'https']) }}"

http のみ、https のみ、httphttps、のそれぞれの動作が確認できました。

なお、テストコードでは、一部処理で http, https, ftp, ftps, ws, wss, fileschemes が指定されていました。

おわりに

これまでありそうでなかったという感じのテストプラグインでした。今後使う機会があるかもしれません。

参考

2.14.0 の Changelog

github.com

new tests url, uri and urn will verify string as such, but they don't check existance of the resource

関連 PR

github.com

[Ansible] ディクショナリのキーを元に、残す、除外する、置き換えるフィルターをためしてみた

この記事は、Ansible Advent Calendar 2022 (Adventar 版)20日目の記事です。

偶然ですが、同日 Qiita 版に同じフィルターを扱った記事がアップされています。こちらも是非ご覧ください。

ansible.utilsコレクションのフィルタ(keep_keys/remove_keys/replace_keys)を試す - うさラボ

はじめに

ansible.utils コレクションの 2.5.0 で、ディクショナリを操作する、以下の3つのフィルターが追加されました

No. フィルター名 概要
1. ansible.utils.keep_keys 特定のキー配下のみ残す
2. ansible.utils.remove_keys 特定のキー配下を取り除く
3. ansible.utils.replace_keys キーの名前を変更する

ディクショナリのキーをもとにして、残す、除外する、置き換える、といったことができます。

おそらく、json_query フィルター経由で JMESPath を使っても同様のことができると思います。(冒頭で紹介した別記事に例があります)

この記事では、上記3つのフィルターを試した結果をまとめます。

  • 環境
    • ansible 7.0.0
    • anislb-core 2.14.0
    • ansible.utils コレクション 2.7.0

1. 特定のキー配下のみ残す ansible.utils.keep_keys フィルター

ansible.utils.keep_keys フィルターは、target で指定したキー配下のみを残す加工をします。

元データの定義と ansible.utils.keep_keys フィルターをかけた結果を出力するサンプルの Playbook は以下のとおりです。

---
- name: Test manipulate keys
  hosts: localhost
  gather_facts: false
  connection: local

  vars:
    data:     # 共通
      - name: oikawa
        kind: fish
        description: kawaii
      - name: kingyo
        kind: fish
        description: totemo kawaii

  tasks:
    - name: Test keep_keys
      ansible.builtin.debug:
        msg: "{{ data | ansible.utils.keep_keys(target=target) }}"
      vars:
        target:
          - name    # これらの
          - kind    # キー配下のみを残す

SQL 文でいう、SELECT name, kind FROM data のようなイメージでしょうか。

出力結果は以下のとおりです。

TASK [Test keep_keys] *********************************************************
ok: [localhost] => {
    "msg": [
        {
            "kind": "fish",
            "name": "oikawa"
        },
        {
            "kind": "fish",
            "name": "kingyo"
        }
    ]
}

残すキーとして namekind として指定しているので、該当しない description キー配下がなくなりました。

2. 特定のキー配下を取り除く ansible.utils.remove_keys フィルター

ansible.utils.remove_keys フィルターは、target で指定したキー配下を取り除きます。

ちょうど、先程の ansible.utils.keep_keys フィルターと逆です。

元データの定義(先ほどと同じ)と ansible.utils.keep_keys フィルターをかけた結果を出力するサンプルの Playbook は以下のとおりです。

---
- name: Test manipulate keys
  hosts: localhost
  gather_facts: false
  connection: local

  vars:
    data:     # 共通
      - name: oikawa
        kind: fish
        description: kawaii
      - name: kingyo
        kind: fish
        description: totemo kawaii

  tasks:
    - name: Test remove_keys
      ansible.builtin.debug:
        msg: "{{ data | ansible.utils.remove_keys(target=target) }}"
      vars:
        target:
          - kind          # これらの
          - description   # キー配下を削除する

SQL 文でいう、SELECT * EXCEPT (kind, description) FROM data のようなイメージでしょうか。

出力結果は以下のとおりです。

TASK [Test remove_keys] *******************************************************
ok: [localhost] => {
    "msg": [
        {
            "name": "oikawa"
        },
        {
            "name": "kingyo"
        }
    ]
}

取り除くキーとして kinddescription として指定しているので、name キー配下だけが残りました。

3. キーの名前を変更する ansible.utils.replace_keys フィルター

先述の2つのフィルターとはやや性質が異なります。

ansible.utils.replace_keys フィルターは、target で指定したキーの名前を変更します。具体的には target 内の before で指定したキーの名前を after で指定した名前に変更します。

元データの定義(先ほどと同じ)と ansible.utils.replace_keys フィルターをかけた結果を出力するサンプルの Playbook は以下のとおりです。

---
- name: Test manipulate keys
  hosts: localhost
  gather_facts: false
  connection: local

  vars:
    data:     # 共通
      - name: oikawa
        kind: fish
        description: kawaii
      - name: kingyo
        kind: fish
        description: totemo kawaii

  tasks:
    - name: Test replace_keys
      ansible.builtin.debug:
        msg: "{{ data | ansible.utils.replace_keys(target=target) }}"
      vars:
        target:
          - before: name         # この名前のキーを
            after: name_changed  # この名前に変更する
          - before: kind         # この名前のキーを
            after: kind_changed  # この名前に変更する

SQL 文でいう、SELECT * EXCEPT(xx,xx) FROM data のようなイメージでしょうか。

出力結果は以下のとおりです。取り除くキーとして kinddescription として指定しているので、name キー配下だけが残りました。

TASK [Test replace_keys] ******************************************************
ok: [localhost] => {
    "msg": [
        {
            "description": "kawaii",
            "kind_changed": "fish",
            "name_changed": "oikawa"
        },
        {
            "description": "totemo kawaii",
            "kind_changed": "fish",
            "name_changed": "kingyo"
        }
    ]
}

namename_changed に、kindkind_changed に変更されました。特に指定していない description はそのままです。

補足

ここまではかんたんなサンプルを掲載しましたが、いくつか補足します。

キーを指定する際のマッチ条件

各フィルターのドキュメントにも記載されていますが、キーを指定する際のマッチ条件を指定する matching_parameter オプションがあります。

デフォルトは完全一致(大文字小文字も区別)です。他に選択できる値は以下のとおりです。

意味
starts_with 前方一致
ends_with 後方一致
regex 正規表現

例えば、matching_parameter: regex にして、^desc.+tion$ という正規表現を指定した場合。

    - name: Test keep_keys2
      ansible.builtin.debug:
        msg: "{{ data | ansible.utils.keep_keys(target=target,matching_parameter='regex') }}"
      vars:
        target:
          - ^desc.+tion$

以下のように、^desc.+tion$ にマッチするキーを description が残ります。

TASK [Test keep_keys] **********************************************************************
ok: [localhost] => {
    "msg": [
        {
            "description": "kawaii"
        },
        {
            "description": "totemo kawaii"
        }
    ]
}

単純なディクショナリでもできた

公式ドキュメントの Examples は、いずれも元データがディクショナリのリスト(テーブルデータのイメージ)だったので、本記事でも同類のデータを扱いました。

試した限り、ただのディクショナリでも意図通りにフィルターできました。

---
- name: Test manipulate keys
  hosts: localhost
  gather_facts: false
  connection: local

  vars:
    data:   # 単純なディクショナリ
      name: oikawa
      kind: fish
      description: kawaii

  tasks:
    - name: Test keep_keys
      ansible.builtin.debug:
        msg: "{{ data | ansible.utils.keep_keys(target=target) }}"
      vars:
        target:
          - name    # これらの
          - kind    # キー配下のみを残す

結果は以下のとおりです。

TASK [Test keep_keys] ***********************************************************
ok: [localhost] => {
    "msg": {
        "kind": "fish",
        "name": "oikawa"
    }
}

元データはそのまま

フィルターはあくまでフィルターなので、元のデータはそのままです。

今回はフィルター結果を debug モジュールで表示するだけでしたが、変数に格納したい場合は ansible.builtin.set_fact モジュールを使うなどします。

キーではなく中身を操作したい場合

ここまで取り上げたフィルターはあくまで、キーに対する操作です。

もし中身を操作したい場合は、公式ドキュメントのhttps://docs.ansible.com/ansible/latest/playbook_guide/playbooks_filters.html#combining-and-selecting-data が参考になります。

おわりに

ansible.utils コレクションの、ディクショナリのキーをもとにして、残す、除外する、置き換えるフィルターをためしてみました。

ansible.utils コレクションは、私の好きなコレクションの一つです。変数こねくり回す系の処理で、こんなことできないかな?と思ったら、Manipulating dataUsing filters to manipulate data のページのほか、ansible.utils コレクションを見るようにしています。