てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] Ansible 2.9 リリース、便利機能や注意点まとめ

f:id:akira6592:20191110130643p:plain:w400

■ はじめに

2019/10/31 に Ansible 2.9.0 がリリースされました。600以上のモジュールの追加や、機能追加、バグ修正機能削除機能の非推奨化、仕様変更が含まれています。

CHANGELOG にはなくてもひっそり追加されたオプションもありそうなので、お使いのモジュールの公式ドキュメンを一度見直すと新しい発見があるかもしれません。

大きめなトピックとしては以下のものがあります。

この記事では、CHANGELOGPorting Guide を中心に、気になった点をピックアップします。CHANGELOG だけ見ても、どのような変更か分かりにくいものもありますので、関連する PR や 公式ドキュメンをのページなどの URL も掲載します。

また、特性が分かるように、主観ですが【●便利そう】 【○地味に便利】【▲ちょっと注意】いうタグのようなものをつけています。Ansible 2.9 へのアップデート判断の材料になれば幸いです。(参考 Ansible 2.8 版はこちら [Ansible] Ansible 2.8 リリース、便利機能や注意点まとめ - てくなべ (tekunabe)

なお、ネットワークに関するものはさらにピックアップして、2019/11/20 開催予定の Ansiblejpネットワーク部 2019.11でもご紹介する予定です。リモートで視聴できるウェブ参加枠もありますので、よろしければご参加ください。

[2019/12/13 追記] 当日の資料はこちらです。

www.slideshare.net


■ 共通

Ansible Galaxy / Ansible Collections

制御 / 変数

その他


■ Package


■ Service


■ Cloud、VMware

■ Network


Windows


■ Database


■ Storage


■ Crypto

■ まとめ

Ansible 2.9.0の、CHANGELOGPorting Guide を中心に、気になった点と関連URLをまとめました。

なお、今後のメンテナンスリリースは、約3週間ごとになりそうです。次(おそらく Ansible 2.9.1)は、2019/11/21 頃とのことです。 Google グループ

[2019/11/14 追記] Security fix を含むためか、早めに Ansible 2.9.1 がリリースされました。

ansible/CHANGELOG-v2.9.rst at stable-2.9 · ansible/ansible · GitHub

おまけ

Ansible 2.x のコードネームは Led Zeppelin の曲名が由来です。Ansible 2.9 は 「Immigrant Song 」。

www.youtube.com

[ACI] WebUI で開いているオブジェクトの DN (Distinguished Name) を調べる(画面右上の Show Debug Info )

はじめに

APICAPI を叩く処理を書いているときに、BD、EPG などの各オブジェクトの DN (Distinguished Name)を知りたい場面はないでしょうか。

便利なことに、APIC Web UI で調べたいオブジェクトを開いて、右上のギアアイコンから「Show Debug Info」をクリックすると、そのオブジェクトの DN が画面下に表示されます。

例をご紹介します。


例1: EPG の DN を調べる

DN を調べたい EPG を開きます。

f:id:akira6592:20191107080831p:plain
EPG を開く

画面右上ギアアイコンの「Show Debug Info」をクリックします。

f:id:akira6592:20191107080850p:plain:w400
Show Debug Info

画面下に DN が表示されます。(ここでは uni/tn-t_test1/ap-ap_test1/epg-epg_test1)

f:id:akira6592:20191107080941p:plain
DN の表示

試しに Postman で この DNの情報 JSON 形式で GET してみます。

f:id:akira6592:20191107085504p:plain
Postman による確認

※ 予めログイン処理が必要 です。


例2: 物理インターフェースの DN を調べる

DN を調べたい インターフェースを開きます。

画面右上ギアアイコンの「Show Debug Info」をクリックすします。(ログイン中、一旦こその操作をした場合は、有効なままです。)

画面したに DN が表示されます。(ここでは topology/pod-1/node-101/sys/phys-[eth1/1])

こちでも試しに Postman で この DNの情報 JSON 形式で GET してみます。

f:id:akira6592:20191107085543p:plain
Postman による確認

なお、表示を隠す場合は「Hide Debug Info」をクリックします。

f:id:akira6592:20191107090459p:plain
Hide Debug Info

まとめ

以下の手順で オブジェクトの DN を確認できることをご紹介しました。

  • APIC Web UI で DN を調べたいオブジェクトを開く
  • 右上のギアアイコンから「Show Debug Info」をクリックする
  • そのオブジェクトの DN が画面下に表示さる

[Ansible][ACI] Vault で暗号化した秘密鍵を署名ベースの認証方式で利用する

【目次】

はじめに

Ansible の ACI モジュールが APIC に対してリクエストする認証方式には、パスワードベース(Password-based authentication)と、署名ベース(Signature-based authentication using certificates)があります。

署名ベース認証方式の仕組みと準備と Playbook の書き方については、以前の記事でとりあげました。 tekunabe.hatenablog.jp

今回の記事では、署名ベース認証方式を利用時に、Ansible Vault で暗号化した秘密鍵を利用する方法を、Ansible Engine と Ansible Tower それぞれのをご紹介します。

公式ドキュメントによると、Ansible 2.8 から暗号化された秘密鍵ファイルを利用できるようなったようです。

Ansible Tower では Tower らしく、Vault のパスワードを認証情報(Credential)に登録します。

  • 前提条件
    • APIC: Cisco DevNet Sandbox (APIC 4.1)
    • Ansible 2.8.2
    • Ansible Tower 3.5.2
    • 署名ベース認証の対象ユーザー: admin
      • 手順の検証容易性のために admin を利用していますが、運用環境では他のユーザーを利用を推奨


秘密鍵の暗号化(Ansible Engine / Ansible Tower 共通)

Ansible Engine、Ansible Tower 共通の準備として、まず秘密鍵を Ansible Vault で暗号化します。 admin.key が秘密鍵です。入力するパスワードは復号時に利用するので覚えておきます。

$ ansible-vault encrypt admin.key 
New Vault password:             (暗号化パスワードを入力)
Confirm New Vault password:     (暗号化パスワードを再度入力)
Encryption successful

秘密鍵が暗号化されました。中身を確認します。

$ cat admin.key 
$ANSIBLE_VAULT;1.1;AES256
34663837626431373834623430303234633730343262353336333331363562303365626435393231
6362663361323336656430313065646530626339643831320a303063343363353030363238393835
...(略)...

無事に暗号化されているのを確認できました。


■ Ansible Engine の場合

Ansible Engine で、暗号化された秘密鍵を利用する方法を紹介します。以下の 2つの方法があります。

  1. private_key オプションで file lookup plugin を利用して暗号化された秘密鍵データを取得
  2. 暗号化データを変数に割りてて、private_key オプションで変数を指定

ここでは、1 の file lookup plugin を利用します。2 の変数を利用する方法は 公式ドキュメントの「Using Ansible Vault to encrypt the private key」を参照してください。

Playbook

特定の EPG の方法を取得する簡単な Playbook で試します。

---
- hosts: apic
  gather_facts: no

  tasks:
    - name: 
      aci_epg:
        host: "{{ ansible_host }}"
        username: admin           # 証明書保有ユーザー名
        certificate_name: admin   # 証明書名
        private_key:  "{{ lookup('file', 'admin.key') }}"  # 秘密鍵
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        state: query
      register: result

なお、private_key オプションには本来、private_key: "admin.key" のように秘密鍵のパスも指定できますが、暗号化された秘密鍵ファイルのパスは指定できないのでご注意ください。秘密鍵ファイルとして認識されれず、Provided private key file '********' does not appear to be a private key. Please correct." というエラーになってしまいます。

実行

ansible-playbook コマンドに --ask-vault-pass をつけて、実行時に都度、暗号化パスワードを入力するようにします。

$ ansible-playbook -i inventory.ini query_epg.yml --ask-vault-pass
Vault password:     (暗号化パスワードを入力)

PLAY [query] ***********************************************************************

TASK [aci_epg] *********************************************************************
ok: [apic01]

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

無事に、暗号化された秘密鍵ファイルを利用して APIC に接続し、Playbook を実行できました。

他にもいろいろ方法はありますが、詳細は公式ドキュメントをご参照ください。

Ansible Vault — Ansible Documentation

Ansible Engine 側の場合はここまでです。


■ Ansible Tower の場合

次に、Ansible Tower で、暗号化された秘密鍵を利用する方法を紹介します。

Tower らしく、Vault というタイプの認証情報(Credential)に Vault のパスワードを登録します。 暗号化された秘密鍵のデータを利用する方法は 2つあります。

  1. private_key オプションで file lookup plugin を利用して暗号化された秘密鍵データを取得(Ansible Engineと同じ)
  2. インベントリ変数に暗号化データを設定して、private_key オプションで変数を指定

まず、認証情報(Credential)の登録手順を説明し、上記 2つの方法をそれぞれ説明します。

準備: 認証情報(Credential)の登録

まず、準備として Vault のパスワードを認証情報に登録します。

認証情報の名前を指定し、タイプとして Vault を指定します。VAULT パスワードには、ansible-vault コマンドで秘密鍵ファイルを暗号化した時に指定したパスワードを入力します。復号時に自動的に利用されます。

f:id:akira6592:20191105235033p:plain
タイプ Vault の認証情報の登録

認証情報の登録は以上です。

続いて、暗号化された秘密鍵のデータを利用する方法は 2つの方法を説明します。

1. file lookup plugin を利用する方法

1 つめは、Ansible Engine のときと同様に file lookup plugin を利用する方法です。 Playbook も同じです。

Playbook

---
- hosts: apic
  gather_facts: no

  tasks:
    - name: 
      aci_epg:
        host: "{{ ansible_host }}"
        username: admin           # 証明書保有ユーザー名
        certificate_name: admin   # 証明書名
        private_key:  "{{ lookup('file', 'admin.key') }}"  # 秘密鍵
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        state: query
      register: result

この Playbook を利用する ジョブテンプレートを作成しておきます。認証情報には先ほど作成した Vault のものを指定します。

f:id:akira6592:20191105235745p:plain:w400
認証情報の選択

f:id:akira6592:20191105235930p:plain
認証情報の選択

認証情報の登録、選択は以上です。

実行

ジョブテンプレートを実行します。ジョブ実行時には、認証情報に登録した Vault パスワードが自動で利用されるため、 入力は求められません。

f:id:akira6592:20191106000039p:plain
ジョブテンプレートの実行

無事に、暗号化された秘密鍵ファイルを利用して APIC に接続し、Playbook を実行できました。

これが、file lookup plugin を利用する方法でした。

2. インベントリ変数に暗号化データを設定して利用する方法

2 つめは、インベントリ変数に暗号化データを設定して利用する方法です。 暗号化された秘密鍵を、ファイルとして管理したくない場合に便利です。

インベントリの設定

APIC のホスト変数に暗号化された秘密鍵のデータそのものを指定します。 指定の方法が Ansible Engine の場合と異なるなるので注意が必要です。

private_key: !vault |
    $ANSIBLE_VAULT;1.1;AES256
    34663837626431373834623430303234633730343262353336333331363562303365626435393231
    6362663361323336656430313065646530626339643831320a303063343363353030363238393835
    39613164626531376334393062343164613639653265616639623037373035386462313934313931
    6533626665356566350a313665633163356130663035343736356165376231623133616266306538
...(略)...

ではなく、以下のような形式で指定する必要があります。 変数名に直接暗号化データを指定するのではなく、__ansible_vault というキーを挟んで、一行ごとに空行を挟んで指定します。

private_key:
  __ansible_vault: >-
      $ANSIBLE_VAULT;1.1;AES256
      
      34663837626431373834623430303234633730343262353336333331363562303365626435393231

      6362663361323336656430313065646530626339643831320a303063343363353030363238393835
      
      39613164626531376334393062343164613639653265616639623037373035386462313934313931
      
      6533626665356566350a313665633163356130663035343736356165376231623133616266306538
...(略)...

f:id:akira6592:20191106000214p:plain
少し特殊な暗号化データの指定

Playbook

private_key オプションには、変数名のみ指定します。

---
- hosts: apic
  gather_facts: no

  tasks:
    - name: 
      aci_epg:
        host: "{{ ansible_host }}"
        username: admin           # 証明書保有ユーザー名
        certificate_name: admin   # 証明書名
        private_key:  "{{ private_key }}"  # 秘密鍵(データそのもの格納された変数)
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        state: query
      register: result

この Playbook を利用する ジョブテンプレートを作成しておきます。

実行

ジョブテンプレートを実行します。先ほどと同じく、ジョブ実行時には、認証情報に登録した Vault パスワードが自動で利用されます。

f:id:akira6592:20191106000421p:plain
ジョブテンプレートの実行

無事に、暗号化された秘密鍵ファイルを利用して APIC に接続し、Playbook を実行できました。

これが、インベントリで暗号化データを変数に割り当てて利用する方法でした。


まとめ

Ansible Engine、Ansible Tower それぞれで、APIC に接続する秘密鍵を VAULT で暗号して利用する方法をご紹介しました。

  • file lookup plugin で暗号化された秘密鍵のファイルを指定して、暗号化パスワードを与えると、秘密鍵を利用できる
  • Ansible Tower では、Vault というタイプの認証情報に Vault パスワードを登録することで、ジョブ実行時に都度パスワードを入力する手間が省ける
  • 暗号化データを変数に設定して利用することで、秘密鍵をファイルとして管理する必要をなくせる
  • Tower の インベントリ変数に VAULT で暗号化したデータを指定する場合はちょっとしたコツがある

[Ansible][ACI] モジュールの冪等性と check モード(dry-run)の対応について

■ はじめに

Ansible の ACI モジュールの冪等性とcheck モード(dry-run)の対応について、簡単なサンプルをもとにご説明します。

  • 環境
    • Cisco DevNet Sandbox (APIC 4.1)
    • Ansible 2.9.0


■ 冪等性あり

すべて自分で検証したわけではないですが、APIC REST API の性質により、ACI モジュールには冪等性(idempotency)があるようです。

Cisco ACI Guide — Ansible Documentation

Because the APIC REST API is intrinsically idempotent and can report whether a change was made, the aci_rest module automatically inherits both capabilities and is a first-class solution for automating your ACI infrastructure.

ここでは aci_epg モジュールを利用して試してみます。

Playbook

tenant1 内の ap1 内に epg1 が存在する、という状態の Playbook を作成します。

---
- hosts: apic
  gather_facts: no
  
  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        state: present

実行(1回目)

f:id:akira6592:20191104225233p:plain:w400
epg1 はまだない
epg1 がまだない状態から、Playbook を初めて実行します。

$ ansible-playbook -i ../../inventory.ini epg_test.yml 

PLAY [apic] ************************************************************************

TASK [epg] *************************************************************************
changed: [apic01]

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

changed というステータスになり、無事に作成されました。

f:id:akira6592:20191104225255p:plain:w400
epg1 が作成された

実行(2回目)

先ほど epg1 が作られたあとに、もう一回実行します。

$ ansible-playbook -i ../../inventory.ini epg_test.yml 

PLAY [apic] ************************************************************************

TASK [epg] *************************************************************************
ok: [apic01]

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

今度は ok となり、特に設定変更が行われなかったことが分かります。


■ check モード(dry-run)も対応

ACI モジュールは、check モード(dry-run)にも対応しています。その Playbook の実行が、設定変更をもたらすのかどうかを、実際の設定変更をせずに知ることができます。ansible-plyaboo コマンドに --check オプションをつけます。

さきほど同じように aci_epg モジュールを利用して試してみます。

Playbook

tenant1 内の ap1 内に epg2 が存在する、という状態の Playbook を作成します。

---
- hosts: apic
  gather_facts: no
  
  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg2
        state: present

実行(--check あり)

--check オプションを付けて、check モードで実行します。

$ ansible-playbook -i ../../inventory.ini epg_test.yml --check

PLAY [apic] ************************************************************************

TASK [epg] *************************************************************************
changed: [apic01]

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

画面でも確認できるように、実際には epg2 は作成されていません。

f:id:akira6592:20191104225349p:plain
epg2 はまだない

なお、もう一度 --check オプションを付けて実行しても結果は同じく changed です。

実行(--check なし)

念の為、--check なしにして、実際に設定変更します。

$ ansible-playbook -i ../../inventory.ini epg_test.yml

PLAY [apic] ************************************************************************

TASK [epg] *************************************************************************
changed: [apic01]

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

ログ上は 同じく changed でが、今度は通常実行なので、実際に epg2 が作成されました。

f:id:akira6592:20191104225430p:plain
epg2 が作成された

--diff オプションには非対応?

--diff オプションには対応していないようです。--diff オプションをつけてもログに変化はありませんでした。

注意点: check モード で changed でも通常実行時にエラーになることも

check モードは、現状の設定と、Playbook で指定した状態に差分があるかどうかを示すものです。 無事に check モードで changed になっても、いざ実際に通常実行するとエラーになることもありえます。

例で示します。以下の Plyabook では tenant1 内の ap9999 (存在しない Application Profile)内に epg1 がある、という状態を示しています。

---
- hosts: apic
  gather_facts: no

  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1
        ap: ap9999    # 存在しない AP
        epg: epg1
        state: present

check モードで実行すると、changed になります。

$ ansible-playbook -i ../../inventory.ini epg_test.yml --check
...(略)...
TASK [epg] $ ***********************************************************************
changed: [apic01]
...(略)...

次に、通常実行するとエラーになりますになります。EPG は、 Tenant > Apprication Profile > EPG というオブジェクト階層ですが、Playbook で指定した Apprication Profile の ap9999 が存在しないためです。

$ ansible-playbook -i ../../inventory.ini epg_test.yml 
...(略)...
TASK [epg] *********************************************************

fatal: [apic01]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "error": {"code": "102", "text": "configured object ((Dn0)) not found Dn0=uni/tn-tenant1/ap-ap9999/epg-epg1, "}, "msg": "APIC Error 102: configured object ((Dn0)) not found Dn0=uni/tn-tenant1/ap-ap9999/epg-epg1, "}
...(略)...


■ まとめ

Ansible の ACI モジュールの冪等性とcheck モード(dry-run)の対応について、簡単なサンプルをもとにご説明しました。

  • 冪等性あり
  • check モード対応

[Ansible] Fortigate (fortiosモジュール)を httpapi コネクションプラグイン経由で触ってみた

はじめに

Ansible 2.8 以降、400以上大量に増えている fortios モジュール。Ansible 2.9 では httpapi コネクションプラグインにも対応しました。

httpapi コネクションプラグインに対応することにより、以下のようなメリットがあります。

  • Playbook がスッキリする
    • タスクごとに指定していた情報などを一括で変数で指定可能
  • インベントリ管理の統一化
    • サーバーなどと同じように、インベントファイルで、対象ホストを定義可能
  • 環境構築が少し楽に
    • Python ライブラリ fortiosapi が不要

従来の方式(legacy mode)を利用されたこちらの記事を拝見しました。

tenko.hatenablog.jp

こちらの記事を参考にさせていただいて、同じ処理を httpapi コネクションプラグインを利用するとどうなるか、という観点でご紹介します。

httpapi を利用できる fortios モジュールは Ansible 2.8 と 2.9 で追加されたモジュール

[2019/11/10 追記]

fortios_facts モジュールのように、Ansible 2.9 で追加されたモジュールには、公式ドキュメントの説明ページに

Support both legacy mode (local_action) and httpapi

と書いてあるので、httpapi に対応していることが分かります。

それだけでなく、fortios_firewall_address のような Ansible 2.8 で追加されたモジュールにまで遡って対応されているようでした。

色々調べていくと、モジュールのコード内に、httpapi と区別するための legacy_mode に関する定義があるモジュールが httpapi に対応しているであろう、とあたりがつきました。

これに基づいて調べた、httpapi に対応している fortios モジュールの一覧は以下のとおりです(Ansible 2.9.0 現在)。

結果としては、Ansible 2.8 と 2.9 で追加されたモジュールと一致しました。

クリックして広げる

準備

インベントリファイル、変数定義ファイル、Playbook を作成します。

インベントリファイル

対象の fortigate のホスト情報をインベントリに定義します。ここでは fortios というグループに所属させます。

  • inventory.ini
[fortios]
fortios01 ansible_host=10.0.0.254

後述の Playbook の hosts でこのグループ fortios を指定します。

変数定義ファイル

グループ fortios に所属するホストが利用する変数を定義します。 以下のように、認証情報や証明書検証の有無を指定できます。

  • group_vars/fortios.yml
ansible_user: admin                 # ユーザー名
ansible_password: xxxxxxx           # パスワード
ansible_network_os: fortios         # ネットワークOS
ansible_connection: httpapi         # httpapi コネクションプラグインを利用
ansible_httpapi_use_ssl: yes        # https を利用
ansible_httpapi_validate_certs: no  # 証明書の検証は無効

legacy mode で利用する各タスクに指定する接続情報と、httpapi コネクションプラグインの変数の対応は以下のとおりです。

項目 legacy mode のオプション httpapi コネクションプラグインの変数
ユーザー名 username ansible_user
パスワード password ansible_password
SSL/TLS の利用有無 https ansible_httpapi_use_ssl
証明書検証の有無 ssl_verify ansible_httpapi_validate_certs

なお、httpapi コネクションプラグインで利用できる変数の一覧は以下を参照してください。

docs.ansible.com

Playbook

fortios_facts モジュールを利用して情報を取得して、debug で出力させるサンプルです。

  • fortios_test.yml
---
- hosts: fortios    # localhost ではなくインベントリで定義したグループ
  gather_facts: false
  vars:
    vdom: "root"

  tasks:
  - name: Get Facts.
    fortios_facts:      # タスクに接続情報は不要
      vdom: "{{ vdom }}"
      gather_subset:
        - fact: 'system_status_select'
    register: getfact_result

  - name: Debug
    debug: 
      var: getfact_result

タスク(ここでは fortios_facts を利用ているタスク)には、接続情報( host username password オプション)は不要です。逆にタスクに接続情報が指定されていると、httpapi コネクションプラグインではく、legacy mode になるのでご注意ください。

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini forti_test.yml 

PLAY [fortios] *******************************************************************************

TASK [Get Facts.] ****************************************************************************
ok: [fortios01]

TASK [Debug] *********************************************************************************
ok: [fortios01] => {
    "getfact_result": {
        "ansible_facts": {
            "ansible_net_gather_network_resources": [],
            "ansible_net_gather_subset": [],
            "ansible_network_resources": {
                "system_status_select": {
                    "action": "select",
                    "build": 1010,
                    "http_method": "GET",
                    "name": "status",
                    "path": "system",
                    "results": {},
                    "serial": "FGxxxxxxxxxxxx",
                    "status": "success",
                    "vdom": "root",
                    "version": "v6.2.2"
                }
            },
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false,
        "warnings": [
            "Platform darwin on host fortios01 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information."
        ]
    }
}

PLAY RECAP ***********************************************************************************
fortios01                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

無事に接続して、システム情報が表示されました。

まとめ

Ansible から fortigate に対して、httpapi コネクションプラグインを利用して接続してみました。

前述の通り、Playbook がスッキリする、インベントリ管理の統一化、環境構築が少し楽に、といったメリットがありますので、対応しているモジュールであれば、httpapi コネクションプラグインが便利そうです。

ただ、Playbook 内に httpapi コネクションプラグインが利用できないモジュールも混在してる場合は、利用しているモジュールのすべてで利用できる方式に統一するほうがよさそうです。

参考

[2019/11/10 追記]

Python 2 系だと

addinfourl instance has no attribute

というエラーが発生するという情報がありました。

tenko.hatenablog.jp

[2019/12/05 追記]

Ansible 2.9.2 で Python 2/3 両方で使えるようになったようです。

ansible/CHANGELOG-v2.9.rst at stable-2.9 · ansible/ansible · GitHub

fortios httpapi plugin - fix the issue that fortios httpapi plugin does not support python2

[Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する(aci_rest モジュール版)

■ はじめに

以前の記事 [Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する - てくなべ (tekunabe)で、aci_epg_to_contract モジュール を利用して、EPG に紐付けられている Contract 一覧を取得するサンプルをご紹介しました。

一方で、aci_rest モジュール を利用する方法もあります。aci_rest モジュールでは、APIC REST API の仕様をより意識する必要がある代わりに、他のモジュールでは手の届かなかった柔軟な処理ができます。

たとえば、aci_epg_to_contract モジュールでは、Provide Contract、Consumed Contract それぞれ別に取得する必要がありました。aci_rest モジュールでは、リクエストする URL を工夫することで、Provide Contract、Consumed Contract 両方いっぺんに取得できます。

この記事では簡単なサンプルをもとにして説明します。

  • 環境
    • Cisco DevNet Sandbox (APIC 4.1)
    • Ansible 2.9.0

参考

aci_epg_to_contractモジュールを利用する場合はこちら

tekunabe.hatenablog.jp


APIC 側画面

f:id:akira6592:20191104110233p:plain:w400
EPG への Contract 紐付け設定画面

以前の記事 https://tekunabe.hatenablog.jp/entry/2019/11/04/ansible_aci_get_epg_contracts:titile と同様に、ある EPG

  • Provided Contract: c_sql、c_web
  • Consumed Contract: c_test

と設定されている状態を想定します。


■ Playbook

取得には、aci_rest モジュールを利用します。

ポイントは path オプション

aci_epg_to_contract モジュールでは、contract_type: provider のときにクエリストリングに rsp-subtree-class=fvRsProvを、contract_type: consumer のときに rsp-subtree-class=fvRsCons を付加します。

一方、今回利用する aci_rest モジュールでは、クエリストリングも自分で指定できます。rsp-subtree-class=fvRsProv,fvRsCons と指定することで、Provide Contract、Consumed Contract 両方いっぺんに取得するようにリクエストできます。

また、今回の場合、EPG の子オブジェクトまでの取得だけで十分なので、rsp-subtree=all ではなく、rsp-subtree=children を指定します。

参考: Cisco APIC REST API ユーザ ガイド - APIC REST API の使用 [Cisco Application Policy Infrastructure Controller(APIC)] - Cisco

少々雑ですが、各 Contract の情報をのまま debug で表示します。

---
- hosts: apic
  gather_facts: no

  tasks:
    # get contracts (provided/consumed)
    - name: get contracts
      aci_rest:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        method: get
        path: /api/mo/uni/tn-tenant1/ap-ap1/epg-epg1.json?rsp-subtree-class=fvRsProv,fvRsCons&rsp-subtree=children
      register: result
    
    # debug  contracts
    - name: debug contracts
      debug: 
        msg: "{{ item }}"
      loop: "{{ result.imdata[0].fvAEPg.children | default([]) }}"
      loop_control:
        label: "{{ ansible_loop.index0 }}"
        extended: yes


■ 実行

Playbook を実行します。

$ ansible-playbook -i ../../inventory.ini get_contracts_rest.yml 

PLAY [apic] ********************************************************************

TASK [get contracts] ***********************************************************
ok: [apic01]

TASK [debug contracts] *********************************************************
ok: [apic01] => (item=0) => {
    "msg": {
        "fvRsProv": {          //  Provided Contract
            "attributes": {
                "annotation": "",
                "childAction": "",
                "ctrctUpd": "ctrct",
                "extMngdBy": "",
                "forceResolve": "yes",
                "lcOwn": "local",
                "matchT": "AtleastOne",
                "modTs": "2019-11-04T01:39:51.572+00:00",
                "monPolDn": "uni/tn-common/monepg-default",
                "prio": "unspecified",
                "rType": "mo",
                "rn": "rsprov-c_sql",
                "state": "formed",
                "stateQual": "none",
                "status": "",
                "tCl": "vzBrCP",
                "tContextDn": "",
                "tDn": "uni/tn-tenant1/brc-c_sql",
                "tRn": "brc-c_sql",
                "tType": "name",
                "tnVzBrCPName": "c_sql",   // Contract 名
                "triggerSt": "triggerable",
                "uid": "15374",
                "updateCollection": "no"
            }
        }
    }
}
ok: [apic01] => (item=1) => {
    "msg": {
        "fvRsProv": {          //  Provided Contract
            "attributes": {
                "annotation": "",
                "childAction": "",
                "ctrctUpd": "ctrct",
                "extMngdBy": "",
                "forceResolve": "yes",
                "lcOwn": "local",
                "matchT": "AtleastOne",
                "modTs": "2019-11-04T01:39:51.572+00:00",
                "monPolDn": "uni/tn-common/monepg-default",
                "prio": "unspecified",
                "rType": "mo",
                "rn": "rsprov-c_web",
                "state": "formed",
                "stateQual": "none",
                "status": "",
                "tCl": "vzBrCP",
                "tContextDn": "",
                "tDn": "uni/tn-tenant1/brc-c_web",
                "tRn": "brc-c_web",
                "tType": "name",
                "tnVzBrCPName": "c_web",   // Contract 名
                "triggerSt": "triggerable",
                "uid": "15374",
                "updateCollection": "no"
            }
        }
    }
}
ok: [apic01] => (item=2) => {
    "msg": {
        "fvRsCons": {          //  Consumed Contract
            "attributes": {
                "annotation": "",
                "childAction": "",
                "ctrctUpd": "ctrct",
                "deplInfo": "",
                "extMngdBy": "",
                "forceResolve": "yes",
                "lcOwn": "local",
                "modTs": "2019-11-04T01:39:51.572+00:00",
                "monPolDn": "uni/tn-common/monepg-default",
                "prio": "unspecified",
                "rType": "mo",
                "rn": "rscons-c_test",
                "state": "formed",
                "stateQual": "none",
                "status": "",
                "tCl": "vzBrCP",
                "tContextDn": "",
                "tDn": "uni/tn-tenant1/brc-c_test",
                "tRn": "brc-c_test",
                "tType": "name",
                "tnVzBrCPName": "c_test",   // Contract 名
                "triggerSt": "triggerable",
                "uid": "15374",
                "updateCollection": "no"
            }
        }
    }
}

PLAY RECAP *********************************************************************
apic01                     : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

各 debug モジュールの取得で

  • Provided Contract: c_sql、c_web
  • Consumed Contract: c_test

となっていることを確認できました。

参考: query で取得したデータの全体

参考のために query した結果の全体の中身も掲載します。

クリックして広げて見る

{
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "failed": false,
    "imdata": [
        {
            "fvAEPg": {
                "attributes": {
                    "annotation": "",
                    "childAction": "",
                    "configIssues": "",
                    "configSt": "applied",
                    "descr": "",
                    "dn": "uni/tn-tenant1/ap-ap1/epg-epg1",
                    "exceptionTag": "",
                    "extMngdBy": "",
                    "floodOnEncap": "disabled",
                    "fwdCtrl": "",
                    "hasMcastSource": "no",
                    "isAttrBasedEPg": "no",
                    "isSharedSrvMsiteEPg": "no",
                    "lcOwn": "local",
                    "matchT": "AtleastOne",
                    "modTs": "2019-11-03T14:54:44.955+00:00",
                    "monPolDn": "uni/tn-common/monepg-default",
                    "name": "epg1",
                    "nameAlias": "",
                    "pcEnfPref": "unenforced",
                    "pcTag": "32771",
                    "prefGrMemb": "exclude",
                    "prio": "unspecified",
                    "scope": "2555904",
                    "shutdown": "no",
                    "status": "",
                    "triggerSt": "triggerable",
                    "txId": "17293822569102705193",
                    "uid": "15374"
                },
                "children": [
                    {
                        "fvRsCons": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "deplInfo": "",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rscons-c_test",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_test",
                                "tRn": "brc-c_test",
                                "tType": "name",
                                "tnVzBrCPName": "c_test",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_test",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-04T01:39:51.572+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_test]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "fvRsProv": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "matchT": "AtleastOne",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rsprov-c_sql",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_sql",
                                "tRn": "brc-c_sql",
                                "tType": "name",
                                "tnVzBrCPName": "c_sql",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_sql",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-03T15:28:38.764+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_sql]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "fvRsProv": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "matchT": "AtleastOne",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rsprov-c_web",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_web",
                                "tRn": "brc-c_web",
                                "tType": "name",
                                "tnVzBrCPName": "c_web",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_web",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-03T15:16:07.253+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_web]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ],
    "status": -1,
    "totalCount": 1,
    "warnings": [
        "Platform darwin on host apic01 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information."
    ]
}


■ まとめ

aci_rest モジュール を利用して、EPG に紐付けれられている Contract の一覧を取得しました。

aci_epg_to_contract モジュールではできない取得方法ができました。

使い分け方としては、基本は aci_epg_to_contract のような各オブジェクト用のモジュールを利用し、それでもできない場合のみ、aci_rest を利用する、といった形になるかと思います。

参考

[Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する - てくなべ (tekunabe)

[Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する(aci_epg_to_contract モジュール版)

■ はじめに

aci_epg_to_contract モジュール は、EPG に対して Contract を紐付けれられます。state オプションに query を指定することで、紐付けされている Contract 一覧を取得できます。

この記事では簡単なサンプルをもとにして説明します。

  • 環境
    • Cisco DevNet Sandbox (APIC 4.1)
    • Ansible 2.9.0

参考

aci_rest モジュール を利用する場合はこちら tekunabe.hatenablog.jp


APIC 側画面

f:id:akira6592:20191104110233p:plain:w400
EPG への Contract 紐付け設定画面
ある EPG

  • Provided Contract: c_sql、c_web
  • Consumed Contract: c_test

と設定されている状態を想定します。


■ Playbook

取得には、aci_epg_to_contract モジュールを利用します。state: query であっても contract_type のオプションの指定(provider / consumer)が必要です。そのため、タイプごとに取得することになります。(aci_rest モジュールを利用すればいっぺんに取得する方法ははこちら [Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する(aci_rest モジュール版) - てくなべ (tekunabe)

Provided Contracts 取得時には fvRsProv が、Consumed Contracts 取得時には fvRsCons が返ってくるので、用途に合わせてキーを変えます。

---
- hosts: apic
  gather_facts: no

  tasks:
    # provided contracts
    - name: get provided contracts
      aci_epg_to_contract:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        contract_type: provider  # provider / consumer の指定が必要
        output_level: debug
        state: query
      register: result_p

    - name: debug provided contracts
      debug:
        msg: "{{ item.fvRsProv.attributes.tnVzBrCPName }}"
      loop: "{{ result_p.current[0].fvAEPg.children | default([]) }}"
      loop_control:
        label: "{{ item.fvRsProv.attributes.tnVzBrCPName }}"

    # consumed contracts
    - name: get consumed contracts
      aci_epg_to_contract:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1
        ap: ap1
        epg: epg1
        contract_type: consumer # provider / consumer の指定が必要
        output_level: debug
        state: query
      register: result_c

    - name: debug consumed contracts
      debug: 
        msg: "{{ item.fvRsCons.attributes.tnVzBrCPName }}"
      loop: "{{ result_c.current[0].fvAEPg.children | default([]) }}"
      loop_control:
        label: "{{ item.fvRsCons.attributes.tnVzBrCPName }}"

Contract の紐付け設定がない場合への対応

表示には、debug モジュールを利用します。Contract の紐付け設定がない場合は fvAEPgchildren がない結果になります。その場合に備えて、loop: "{{ result_p.current[0].fvAEPg.children | default([]) }}" のように、default フィルターを利用しています。よって、Contract の紐付け設定がない場合は、debug のループをしません。 他には、when: result.current[0].fvAEPg.children is defined という条件をつける方法もあります。


■ 実行

Playbook を実行します。

$ ansible-playbook -i ../../inventory.ini get_contracts.yml 

PLAY [apic] ************************************************************************************

TASK [get provided contracts] ******************************************************************

ok: [apic01]

TASK [debug provided contracts] ****************************************************************
ok: [apic01] => (item=c_sql) => {
    "msg": "c_sql" 
}
ok: [apic01] => (item=c_web) => {
    "msg": "c_web"
}

TASK [get consumed contracts] ******************************************************************
ok: [apic01]

TASK [debug consumed contracts] ****************************************************************
ok: [apic01] => (item=c_test) => {
    "msg": "c_test"
}

PLAY RECAP *************************************************************************************
apic01                     : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

各 debug モジュールの取得で

  • Provided Contract: c_sql、c_web
  • Consumed Contract: c_test

となっていることを確認できました。

参考: query で取得したデータの全体

今回は、query した結果の中身の特定のキーを指定しています。参考のために query した結果の全体の中身も掲載します。 output_level: debug を指定した結果なので、filterurl なども含まれます。

取得した provided contracts のデータ

クリックして広げて見る

{
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "current": [
        {
            "fvAEPg": {
                "attributes": {
                    "annotation": "",
                    "childAction": "",
                    "configIssues": "",
                    "configSt": "applied",
                    "descr": "",
                    "dn": "uni/tn-tenant1/ap-ap1/epg-epg1",
                    "exceptionTag": "",
                    "extMngdBy": "",
                    "floodOnEncap": "disabled",
                    "fwdCtrl": "",
                    "hasMcastSource": "no",
                    "isAttrBasedEPg": "no",
                    "isSharedSrvMsiteEPg": "no",
                    "lcOwn": "local",
                    "matchT": "AtleastOne",
                    "modTs": "2019-11-03T14:54:44.955+00:00",
                    "monPolDn": "uni/tn-common/monepg-default",
                    "name": "epg1",
                    "nameAlias": "",
                    "pcEnfPref": "unenforced",
                    "pcTag": "32771",
                    "prefGrMemb": "exclude",
                    "prio": "unspecified",
                    "scope": "2555904",
                    "shutdown": "no",
                    "status": "",
                    "triggerSt": "triggerable",
                    "txId": "17293822569102705193",
                    "uid": "15374"
                },
                "children": [
                    {
                        "fvRsProv": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "matchT": "AtleastOne",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rsprov-c_sql",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_sql",
                                "tRn": "brc-c_sql",
                                "tType": "name",
                                "tnVzBrCPName": "c_sql",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_sql",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-03T15:28:38.764+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_sql]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    },
                    {
                        "fvRsProv": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "matchT": "AtleastOne",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rsprov-c_web",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_web",
                                "tRn": "brc-c_web",
                                "tType": "name",
                                "tnVzBrCPName": "c_web",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_web",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-03T15:16:07.253+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_web]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ],
    "failed": false,
    "filter_string": "?rsp-subtree-class=fvRsProv&rsp-subtree=full",
    "method": "GET",
    "proposed": {},
    "response": "OK (2348 bytes)",
    "sent": {},
    "status": 200,
    "url": "https://apic01/api/mo/uni/tn-tenant1/ap-ap1/epg-epg1.json",
    "warnings": [
        "Platform darwin on host apic01 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information."
    ]
}

取得した consumed contracts のデータ

クリックして広げて見る

{
    "changed": false,
    "current": [
        {
            "fvAEPg": {
                "attributes": {
                    "annotation": "",
                    "childAction": "",
                    "configIssues": "",
                    "configSt": "applied",
                    "descr": "",
                    "dn": "uni/tn-tenant1/ap-ap1/epg-epg1",
                    "exceptionTag": "",
                    "extMngdBy": "",
                    "floodOnEncap": "disabled",
                    "fwdCtrl": "",
                    "hasMcastSource": "no",
                    "isAttrBasedEPg": "no",
                    "isSharedSrvMsiteEPg": "no",
                    "lcOwn": "local",
                    "matchT": "AtleastOne",
                    "modTs": "2019-11-03T14:54:44.955+00:00",
                    "monPolDn": "uni/tn-common/monepg-default",
                    "name": "epg1",
                    "nameAlias": "",
                    "pcEnfPref": "unenforced",
                    "pcTag": "32771",
                    "prefGrMemb": "exclude",
                    "prio": "unspecified",
                    "scope": "2555904",
                    "shutdown": "no",
                    "status": "",
                    "triggerSt": "triggerable",
                    "txId": "17293822569102705193",
                    "uid": "15374"
                },
                "children": [
                    {
                        "fvRsCons": {
                            "attributes": {
                                "annotation": "",
                                "childAction": "",
                                "ctrctUpd": "ctrct",
                                "deplInfo": "",
                                "extMngdBy": "",
                                "forceResolve": "yes",
                                "lcOwn": "local",
                                "modTs": "2019-11-04T01:39:51.572+00:00",
                                "monPolDn": "uni/tn-common/monepg-default",
                                "prio": "unspecified",
                                "rType": "mo",
                                "rn": "rscons-c_test",
                                "state": "formed",
                                "stateQual": "none",
                                "status": "",
                                "tCl": "vzBrCP",
                                "tContextDn": "",
                                "tDn": "uni/tn-tenant1/brc-c_test",
                                "tRn": "brc-c_test",
                                "tType": "name",
                                "tnVzBrCPName": "c_test",
                                "triggerSt": "triggerable",
                                "uid": "15374",
                                "updateCollection": "no"
                            },
                            "children": [
                                {
                                    "fvCollectionCont": {
                                        "attributes": {
                                            "childAction": "deleteNonPresent",
                                            "collectionDn": "uni/tn-tenant1/brc-c_test",
                                            "lcOwn": "local",
                                            "modTs": "2019-11-04T01:39:51.572+00:00",
                                            "monPolDn": "uni/tn-common/monepg-default",
                                            "name": "",
                                            "nameAlias": "",
                                            "rn": "collectionDn-[uni/tn-tenant1/brc-c_test]",
                                            "status": ""
                                        }
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ],
    "failed": false,
    "filter_string": "?rsp-subtree-class=fvRsCons&rsp-subtree=full",
    "method": "GET",
    "proposed": {},
    "response": "OK (1514 bytes)",
    "sent": {},
    "status": 200,
    "url": "https://apic01/api/mo/uni/tn-tenant1/ap-ap1/epg-epg1.json"
}


■ まとめ

aci_epg_to_contract モジュール を利用して、EPG に紐付けれられている Contract の一覧を取得しました。 state: query を指定し、Provided Contract、Consumed Contract それぞれ取得しました。

参考

[Ansible][ACI] EPG に紐付けされている Contract 一覧を取得する(aci_rest モジュール版) - てくなべ (tekunabe)