Ansible TowerのAPI経由からテンプレートに変数を渡して実行してみた

Ansible TowerのAPI経由からテンプレート実行で変数(extra_vars)を渡すやり方で少しハマったのでメモっておきます。

# 環境

| 項目          | バージョン |
|:--------------|:-----------|
| 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](http://docs.ansible.com/ansible-tower/3.1.4/html/towerapi/index.html)

# 検証ストーリー

* Playbookはローカルの `/var/lib/aws/projects` に直接作成したものを使います
* REST APIから変数をセットした情報を渡してテンプレートを実行します
* APIはpythonスクリプトから実行します

# Ansible Tower設定

## プロジェクト設定

![スクリーンショット 2018-03-20 22.08.40.png](https://qiita-image-store.s3.amazonaws.com/0/180118/56963973-87c1-25d6-8bd6-20344bba6cd4.png)

## テンプレート設定

注意点として `追加変数` の `起動プロンプト` にチェックを入れてください。  
チェックが入っていない場合は、変数をAPI経由で渡しても無視されます。(デフォルトは無視になっています)

![スクリーンショット 2018-03-20 22.09.39.png](https://qiita-image-store.s3.amazonaws.com/0/180118/bc97a4f1-9a11-ab1a-470c-8acaa423c8a7.png)

# テスト用Playbook

テスト用Playbookは以下の２つを使ってみます。

## msgで変数値を出力

```yaml:test.yml
---
- name: TEST Playbook
  hosts: localhost
  tasks:
    - debug: msg="{{ MSG }}"
```

## ループで変数値を出力

```yaml:test.yml
---
- name: TEST Playbook
  hosts: localhost
  tasks:
    - debug:
        msg: "{{ item }}"
      with_items: "{{ str_list }}"
```

# APIを実行するツール

## ソース

検証ツールとして以下のものを使用します。

```python:ansible-tower-api-tool.py
#!/usr/bin/env python3
from getpass import getpass
import argparse
import sys
import json
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

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)
    print(r.status_code)
```

## 使い方

```
$ ./ansible-tower-api-tool.py --host AnsibleTowerのIP -t テンプレート名 -d key,valueをセットしたdict
```

# 検証

## MSGの変数に値を入れてみる

`MSG` に `TEST Message` を入れてAPIを実行してみます。

```
$ ./ansible-tower-api-tool.py --host 192.168.0.234 -t TEST -d '{"extra_vars": {"MSG":"TEST Message"}}'
Ansible Tower Password:
201
```

![スクリーンショット 2018-03-20 22.30.13.png](https://qiita-image-store.s3.amazonaws.com/0/180118/d0d6538f-7f49-a694-9dfd-5d2a41401230.png)

## ループで実行してみる

`str_list` に配列で `Mes1` `Mes2` `Mes3` を入れてAPIを実行してみます。

```
$ ./ansible-tower-api-tool.py --host 192.168.0.234 -t TEST -d '{"extra_vars": {"str_list":["Mes1","Mes2","Mes3"]}}'
Ansible Tower Password:
201
```

![スクリーンショット 2018-03-20 22.34.52.png](https://qiita-image-store.s3.amazonaws.com/0/180118/725e8426-ee27-9adb-0630-9dd1c3485355.png)

# まとめ

Ansible TowerのAPI経由から変数を渡す場合は `追加変数` の `起動プロンプト` にチェックが入っていることが必要です。  
これは、API経由(以下URL)からでも確認が可能です。

| URL                                                                  |
|:---------------------------------------------------------------------|
| `https://$ansible_tower_ip/api/v1/job_templates/$TemplateID/launch/` |

以下が、GETした時の結果例です。  
`ask_variables_on_launch` が `true` になっている必要性があります。

```json
{
  "can_start_without_user_input": false,
  "passwords_needed_to_start": [],
  "ask_variables_on_launch": true,
  "ask_tags_on_launch": false,
  "ask_diff_mode_on_launch": false,
  "ask_skip_tags_on_launch": false,
  "ask_job_type_on_launch": false,
  "ask_limit_on_launch": false,
  "ask_verbosity_on_launch": false,
  "ask_inventory_on_launch": false,
  "ask_credential_on_launch": false,
  "survey_enabled": false,
  "variables_needed_to_start": [],
  "credential_needed_to_start": false,
  "inventory_needed_to_start": false,
  "job_template_data": {
    "id": 13,
    "description": "",
    "name": "TEST"
  },
  "defaults": {
    "credential": {
      "id": 2,
      "name": "root"
    },
    "job_tags": "",
    "extra_vars": "",
    "verbosity": 0,
    "job_type": "run",
    "diff_mode": false,
    "skip_tags": "",
    "limit": "",
    "inventory": {
      "id": 2,
      "name": "TEST Host List"
    },
    "vault_credential": {
      "id": null,
      "name": null
    }
  }
}
```