うさラボ

お勉強と備忘録

AWXで作成したテキストファイルをZipに固めて手元に持ってきたい

Ansible Advent Calendar 2023の7日目の記事です。

qiita.com

やりたいこと

AWXで何か処理を実行した際、ログなどをテキストとして保存したいときがあります。(あります)
基本は、debugモジュールなどで実行結果に表示させればログは残せます、機器がたくさんあったり出力が長いと見にくかったりします。

もしテキストファイルなどを作成した場合も、デフォルトでは処理を実行したPodはJobTemplateが完了したタイミングで終了されるためテキストを残す場合にはAWXの細かい設定やPlaybook側で調整が必要だったりします

今回はPlaybook実行時に生成したテキストファイルをZIPファイルに圧縮し、
Base64でバイナリデータにしてJobTemplateのArtifactsに登録する、さらに手元の環境まで持ってくる方法を紹介します。

環境

AWX: 23.9

手順

  1. Zipをバイナリデータに変換するフィルタの作成
  2. Playbookの作成
  3. JobTemplateの作成
  4. JobTemplateの実行
  5. 実行結果からZipを生成,中身の確認

1. Zipをバイナリデータに変換するフィルタの作成

ZipバイナリデータにをBase64でASCII文字列として返すフィルタを作成します
ansibleにはb64encode_filterがありましたが、文字列をエンコードすることしかできないため自作しました
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/b64encode_filter.html

zipファイルをバイナリにエンコードしてから文字列としてデコードすることで、文字列になります。

↑の指摘いただいので修正

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で呼び出せるように設定をしておきます。

↑カスタムフィルタではなく、既存のフィルタを組み合わせてもできそうでした! 教えていただいてありがとうございます

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の割合がちょっと多くなってしまいました