てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] インストールされているコレクションから requirements.yml を生成する(Playbook編)

はじめに

pip でいう pip freezerequirements.txt のフォーマットを出力できるのと同じように、Ansible のコレクションでもコレクションの一覧を requirementst.yml のフォーマットで出力できれば便利かもしれないなと思いました。

ansible-galaxy collection コマンド自体にはそのような機能はなさそうだったでした(もしあったら教えてください!)。ちょっとしたスクリプトを書けばできそうですが、今回はPlaybook でやってみることにしました。

  • 環境
    • ansible-core 2.14.1
      • ansible-galaxy collection list--format オプションが使える必要あり

Playbook

ansible-galaxy collection list--format オプションで JSON を指定して、その結果を Jinja2 テンプレートにあててファイルを生成します。コレクションの保存先パスが複数あることと想定して、保存先パスごとにディレクトリを用意して、その中に requirements.yml を生成します。

また、Ansible がインストールされているコントロールノード自身を対象とするため、hosts: localhost を指定しています。

---
- name: Generate requirements.yml
  hosts: localhost
  gather_facts: false
  connection: local

  vars:
    ansible_python_interpreter: "{{ ansible_playbook_python }}"

  tasks:
    # コレクションのリストを JSON で出力
    - name: Execute ansible-galaxy collection list 
      ansible.builtin.command:
        cmd: ansible-galaxy collection list --format json
      changed_when: false
      register: collection_list

    # コレクションの保存ディレクトリに基づいて requiirements.yml 保存先ディレクトリを作成
    - name: Create directories
      ansible.builtin.file:
        path: "{{ item.key | replace('/', '_') }}"
        state: directory
      loop: "{{ collection_list.stdout | from_json | dict2items }}"
      loop_control:
        label: "{{ item.key }}"

    # 各ディレクトリに requiirements.yml を出力
    - name: Output requirements.yml
      ansible.builtin.copy:
        content: |-
          ---
          collections:
          {% for c in (item.value | dict2items) %}
            - name: {{ c.key }}
              version: {{ c.value.version }}
          {% endfor %}
        dest: "{{ item.key | replace('/', '_') }}/requirements.yml"
      loop: "{{ collection_list.stdout | from_json | dict2items }}"
      loop_control:
        label: "{{ item.key }}"

なんとなく、別の Jinja2 テンプレートファイルを作りたくなかったため、ansible.builtin.template ではなく ansible.builtin.copy モジュールの content オプションを利用しました。

実行結果

% ansible-playbook -i localhost, list.yml

PLAY [Generate requirements.yml] ******************************************************************************

TASK [Execute ansible-galaxy collection list] *****************************************************************
ok: [localhost]

TASK [Create directories] *************************************************************************************
changed: [localhost] => (item=/Users/sakana/envs/a7/lib/python3.9/site-packages/ansible_collections)
changed: [localhost] => (item=/Users/sakana/.ansible/collections/ansible_collections)

TASK [Output requirements.yml] ********************************************************************************
changed: [localhost] => (item=/Users/sakana/envs/a7/lib/python3.9/site-packages/ansible_collections)
changed: [localhost] => (item=/Users/sakana/.ansible/collections/ansible_collections)

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

今回、2つのディレクトリ保存パスが存在するため、以下の2つの requirements.yml が生成されました。

  • _Users_sakana_.ansible_collections_ansible_collections/requirements.yml
  • _Users_sakana_envs_a7_lib_python3.9_site-packages_ansible_collections/requirements.yml

ディレクトリが深くなることを避けるため、 /_ に変換したため若干不格好ですが一応元ネタが識別できます。

代表して一つ、_Users_sakana_.ansible_collections_ansible_collections/requirements.yml の中身を確認します。

---
collections:
  - name: arista.eos
    version: 6.0.0
  - name: amazon.aws
    version: 5.3.0
  - name: azure.azcollection
    version: 1.15.0
  - name: cisco.ios
    version: 4.0.0
  - name: ansible.netcommon
    version: 4.1.0
  - name: ansible.utils
    version: 2.8.0

これで、使えそうなファイルになりました。