git repo にホストした自作 Ansible Collection を使う

サーバーの構築で共通する設定作業などを、 Ansible Collection で再利用できるようにしておくと便利ですね。
詳しい情報が[公式ドキュメント](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html)に載っていますので、これを見ながら実際に試してみた（そして躓いた）手順を記録しておきます。

## collection を作る

`ansible-galaxy collection init` のコマンドで空 (skelton) の collection を作ることができます。
ここでは、 `thisis` という namespace に `example` という名前のロールを作ってみます。

```zsh
% ansible-galaxy collection init thisis.example
- Collection thisis.example was created successfully
% find .
.
./thisis
./thisis/example
./thisis/example/plugins
./thisis/example/plugins/README.md
./thisis/example/roles
./thisis/example/docs
./thisis/example/README.md
./thisis/example/galaxy.yml
```

ぱっと見で分かる通り、 collection を作るには正しいディレクトリ構成と `galaxy.yml` があればOKです。
この `galaxy.yml` のファイルに collection のメタ情報を記載します。
`ansible-galaxy init collection` の実行直後では、以下の様なデフォルト値が設定されています。

```zsh
% grep '^[^#]' thisis/example/galaxy.yml
namespace: thisis
name: example
version: 1.0.0
readme: README.md
authors:
- your name <example@domain.com>
description: your collection description
license:
- GPL-2.0-or-later
license_file: ''
tags: []
dependencies: {}
repository: http://example.com/repository
documentation: http://docs.example.com
homepage: http://example.com
issues: http://example.com/issue/tracker
build_ignore: []
```

上記で必須の設定は `authors` より上だけなので、必要な情報を集めて適宜編集します。
`galaxy.yml` が準備できたら、共有したい作業などを `roles/` 以下に用意しましょう。

```zsh
% mkdir -p thisis/example/roles/aaa/tasks/
% echo <<EOL > thisis/example/roles/aaa/tasks/main.yml
heredoc> ---
heredoc> - name: aaa
heredoc>   debug:
heredoc>     msg: 問題なし！OK！
heredoc>
heredoc> EOL
```

ここまでできたら、あとは `exapmle` のディレクトリを適当な git repo として push すれば作成完了です。

## collection を使ってみる

collection を使いたい playbook の `requirements.yml` で、使用したい collection （==先ほど作成したもの）を指定します。
git repo へのアクセスに ssh 認証が必要でしたら、以下の様に `git+ssh://git@~` の形式で `name` を指定します。

```zsh
% cat requirements.yml
collections:
  - name: git+ssh://git@your-git-host.com/your-org/ansible-collection-example.git
    type: git
    version: main
```

上記を用意して `ansible-galaxy install -r requirements.yml` を実行すると、ホストの `ANSIBLE_COLLECTIONS_PATHS` （デフォルトだと `~/.ansible/collections/` 配下）に role がインストールされます。

```zsh
% ansible-galaxy install -r requirements.yml
Starting galaxy collection install process
Process install dependency map
Cloning into '/Users/whoami/.ansible/tmp/(略)/ansible-collection-examplexxxxxx'...
remote: Enumerating objects: 24, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 24 (delta 1), reused 0 (delta 0), pack-reused 13
Receiving objects: 100% (24/24), done.
Resolving deltas: 100% (1/1), done.
Already on 'main'
Your branch is up to date with 'origin/main'.
Starting collection install process
Installing 'thisis.example:1.0.0' to '/Users/whoami/.ansible/collections/ansible_collections/thisis/example'
Created collection for thisis.example:1.0.0 at /Users/whoami/.ansible/collections/ansible_collections/thisis/example
thisis.example:1.0.0 was installed successfully
```

ここまでできたら、 playbook でロールを指定して実行することができます。

```zsh
% find .
.
./inventory
./playbook.yml
./requirements.yml
% cat playbook.yml
- hosts: all

  roles:
    - thisis.example.aaa

% ansible-playbook -i inventory playbook.yml

PLAY [all] **********************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [targethost]

TASK [thisis.example.aaa : aaa] *************************************************************************************
ok: [targethost] => {
    "msg": "問題なし！OK！"
}

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

## 余談: 躓いたこと

ここまでに書いてきた通り、 collection を作るには `galaxy.yml` と共有したいロールの実体、それらを内包するディレクトリ構成が必要です。
やろうと思えば、自分でディレクトリを用意して、 `galaxy.yml` に必須パラメータ `namespace` `name` `version` `readme` `authors` だけを記載することで collection を作ることもできます。（実際、自分も最初はそれで試しました。）

ここで注意が必要なのは、 `namespace` と `name` には使用できる文字が制限されている、ということです。
使用できる文字は `[a-zA-Z0-9_]` のいずれかであり、 `-` などは使用できません。（自動生成された `galaxy.yml` のコメントにもその旨が記載されています）

万が一 `namespace` や `name` に `-` が含まれていると、 `ansible-galaxy install -r requirements.yml` でのインストールこそ成功しますが、 playbook の実行時にエラーになってしまいます。

(↓ インストールは成功するが)

```zsh
% ansible-galaxy install -r requirements.yml
Starting galaxy collection install process
Process install dependency map
Cloning into '/Users/whoami/.ansible/tmp/(略)/ansible-collection-examplexxxxxx'...
remote: Enumerating objects: 31, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 31 (delta 2), reused 0 (delta 0), pack-reused 13
Receiving objects: 100% (31/31), 4.40 KiB | 1.46 MiB/s, done.
Resolving deltas: 100% (2/2), done.
Already on 'main'
Your branch is up to date with 'origin/main'.
Starting collection install process
Installing 'thisis.ex-am-ple:1.0.0' to '/Users/whoami/.ansible/collections/ansible_collections/thisis/ex-am-ple'
Created collection for thisis.ex-am-ple:1.0.0 at /Users/whoami/.ansible/collections/ansible_collections/thisis/ex-am-ple
thisis.ex-am-ple:1.0.0 was installed successfully
```
(↓ 実行時にエラー(ロールが見つからない旨）になる様子）

```zsh
% cat playbook.yml
- hosts: all

  roles:
    - thisis.ex-am-ple.aaa

% ansible-playbook -i inventory playbook.yml
ERROR! the role 'thisis.ex-am-ple.aaa' was not found in /Users/whoami/tmp/roles:/Users/whoami/.ansible/roles:/Users/whoami/tmp

The error appears to be in '/Users/whoami/tmp/playbook.yml': line 4, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  roles:
    - thisis.ex-am-ple.aaa
      ^ here
```

上記の「ロールが見つからない」というエラーメッセージをそのまま受け取ってしまうと、色々と調査した結果、時間を無駄に浪費してしまうことになります（経験済）。

この命名の制限についてはちゃんと Ansible のドキュメント（または以前から出ている警告メッセージ）を読んでいれば認識できますし、あるいは一番最初に `ansible-galaxy collection init` で skelton を作成すれば、コマンド自体がエラーになるので気付くことができます。

```zsh
% ansible-galaxy collection init thisis.ex-am-ple
ERROR! Invalid collection name 'thisis.ex-am-ple', name must be in the format <namespace>.<collection>.
Please make sure namespace and collection name contains characters from [a-zA-Z0-9_] only.
```

というわけで、公式の情報をちゃんと読むことは大事ですね。皆さんもお気をつけ下さい。