要約
Ansibleタスクのyamlファイルを読んでモジュール名をFQCN形式に変換して保存するスクリプトを紹介します。
解説
Ansible 2.10からタスクでモジュール名をFQCNで書く亊が推奨されるようになった。実際には後方互換のリダイレクタが有効なので、従来の記法でも問題なく動作する。
一件ずつ変更するのはとても面倒なので、ファイルを読み込んでモジュール名を抽出、リダイレクタの設定からFQCNに変換するPythonスクリプトを書いた。全然大したコードではないですが、どうせ書いたからお裾分けします。
機能
- 指定したディレクトリの指定したglobパターンのファイルを対象として処理する。
- ファイルをテキストとして読み込んで正規表現で置換するので、YAMLの文法チェックなどは行わない。
- 各タスクの
name
キーの次のキーをモジュール名と見做し、指定したリダイレクタの設定(ansible_builtin_runtime.yml
)で変換して書き換える。- 従って
name
キーの無いタスクは無視される。
- 従って
-
debug
・template
などリダイレクタの設定に無いものは変換しない。 - 変換の結果ファイル単位で差分があれば上書き保存する。
用意するもの
環境
- Python 3.7以上
- Ansible 2.10
情報
以下の三つの情報をスクリプト冒頭の定数に定義する。
- バージョンアップ先のAnsibleライブラリのansible_builtin_runtime.ymlのパス(例:
site-packages/ansible/config/ansible_builtin_runtime.yml
) - 対象となるAnsibleプロジェクトのディレクトリのパス
- そのディレクトリからのタスクYAMLを示すglobパターン(例:
**/tasks/*.yml
)
前述のように確認せずにファイルを書き換えるので、大事なコードを事前にコミットしてワークスペースを綺麗にしておく。
制限事項
- 本来はプラグインやフィルターもFQCNに変換するべきらしい(リダイレクタを無効にするとこれらも旧名では機能しない)が、あくまでもタスクのみが対象です。
- 一切の保証はありません。ころんでも泣かない。
参考
- [Ansible] モジュールの指定などを FQCN 表記に移行する手順 - てくなべ (tekunabe)
- Ansible 2.10 Porting Guide — Ansible Documentation
- Conversion to Collection - YAML roundtrip with ruamel | Linux System Roles
実行例
対象のタスクファイル
---
- hosts: win_host
gather_facts: yes
tasks:
- name: get terminal service behaviour on failure
win_command: sc qfailure TermService
register: qfailure
changed_when: no
- name: debug
debug: var=qfailure
実行
$ python convert2fqcn.py
processing old.yml
checking win_command...
converted to ansible.windows.win_command
checking debug...
not found.
結果
win_command
がansible.windows.win_command
に変換された。値の部分は変更されていない。またdebug
も変換されていない。
差分を表示する為に変換前後でファイル名を変えているが、実際には上書きするのでご注意下さい。
--- old.yml 2021-03-25 10:01:46.297632163 +0900
+++ new.yml 2021-03-25 09:55:20.492025609 +0900
@@ -7 +7 @@
- win_command: sc qfailure TermService
+ ansible.windows.win_command: sc qfailure TermService
スクリプト
convert2fqcn.py
from pathlib import Path
import re
import yaml
'''
python convert2fqcn.py
Ansibleタスクのモジュール名をFQCN形式に変換します。以下に指定したディレクトリ配下に見つかった
ファイルを直接書き換えます。
'''
# Ansibleモジュールのリダイレクト用config 'ansible_builtin_runtime.yml'へのパス
CONVERTION_MAP_PATH = './ansible-2.10-venv/lib/python3.6/site-packages/ansible/config/ansible_builtin_runtime.yml.back'
# 置換を行うパス
TARGET_DIR = 'roles/'
# 置換を行うタスクファイルのglobパターン。前項からの相対パスで表す。
TASKS_GLOB_PATTERN = '**/tasks/*.yml'
map_cache = {}
# nameキーで始まるオブジェクトにマッチ
name_line_regex = ' *- name:.*\n'
# 次の行のコロンまでをモジュール名と判定
module_line_regex = '([\S_]+):'
module_name_regex = f'({ name_line_regex } *){module_line_regex}'
def main():
role_path = Path(TARGET_DIR).rglob(TASKS_GLOB_PATTERN)
for path in role_path:
with open(path, 'r') as role_file:
print(f'processing {path}')
task_data = role_file.read()
new_task_data = replace(task_data)
if new_task_data != task_data:
with open(path, 'w') as role_file:
role_file.write(new_task_data)
def load_covertion_map(convertion_map_path):
runtime_data = yaml.load(open(convertion_map_path))
lis = runtime_data.get('plugin_routing').get('modules')
return lis
def replace(task_data):
r = re.compile(module_name_regex)
return re.sub(r, convert_task, task_data)
def convert_task(match_obj):
res = None
if match_obj.groups():
prev_lines, old_key = match_obj.groups()
print(f' checking {old_key}...')
entry = query(old_key)
else:
# マッチしなければ呼ばれないのでここには来ないはず。
return None
if entry:
new_key = entry.get('redirect')
print(f' converted to {new_key}')
key = new_key
else:
print(' not found.')
key = old_key
# キー区切りのコロンまでマッチしているので必ず付ける。
return prev_lines + key + ':'
def query(key):
cached = map_cache.get(key)
if cached:
return cached
entry = CONVERTION_MAP.get(key)
map_cache[key] = entry
return entry
if __name__ == '__main__':
CONVERTION_MAP = load_covertion_map(CONVERTION_MAP_PATH)
main()