この記事はAnsible - Qiita Advent Calendar 2024 - Qiita 12月7日(土) の記事です。


AWX / Ansible Tower 操作用の PowerShell モジュールを開発中だよという紹介です。

AWX / Ansible Tower とは

正式名称は "Automation Controller" に替わっているようですが、Ansible を (Web) GUI ツールから操作・管理するサーバーソフトウェアです。 Ansible Tower は RedHat 社による有償提供に対して、AWX は無償の OSS 版になります。

  • プロジェクト管理
    • playbook や inventory file 管理
    • git や subversion 等の SCM リポジトリから clone/checkout など
  • ジョブ管理
    • playbook 実行のジョブ・テンプレート設定
    • それらをフローとしてまとめたワークフロー・ジョブ・テンプレート設定
    • 実行ログ表示
  • スケジュール管理
  • インベントリー管理
    • ホスト/グループ設定
    • 変数設定
  • ユーザーやチーム管理
    • ユーザー作成
    • チーム作成
    • 権限設定
  • 認証管理
    • リモート接続のユーザー/パスワード/鍵
    • SCM 接続のユーザー/パスワード/鍵
    • Ansible vault

等々が行えます。

Jagabata.psm

AWX / Ansible Tower の操作は主に Web ブラウザから行いますが、コマンドラインで操作したいこともあります。

Jagabata.psm logo

そのコマンドライン操作を行うのが、今回紹介する Jagabata.psm です。

  • 各種情報取得(Get-*Find-*)コマンド
  • ジョブ実行(Invoke-*, Start-*)、完了待機(Wait-*)、停止(Stop-*)コマンド
  • 各種オブジェクト作成(New-*)、更新(Update-*)、登録(Register-*, Unregister-*)、削除(Remove-*)コマンド

等々、総勢 200 近いコマンド を備えていています。 自分が作ったものの中では過去一番の努力作となります。

やっていることは、基本的には AWX / Ansible Tower 自体が備えている Rest API にクエリを投げて結果を出力しているだけですので、 原始的には curljq でも可能ですし、tower-cli といったツールもあります。

しかし、もう少し簡単に、そして対話的に操作したい。 そんな欲求から PowerShell での実装に取り組んでいます。 PowerShell のオブジェクトを扱える特性を生かせば、特定のフィールドの表示や条件に合致したオブジェクトの抽出、パイプラインで別コマンドへオブジェクトを渡すといったことが容易に行えます。 ただ JSON 情報を出力するだけだと情報量が多すぎて目がすべってしまいます。 必要な情報をピンポイントで得られるよう好みに合わせた取捨選択ができるのが PowerShell の強みではないかと思っています。

例えば、/api/v2/jobs/ から得られる実行ジョブのリストですが、一つのジョブ情報に50以上ものプロパティがあります。 それらを10個や20個も見るなどやっていられません。 Jagabata.psm では、こういったリストを(標準では)項目数を絞ったテーブル形式(Format-Table)で表示するようにしています。 もちろんリスト形式(Format-List)で事細かに出力もできますし、出力させたい項目を個別に指定することもできます。

標準のテーブル形式:
List of jobs in table format

リスト形式を指定して出力:
List of jobs in list format

制限事項

PowerShell モジュールとして作っていますが、残念ながら Windows 標準で入っている PowerShell Desktop 5.1 では動きません。 GNU/Linux, macOS でも動くクロスプラットフォームを目指していることもあって PowerShell Core (version 7.x) 向けです。

僕自身が C# で書きたかったこと、書くなら比較的新しい .NET (version 8)を使いたかったこともあります。

あと、Windows Terminal 等の VT100 互換のターミナルで使用することを推奨です。 (おそらく最近のならコマンドプロンプトでも問題なく動くと思いますが……)

それから、Docker 上に構築した AWX 23.3.1 でテスト実行しています。 最新版や古いバージョンで正しく動くか、特に API から返ってくる JSON の形式が変わっていたりして JSON のデシリアライズに失敗するかも知れません。 そういうのを見つけたら Issue 等で報告いただけると助かります。

デモ動画

ジョブ・テンプレートの実行をしているデモ動画です。

demo movie - 1

ここでは、以下のようなことをしています。

  1. ジョブテンプレートの取得/表示 (Find-AnsibleJobTemplate)
  2. ジョブテンプレートの実行(+ 完了するまでログ取得と出力)(Invoke-AnsibleJobTemplate)
  3. 実行されたジョブの取得(Find-AnsibleJob)
  4. ジョブのログを取得(Find-AnsibleJobLog)

話が逸れますが、デモ動画は vhs というコマンドで撮りました。 タイプする内容を書いたファイルを実行させて撮るので、撮影時のミスタイプ等を防ぐことができて良いです。 (Ubuntuだとインストールがちょっと面倒だったけど)

例: エラーとなったジョブの調査

実行されたジョブ一覧からエラーとなっているものを発見し、状況に応じて詳細情報を得て深堀していく例を紹介してみます。

  1. ある期間内に実行されたジョブ一覧を取得
  2. 失敗しているジョブのみ表示
  3. 失敗したジョブのログを出力
  4. ジョブイベントからエラー箇所の詳細表示

1. ある期間内に実行されたジョブ一覧を取得

8/19~8/30 のジョブを取得し、jobs 変数に格納します(-ov(-OutVariableのエイリアス)を使用)

Find-AnsibleJob -Filter finished__gt=2024-08-19T00:00+09:00, finished__lt=2024-08-30T00:00+09:00 -ov jobs
 Id Type Name   JobType LaunchType     Status Finished            Elapsed LaunchedBy     Template
 -- ---- ----   ------- ----------     ------ --------            ------- ----------     --------
425  Job Test4      Run   Workflow     Failed 2024/08/28 18:21:49    1.95 [user][1]admin [28]Test4
423  Job Test_1     Run   Workflow Successful 2024/08/28 18:16:05   1.771 [user][1]admin [9]Test_1
419  Job Test_1     Run     Manual Successful 2024/08/26 18:39:14   2.052 [user][1]admin [9]Test_1
415  Job Test_1     Run   Workflow Successful 2024/08/19 18:20:06   1.535 [user][1]admin [9]Test_1
414  Job Test_1     Run   Workflow Successful 2024/08/19 18:20:07   2.558 [user][1]admin [9]Test_1

2. 失敗しているジョブのみ表示

デフォルトでは一覧性のためにテーブル形式で一部の情報しか表示されません。 失敗しているジョブを抜き出して、リスト形式で出してみます。

$jobs | ? Status -eq Failed | Format-List
Id                     : 425
Type                   : Job
Created                : 2024/08/28 18:21:47
Modified               : 2024/08/28 18:21:47
Name                   : Test4
Description            :
LaunchType             : Workflow
Status                 : Failed
ExecutionEnvironment   : 1
Failed                 : True
Started                : 2024/08/28 18:21:47
Finished               : 2024/08/28 18:21:49
CanceledOn             :
Elapsed                : 1.95
JobExplanation         :
ExecutionNode          : awx_1
ControllerNode         : awx_1
LaunchedBy             : [user][1]admin
WorkUnitId             : plsPcgxp
JobType                : Run
Organization           : 1
OrganizationName       : Default
Project                : 6
ProjectName            : Demo Project
Inventory              : 2
InventoryName          : TestInventory
InstanceGroup          : 2
JobTemplate            : 28
SourceWorkflowJob      : { Id = 424, Type = WorkflowJob, Name = TestFlow3, Description = NodePromptsRejected, Status = Failed, Failed = True, Elapsed = 2.252 }
Labels                 :
Playbook               : hello_world.yml
ScmBranch              :
ScmRevision            : 347e44fea036c94d5f60e544de006453ee5c71ad
Forks                  : 0
Limit                  :
Verbosity              : Normal
ExtraVars              : {"message": "hello"}
JobTags                :
SkipTags               :
ForceHandlers          : False
StartAtTask            :
Timeout                : 0
UseFactCache           : False
PasswordsNeededToStart : {}
AllowSimultaneous      : False
Artifacts              : {}
DiffMode               : False
JobSliceNumber         : 0
JobSliceCount          : 1
WebhookService         :
WebhookCredential      :
WebhookGuid            :

こういったインタラクティブに加工できるのが、PowerShell の魅力の一つだと思っています。

3. 失敗しているジョブのログを見る

ジョブのログを取得して見てみます。

$jobs | ? Status -eq Failed | Get-AnsibleJobLog
==> [425] Job

PLAY [Hello World Sample] ******************************************************

TASK [Gathering Facts] *********************************************************
fatal: [gitrepo]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Warning: Permanently added '172.18.0.5' (ED25519) to the list of known hosts.\\r\\nno such identity: id_ed25519_ansible: No such file or directory\\r\\nansible@172.18.0.5: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).", "unreachable": true}
ok: [localhost]

TASK [Hello Message] ***********************************************************
ok: [localhost] => {
    "msg": "Hello World!"
}

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

※: VT100互換ターミナルを使用している場合は、ANSI escape sequences を用いた色が付きます

4. ジョブイベントからエラー箇所の詳細表示

この例だとログから最初のタスクで UNREACHABLE となっていることが分かるので詳細を得るまでもないですが、 ジョブ・イベントからエラーとなったタスクの詳細から、アクションやその引数、開始終了時間といったログからでは分からない情報を得ることができます。

PS1> $events = $jobs | ? Status -eq Failed | Find-AnsibleJobEvent -All
PS1> $events | ? Failed | ft Counter,Event,Hostname,Task

Counter               Event HostName Task
-------               ----- -------- ----
      3 PlaybookOnTaskStart          Gathering Facts
      6 RunnerOnUnreachable gitrepo  Gathering Facts
     11     PlaybookOnStats

PS1> $events.Where({$_.Counter -eq 6}).EventData

Key             Value
---             -----
playbook        hello_world.yml
playbook_uuid   08baa4a7-9045-44ec-a26d-1f12332885d3
play            Hello World Sample
play_uuid       f2718640-ddd5-4f1f-7096-000000000003
play_pattern    all
task            Gathering Facts
task_uuid       f2718640-ddd5-4f1f-7096-000000000009
task_action     gather_facts
resolved_action ansible.builtin.gather_facts
task_args
task_path       /runner/project/hello_world.yml:1
host            gitrepo
remote_addr     gitrepo
start           2024/08/28 18:21:48
end             2024/08/28 18:21:48
duration        0.072
res             {[unreachable, True], [msg, Failed to connect to the host via ssh: Warning: Permanently added '172.18.0.5' (ED25519) to the list of known hosts.…
uuid            88df05e5-f94b-411b-98a0-58c6db597ab6
guid            52d20c748a454ba9b62fab208f74daf4

インストールと初期設定

少しは魅力を伝えられたでしょうか…使ってみたいと思っていただけたら嬉しいです。

が、使うには少し以下2点の準備が必要です。

  1. モジュールのインストール
  2. 初期設定として、APIトークンの取得と設定

モジュールのインストール

PowerShell Gallary に登録してあるので、 以下のコマンドでインストールできます。

Install-Module -Name Jagabata.psm -Scope CurrentUser

ビルドからしたい場合は、Build を参照ください。 (基本的には dotnet build するだけ)

APIトークンの取得と設定

APIトークンの取得と設定 を参照ください。

大きくは以下の2点です。

  1. Web ブラウザでログインしてパーソナル・アクセス・トークンを取得
  2. New-AnsibleApiConfig コマンドで接続先 URI と取得したトークンを設定

demo movie - 2

内部的にやっていること

最初の方でも少し書きましたが、 AWX / AnsibleTower は RestAPI を備えています。

Web UI もこれらの API を用いて実装されていて、 ブラウザの開発者ツール等を用いて通信状況を覗くと随所で GETPOST をしているのが見えますので、通信内容と上記 API リファレンスを見比べてみると面白いかもしれません。

また、パーソナル・アクセス・トークン(PAT)を取得して使用することで、ログイン操作なしで各種情報にアクセスできます。(もちろんそのユーザーの権限が及ぶ範囲内で) (APIトークンの取得: 9.6. Users - Tokens)

Rest API から JSON 文字列を取得し、.Net の System.Text.Json.JsonSerializer で見合ったクラスへデシリアライズして出力しています。

なぜ "Jagabata" なのか

Angry Potato AWX を立ち上げてログイン画面へ行くと、なんだか良く分からないマスコット・キャラクター(?)が表示されますが、 これ、"The Angry Potato" と言うらしいです。

そこからポテト…じゃがいも…、と来て、最初の浮かんだのが「じゃがバタ」だったからです。

正直、大した意味は無い……

今後の展望

  • テストをきちんと書きたい……
  • ドキュメントの充実
    • 英語で書いているけど自信がない
    • 日本語ドキュメントも用意したい
  • コマンドのパラメーター補完の充実
    • 関連オブジェクトを ID 番号で渡すパラメーター部分など、一度取得したものはキャッシュしておいて補完できるようにしたい
  • 設定ファイルの作り先、HOMEディレクトリではなく、XDG_CONFIG_HOME にする方が良さそう

時間、時間が欲しい。