Ansible Towerで実行したテンプレートのジョブステータスをAPIで取得した時のメモです。
環境
項目 | バージョン |
---|---|
OS | RHEL7.4 |
Ansible Tower | 3.2.3 |
python | 3.4 |
Ansible Tower APIドキュメント
http://docs.ansible.com/ansible-tower/3.1.4/html/towerapi/index.html
ジョブ情報取得URL
テンプレートを実行した時に生成されるジョブIDを元に、そのジョブの情報が以下URLから取得できます。
URL |
---|
/api/v1/ジョブID |
ジョブIDはテンプレート実行した時のレスポンスBodyに含まれています。
以下がレスポンスの例です。
レスポンスの url
を元に実行したジョブのステータス情報が取得できます。
{
"ignored_fields": {},
"id": 142,
"type": "job",
"url": "/api/v1/jobs/142/",
(snip)
}
検証
検証Playbook
---
- name: TEST Playbook
hosts: localhost
tasks:
- wait_for: timeout=5
- debug: msg="Hello World"
検証テンプレート
検証ツール
ansible-tower-api-tool.py
#!/usr/bin/env python3
from getpass import getpass
import argparse
import sys
import json
import re
import time
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class colors:
GREEN = '\033[32m'
RED = '\033[91m'
END = '\033[0m'
def options():
parser = argparse.ArgumentParser(prog="ansible-tower-api-tool.py",
add_help=True,
description="Ansible Towerのテンプレートを実行するツール")
parser.add_argument("--host",
type=str, required=True,
help="Ansible TowerサーバのIPまたはホスト名")
parser.add_argument("--username", "-u",
type=str,default="admin",
help="Ansible Towerアカウント(default:admin)")
parser.add_argument("--password", "-p",
type=str,
help="Ansible Towerアカウントのパスワード")
parser.add_argument("--template", "-t",
type=str, required=True,
help="実行するテンプレート名")
parser.add_argument("--data", "-d",
type=eval,
help="テンプレートのextra_vars")
args = parser.parse_args()
if(not(args.password)):
args.password = getpass(prompt="Ansible Tower Password: ")
return args
def get_header():
headers = { "Content-Type": "application/json" }
return headers
def get_cred(username, password):
cred = {
'username': username,
'password': password
}
return cred
def create_url(host, path):
url = "https://%s%s" % (host, path)
return url
def get_token(host, cred):
url = create_url(host, "/api/v2/authtoken/")
r = post(url, get_header(), cred)
if(r.status_code == 200):
token = json.loads(r.text)['token']
return token
else:
print('Token acquisition error.')
sys.exit(1)
def get(url, headers):
r = requests.get(url,
headers=headers,
verify=False)
return r
def post(url, headers, data):
r = requests.post(url,
headers=headers,
data=json.dumps(data),
verify=False)
return r
if __name__ == '__main__':
# オプションを取得
args = options()
# 認証Tokenを取得
cred = get_cred(args.username, args.password)
token = get_token(args.host, cred)
# ヘッダー情報を生成
headers = get_header()
headers["Authorization"] = "Token " + token
# テンプレードID取得
url = create_url(args.host, "/api/v1/job_templates")
r = get(url, headers)
for i in json.loads(r.text)['results']:
if(i["name"] == args.template):
template_launch_url = i["related"]["launch"]
break
if(not("template_launch_url" in locals())):
print("%s template not found" % args.template)
sys.exit(1)
# テンプレートを実行
url = create_url(args.host, template_launch_url)
r = post(url, headers, args.data)
# ジョブIDとURLを取得
job_id = json.loads(r.text)["job"]
job_url = json.loads(r.text)["url"]
# ジョブステータス確認
Loop = True
url = create_url(args.host, job_url)
while Loop:
r = get(url, headers)
r = json.loads(r.text)
if(r["id"] == job_id and re.match(r"new|pending|running", r["status"])):
msg = "jobID: %s" % r["id"]
sys.stdout.write(msg.ljust(20) + "[%s]" % r["status"] + "\r")
elif(r["id"] == job_id and re.match(r"successful|failed|error|canceled", r["status"])):
msg = "jobID: %s" % r["id"]
if(r["status"] == "successful"):
for i in range(1,30): sys.stdout.write(" ")
sys.stdout.write("\r" + msg.ljust(20) + "[" + colors.GREEN + r["status"] + colors.END + "]" + "\n")
else:
for i in range(1,30): sys.stdout.write(" ")
sys.stdout.write("\r" + msg.ljust(20) + "[" + colors.RED + r["status"] + colors.END + "]" + "\n")
Loop = False
time.sleep(1)
テンプレートを実行した後にレスポンスから job_url
を取得してステータスが終了するまでループします。
実行例
こんな感じでステータスが確認できます。
最後に
ジョブ情報の中には result_stdout
に実行結果が入るので以下のコードを上記ツールの最後に入れて実行すれば
(snip)
# ジョブ実行結果出力
r = get(url, headers)
print(json.loads(r.text)["result_stdout"])
こんな感じで結果も見える。
[root@localhost ~]# ./ansible-tower-api-tool.py --host 192.168.0.234 -t TEST -d '{}'
Ansible Tower Password:
jobID: 149 [successful]
SSH password:
PLAY [TEST Playbook] ***********************************************************
TASK [Gathering Facts] *********************************************************
ok: [127.0.0.1]
TASK [wait_for] ****************************************************************
ok: [127.0.0.1]
TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
"msg": "Hello World"
}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=3 changed=0 unreachable=0 failed=0
追記
replace
で文字の \n
を改行に変更してあげればエラーが改行されて見やすくなる。
# ジョブ実行結果出力
r = get(url, headers)
print(json.loads(r.text)["result_stdout"].replace("\\n","\n"))
結果
[root@localhost ~]# ./ansible-tower-api-tool.py --host 192.168.0.234 -t TEST -d '{}'
Ansible Tower Password:
jobID: 151 [failed]
SSH password:
PLAY [TEST Playbook] ***********************************************************
TASK [Gathering Facts] *********************************************************
ok: [127.0.0.1]
TASK [debug] *******************************************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'MSG' is undefined
The error appears to have been in '/var/lib/awx/projects/test/test.yml': line 7, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
#- debug: msg=\"Hello World\"
- debug: msg=\"{{ MSG }}\"
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- \"{{ foo }}\"
exception type: <class 'ansible.errors.AnsibleUndefinedVariable'>
exception: 'MSG' is undefined"}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=1