Ansible Advent Calendar 2023の7日目の記事です。
やりたいこと
AWXで何か処理を実行した際、ログなどをテキストとして保存したいときがあります。(あります)
基本は、debugモジュールなどで実行結果に表示させればログは残せます、機器がたくさんあったり出力が長いと見にくかったりします。
もしテキストファイルなどを作成した場合も、デフォルトでは処理を実行したPodはJobTemplateが完了したタイミングで終了されるためテキストを残す場合にはAWXの細かい設定やPlaybook側で調整が必要だったりします
今回はPlaybook実行時に生成したテキストファイルをZIPファイルに圧縮し、
Base64でバイナリデータにしてJobTemplateのArtifactsに登録する、さらに手元の環境まで持ってくる方法を紹介します。
環境
AWX: 23.9
手順
- Zipをバイナリデータに変換するフィルタの作成
- Playbookの作成
- JobTemplateの作成
- JobTemplateの実行
- 実行結果からZipを生成,中身の確認
1. Zipをバイナリデータに変換するフィルタの作成
ZipバイナリデータにをBase64でASCII文字列として返すフィルタを作成します
ansibleにはb64encode_filterがありましたが、文字列をエンコードすることしかできないため自作しました
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/b64encode_filter.html
zipファイルをバイナリにエンコードしてから文字列としてデコードすることで、文字列になります。
↑の指摘いただいので修正
> zipファイルをバイナリにエンコードしてから文字列としてデコードすることで、文字列になります
— zaki (@zaki_hmkc) 2023年12月7日
ここがわかりにくかったんですが、「zipのバイナリデータをbase64エンコードするとASCII printable charactersな文字列になる」ってことですよね?(なのでバイナリもテキストのログにも残せる、と)
zipのバイナリデータをエンコードしてASCIIにするが表現として正しいです。
指摘ありがとうございます。
import base64 def base64_encode_zip(file_path): encoded = str() try: with open(file_path, 'rb') as f: bytes = f.read() encoded = base64.b64encode(bytes) return encoded.decode('utf-8') except FileNotFoundError: return encoded class FilterModule(object): def filters(self): return { 'base64_encode_zip': base64_encode_zip, }
こちらを呼び出せるように配置しansible.cfgで呼び出せるように設定をしておきます。
↑カスタムフィルタではなく、既存のフィルタを組み合わせてもできそうでした! 教えていただいてありがとうございます
この機能 AWX に標準でほしいですね……!(重いファイルをほいほい食べさせると DB の容量がアレなことになりそうですが)
— くろい (@kurokobo) 2023年12月7日
ところでモジュールを自製されたところ、{{ lookup('file', ..., rstrip=false) | b64encode }} でどうでしょう? pic.twitter.com/88K6iEeD9X
2. Playbookの作成
Playbookは、ローカルホストに対してpip freezeコマンドを実行、標準出力をテキストファイルに保存
テキストファイルをZIP化、base64でZIPバイナリを文字列に変換
set_statsでJobTemplateのArtifactsに保存させるものです
--- - hosts: localhost gather_facts: false connection: local tasks: - name: get pip freeze shell: pip freeze register: res_pip_freeze - name: create text copy: content: "{{ res_pip_freeze.stdout }}" dest: "{{ playbook_dir }}/test.txt" - name: archive archive: dest: "{{ playbook_dir }}/test.zip" format: zip path: - "{{ playbook_dir }}/test.txt" - name: base64 encord set_stats: data: zip_binary: "{{ (playbook_dir + '/test.zip') | base64_encode_zip }}"
archiveのformat: zipでzipファイルを作れます、pathに定義されたパスのファイルをzipに圧縮します
3. JobTemplateの作成
本題じゃないので割愛
4. JobTemplateの実行
JobTemplateを実行すると、artifactのzip_binaryに文字列が登録されていることが確認できます
出力にはdebugモジュールで出力させたpip freezeの中身も確認できます
5. 実行結果からZipを生成,中身の確認
AWXのREST APIをたたく簡単なスクリプトを作ります JobIDを指定してJobをGET、手元にZIPファイルを作成するスクリプトを作成しました
requests.getでJobを取得し、artifacts内のzip_binaryを文字列からバイナリにエンコードして、 バイナリファイルをZIPファイルとしてでコードしてます
""" AWXのJobにアクセス、artifactに格納された文字列をbinaryに変換後、zipとして保存する 保存したzipを展開する """ import base64 import shutil import requests AWX_URL = "http://<AWX_IP>/api/v2/" METHOD = "jobs/54" REQUEST_URL = AWX_URL+METHOD response=requests.get(REQUEST_URL, auth=requests.auth.HTTPBasicAuth(<USER>, <PASSWORD>)) json_response = response.json() str_binary = json_response['artifacts']['zip_binary'] zip_binary = str_binary.encode('utf-8') # NOTE: zipを格納 outfile_path = "output/artifact.zip" with open(outfile_path, "wb") as f: f.write(base64.b64decode(zip_binary)) # NOTE: zipを解凍 shutil.unpack_archive(outfile_path, 'output/')
スクリプトを実行すると、ZIPと、展開後のtest.txtがoutputファイルに格納されます
test.txtはAWXでローカルホストに向けて実行したpip freezeの結果が確認できます
Pythonの割合がちょっと多くなってしまいました