てくなべ (tekunabe)

ansible / network / automation

[Ansible/AWX] カスタム venv でジョブ実行時 ansible-playbook コマンドが No such file or directory のエラーになる

はじめに

こちらの記事で紹介した方法で、ホスト側に カスタム venv を作って ansible をインストールし、awx_task コンテナにマウントさせてジョブテンプレートを実行したとろころ、以下のようなエラーが発生しました。

f:id:akira6592:20201025224951p:plain
エラー

  • 環境
    • AWX 15.0

エラー詳細

エラー全文は以下の通り。

Traceback (most recent call last): File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/awx/main/tasks.py", line 1501, in run res = ansible_runner.interface.run(**params) File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/interface.py", line 178, in run r.run() File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/ansible_runner/runner.py", line 179, in run use_poll=self.config.pexpect_use_poll, File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/pexpect/pty_spawn.py", line 204, in __init__ self._spawn(command, args, preexec_fn, dimensions) File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/pexpect/pty_spawn.py", line 303, in _spawn cwd=self.cwd, **kwargs) File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/pexpect/pty_spawn.py", line 314, in _spawnpty return ptyprocess.PtyProcess.spawn(args, **kwargs) File "/var/lib/awx/venv/awx/lib/python3.6/site-packages/ptyprocess/ptyprocess.py", line 330, in spawn raise exception OSError: [Errno 2] No such file or directory: b'/opt/my-envs/a210/bin/ansible-playbook'

/opt/my-envs/a210 が作成したvenvです。

調査

エラーによると、/opt/my-envs/a210/bin/ansible-playbook がないとのことですが、awx_task` コンテナの中に入って調べてみるとちゃんとある状態でした。

そこで、awx_task コンテナで /opt/my-envs/a210/bin/ansible-playbook --version を実行してみました。

bash-4.4# /opt/my-envs/a210/bin/ansible-playbook --version
bash: /opt/my-envs/a210/bin/ansible-playbook: /opt/my-envs/a210/bin/python3: bad interpreter: No such file or directory

どうやら、Python インタープリタ/opt/my-envs/a210/bin/python3 がないようです。

対処

詳細は分からないですが、venv を作るときのちょっとした手違いかなにかかと思い、再度ホスト側で venv を作成しなおしたら、エラーはなくなり正常に実行できました。

ansible-playbook コマンドがないというエラーのときは、python インタープリター の有無もあわせて調べてみると良いかも知れません。

[Ansible] 「つまずき Ansible 【Part20】VSCodeでいい感じにansible-lintしたい」ふりかえり

はじめに

2020/10/24 に、YouTube Live で「[Ansible] 「つまずき Ansible 【Part20】VSCodeでいい感じにansible-lintしたい」ふりかえり」という配信をしました。

実際に作業しながら(ときには)エラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、今回は、Playook の書きっぷりをチェックするツールである ansible-lint を Visual Studio Code からいい感じに呼び出す方法をました。

f:id:akira6592:20201024212617p:plain
最終イメージ(ansible-lintとyamllint同時実行)

やったことをふりかえります。

  • 環境
    • ansible 2.9.14
    • ansible-lint 4.3.5

動画

www.youtube.com


■ やったこと

ansible-lint のインストール

ansible-lint は、Playbook の書き方がルールに沿っているかチェックするツールです。

Ansible Lint Documentation — Ansible Lint Documentation

デフォルトのルールとして、たとえば、行の末尾に余計なスペースがないか、タスクに name がついているかなどがあります。 デフォルトのルールは以下のページに記載されています。

Default Rules — Ansible Lint Documentation

インストールは pip でできます。

pip install ansible-lint

ansible-lint をコマンドで手動実行

まずは、以下のような Playbook を対象にします。(おそらく配信時と微妙にことなります)

- hosts: all
  gather_facts: no
  
  vars:
    msg: greeting

  tasks:
    - debug:
        msg: "{{msg}}"

    - name: Create a directory if it does not exist
      file:
        path: /etc/some_directory
        state: directory
        mode: 755

    - name: Send summary mail
      local_action:
        module: debug
        msg: hello

ansible-lint ファイル名 でチェックします。

(a2914) [root@centos7 stumble2]# ansible-lint test01.yml 
[201] Trailing whitespace
test01.yml:3
  

[206] Variables should have spaces before and after: {{ var_name }}
test01.yml:9
        msg: "{{msg}}"

[202] Octal file permissions must contain leading zero or be a string
test01.yml:11
Task/Handler: Create a directory if it does not exist

[504] Do not use 'local_action', use 'delegate_to: localhost'
test01.yml:18
      local_action:

...(略)...

いくつかのメッセージが表示されました。

VS Code のタスクに登録

コマンドで実行するのは少々面倒に思い、VS Code の「タスク」に登録することにします。

さらに、lint結果を problem matcher で解析して、VS Code の問題として認識させるようにします。

問題として認識することで、エディター画面で問題箇所を示してくれたり、「問題」を一覧表示してくれたりします。

.vscode/tasks.json に、以下のように定義します。

  • .vscode/tasks.json
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "ansible-lint current file",
      "type": "shell",
      "command": "/root/envs/a2914/bin/ansible-lint",  // 環境に応じて変える
      "args": [
        "${file}",
        "-p"
      ],
      "detail": "現在開いているファイルを ansible-lint する",
      "presentation": {
        "reveal": "never",
      },
      "problemMatcher": {
        "owner": "ansible-lint",
        "fileLocation": "autoDetect",
        "pattern":[
            {
                // sample
                // 01_show.yml:10: [E201] Trailing whitespace
                "regexp": "^(.+):(\\d+): (.+)$",
                "file": 1,
                "line": 2,
                "message": 3,
            }
          ],
      },
    }
  ]
}

これで、タスク ansible-lint current file を実行すると、今開いているファイルにたいして ansible-lint ファイル名 -p を実行し、問題を表示してくれます。

-p を付けたのは、problem matcher の正規表現で拾いやすくするためです。

タスクの定義方法の詳細は、VS Code の公式ドキュメントを参照してください。

エディタで対象の Playbook を開いている状態で、コマンドパレット(mac: Command + Shift + P / win: Ctrl + Shift + P )から、「タスク: タスクの実行」を選択します。

f:id:akira6592:20201024212802p:plain
コマンドパレットからタスクの実行を選択

タスクの一覧の中から、先程 tasks.json に定義した ansible-lint current file を選択します。

f:id:akira6592:20201024212846p:plain
タスク ansible-lint current file を選択

すると、開いていた Playbook に対して ansible-lint が実行され、問題の箇所に波線が引かれたり、問題の一覧が表示されたります。

f:id:akira6592:20201024213127p:plain
ansible-lint 結果がエディタ内や問題一覧に表示される

ファイル保存時に自動 ansible-lint

ここまでで、タスク経由で ansible-lint を実行して問題として扱えるようになりました。

しかしまだ、都度タスクを実行するが手間と感じため、ファイル保存時に ansible-lint ができるようにしようと考えました。 (本当はリアルタイムにやりたい)。

ファイル保存をトリガーにしてタスクを実行する機能は標準にはないようなので、「Trigger Task on Save」という拡張を利用しました。

インストールした上で、.vscode/setting.jsontasks.jsonではなく)に、以下の設定を追加します。

  • .vscode/setting.json
{
    // 既存設定は省略

    "triggerTaskOnSave.on": true,
    "triggerTaskOnSave.tasks": {
        "ansible-lint current file": [
            "*.yml",
            "*.yaml"
        ]
    }
}

これで、*.yml*.yaml のファイルを保存するときに、さきほどのタスク ansible-lint current file を実行するようになります。

さらに yamllint も同時に自動実行

ansible-lint と似た linter に yamllint があります。(pip install yamllint でインストール)

ansible-lintyamllint のルールをカバーしているわけではないので、両者を組み合わせて利用されているかたも多いのではないでしょうか。

同じような設定することで、yamllint も同時に自動実行できるようになります。

両方実行する、tasks.jsonsetting.json はそれぞれ以下の通りです。

  • .vscode/tasks.json
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    // {
    //   "label": "echo",
    //   "type": "shell",
    //   "command": "echo Hello"
    // },
    {
      "label": "ansible-lint current file",
      "type": "shell",
      "command": "/root/envs/a2914/bin/ansible-lint",
      "args": [
        "${file}",
        "-p"
      ],
      "detail": "現在開いているファイルを ansible-lint する",
      "presentation": {
        "reveal": "never",
      },
      "problemMatcher": {
        "owner": "ansible-lint",
        "fileLocation": "autoDetect",
        "pattern":[
            {
                // sample
                // 01_show.yml:10: [E201] Trailing whitespace
                "regexp": "^(.+):(\\d+): (.+)$",
                "file": 1,
                "line": 2,
                "message": 3,
            }
          ],
      },
    },
    {
      "label": "yamllint current file",
      "type": "shell",
      "command": "/root/envs/a2914/bin/yamllint",
      "args": [
        "-f",
        "parsable",
        "${file}",
      ],
      "detail": "現在開いているファイルを yamllint する",
      "presentation": {
        "reveal": "never",
      },
      // "isBackground": true,
      "problemMatcher": {
        "owner": "yamlllint",
        "fileLocation": "autoDetect",
        "pattern":[
            {
                // sample
                /// /home/sakana/01_show.yml:8:1: [error] too many blank lines (8 > 0) (empty-lines)
                "regexp": "^(.+):(\\d+):(\\d+): \\[(.+?)\\] (.+)$",
                "file": 1,
                "line": 2,
                "column": 3,
                "severity": 4,
                "message": 5,
            }
          ],
      },
    },
  ]
}
  • .vscode/setting.json
{
    // 既存設定は省略

    "triggerTaskOnSave.on": true,
    "triggerTaskOnSave.tasks": {
        "ansible-lint current file": [
            "*.yml",
            "*.yaml"
        ],
        "yamllint current file": [
            "*.yml",
            "*.yaml"
        ],
    }
}

これで、ファイル保存時に ansible-lintyamllint が実行されます。

f:id:akira6592:20201024212617p:plain
ansible-lintとyamllint同時実行

ansible-lint では引っかからなかった、 真偽値は falsetrue で指定してね、というメッセージが表示されるようになったことが分かります。


さらに(妄想レベル)

さらに、ansible-playook コマンドの --syntax-check オプションの結果も扱えるようにできたらいいなと思っています。

また、ファイル保存時ではなく、もっと早くリアルタイムに「問題」として検出できる方法があれば知りたいです。


Part21 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連ほかにも
  • connection: local ななにか
  • Ansible Toewr / AWX をコマンドがら操作する
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • parsee_cli モジュール(Part15 の続き)

[Ansible] Best Practice に記載されていた Directory Layout は Sample Ansible setup へ移動した

Ansible 2.10 のリリースに合わせて、公式ドキュメントの一部で、ページ構成ごと変わるような変更がありました。

代表的なのは「Best Practice」というページです(2.9 版ドキュメントはこちら)、このページには Directory Layoutや、Alternative Directory Layoutが掲載されていたため、「ベストプラクティのディレクトリ構成」と呼ばれる傾向があったように思います。

Ansible 2.10 のドキュメントでは、Best Practice のページのタイトルは Tips and tricks に変更されました。

docs.ansible.com

また、各 Directory Layout とは Sample Ansible setupという新規ページに移動しました。

docs.ansible.com

探している方もいらっしゃったようなので、まとめておきます、

[Ansible] Ansible Tower / AWX の API で情報取得する汎用 lookup plugin の tower_api

はじめに

Ansible には、Ansible Tower や AWX の情報を取得したり、設定を行うモジュールが多数あります。

docs.ansible.com

バージョンを追うごとに増えていて、対応範囲が広がっていますが、それでもまだのものもあります。

そんなときに便利そうなのが、tower_api lookup plugin です。CHANGELOG には見当たりませんでしたが、awx.awx collection 14.0 で追加されたようです。

おそらくGETのみとなりますが、モジュールがなくてもAPI のエンドポイントやフィルターを直接指定して、情報を取得できます。

モジュールがない場合の代替手段は、uri モジュール あたりになりますが、それよりは楽に URL を組み立てられたりするかと思います。

この記事では簡単なサンプルで説明します。

  • 動作確認環境
    • Ansible 2.9.14
    • AWX 15.0

サンプル Playbook

ここでは、ワークフロージョブの結果を取得して画面に表示する Playbook を作成します。

ジョブであれば、tower_job_list モジュール が利用できますが、ワークフロージョブに対応するモジュールは現状ないようです。

そのため、awx.awx.tower_api lookup plugin を利用します。

---
- hosts: awx01
  gather_facts: false
  connection: local

  tasks:
    - name:
      debug:
        msg: "{{ res }}"
      vars:
        res: "{{ query('awx.awx.tower_api', 'workflow_jobs', query_params={'id': 38}, 
                  host='http://10.0.0.90',username='admin', password='password') }}"

queryawx.awx.tower_api を指定します。

次の引数で、対象の API エンドポイントを指定します。 予め、実機の http://tower/api/v2/ や、API リファレンスなどで調べておきます。

ワークフロージョブの実行結果(テンプレートではなく)の場合は、workflow_jobs です。

query_params には、フィルターを指定します。なくても構いません。ここでは、 id38 のものと指定しています。利用できるフィルターは、API リファレンスなどを参照してください。

他は、接続や認証情報です。サンプルのためベタ書きしています。後述の環境変数で指定する方法がうまくいけば指定不要になるでしょう。

すべてのオプショの説明は awx.awx.tower_api lookup plugin のページを参照してください。

接続・認証情報についての補足

なお、awx.awx.tower_api lookup plugin のページには TOWER_HOST などの環境変数でも指定できると記載がありますが、私が以下のように試したところ反映されずに、デフォルト設定で接続(localhostへ)しようとしていました。lookup plugin ではなく、他の tower_* モジュールではこの方法でうなくいったのですが。

---
- hosts: awx01
  gather_facts: false
  connection: local
  environment:   # うまくいかなかった指定例
    TOWER_USERNAME: admin
    TOWER_PASSWORD: password
    TOWER_HOST: http://10.0.0.90

  tasks:
    # ...(略)... awx.awx.tower_api 利用タスク

実行

Playbook を実行します。

$ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook -i inventory.ini job.yml     

PLAY [all] ************************************************************************************************************

TASK [debug] **********************************************************************************************************
ok: [lolcahost] => {
    "msg": [
        {
            "allow_simultaneous": false,
            "canceled_on": null,
            "created": "2020-10-18T16:17:54.013277Z",
            "description": "",
            "elapsed": 8.067,
            "extra_vars": "{\"wf_extra\": \"val1\", \"wf_extra_list\": [111, 222, 333], \"wr_survey_var1\": \"ttttttttt\"}",
            "failed": false,
            "finished": "2020-10-18T16:18:02.282742Z",
            "id": 38,
            "inventory": null,
            "is_sliced_job": false,
            "job_explanation": "",
            "job_template": null,
            "launch_type": "manual",
            "limit": null,
            "modified": "2020-10-18T16:17:54.218539Z",
            "name": "wf_test",
            "related": {
                "activity_stream": "/api/v2/workflow_jobs/38/activity_stream/",
                "cancel": "/api/v2/workflow_jobs/38/cancel/",
                "created_by": "/api/v2/users/1/",
                "labels": "/api/v2/workflow_jobs/38/labels/",
                "modified_by": "/api/v2/users/1/",
                "notifications": "/api/v2/workflow_jobs/38/notifications/",
                "relaunch": "/api/v2/workflow_jobs/38/relaunch/",
                "unified_job_template": "/api/v2/workflow_job_templates/8/",
                "workflow_job_template": "/api/v2/workflow_job_templates/8/",
                "workflow_nodes": "/api/v2/workflow_jobs/38/workflow_nodes/"
            },
            "scm_branch": null,
            "started": "2020-10-18T16:17:54.215745Z",
            "status": "successful",
            "summary_fields": {
                "created_by": {
                    "first_name": "",
                    "id": 1,
                    "last_name": "",
                    "username": "admin"
                },
                "labels": {
                    "count": 0,
                    "results": []
                },
                "modified_by": {
                    "first_name": "",
                    "id": 1,
                    "last_name": "",
                    "username": "admin"
                },
                "unified_job_template": {
                    "description": "",
                    "id": 8,
                    "name": "wf_test",
                    "unified_job_type": "workflow_job"
                },
                "user_capabilities": {
                    "delete": true,
                    "start": true
                },
                "workflow_job_template": {
                    "description": "",
                    "id": 8,
                    "name": "wf_test"
                }
            },
            "type": "workflow_job",
            "unified_job_template": 8,
            "url": "/api/v2/workflow_jobs/38/",
            "webhook_credential": null,
            "webhook_guid": "",
            "webhook_service": "",
            "workflow_job_template": 8
        }
    ]
}

PLAY RECAP ************************************************************************************************************
lolcahost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

無事に表示されました。

AWX の画面でいうとこの画面に相当する情報です。

f:id:akira6592:20201019094348p:plain
ワークフロージョブ実行結果画面

おわりに

仮に、対応するモジュールがあったとしても、情報取得するだけあれば、わざわざ1タスクにする必要がない awx.awx.tower_api lookup plugin は便利かもしれません。

[Ansible] 「つまずき Ansible 【Part19】モジュールのコードをデバッグしたい」ふりかえり

はじめに

2020/10/17 に、YouTube Live で「つまずき Ansible 【Part19】モジュールのコードをデバッグしたい」という配信をしました。

実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、ANSIBLE_KEEP_REMOTE_FILES の設定を有効にして Playbook を実行して残った Python のコードをもとにしてデバッグをしてみます。

やったことや、わかったことをふりかえります。

  • 環境

動画

youtu.be

チャプターもつけてます。


■ やったこと

ANSIBLE_KEEP_REMOTE_FILES を有効にして実行

利用した Playbook は以下のとおりです。

NetBox にサイトというオブジェクトを作成する APIuri モジュールで叩きます。

本当は専用のモジュールがあるのですが、ここではよりメジャーなモジュールとして uri を取り上げました。

---
- hosts: netbox01
  gather_facts: false
  connection: local

  vars:
    url: http://192.168.1.145:32769/api/dcim/sites/
    
  tasks:
    - name: 
      uri:
        url: "{{ url }}"
        method: POST
        headers:
          Authorization: "Token {{ token }}"
        # body: "{{ lookup('file', 'body.json') }}"
        body:
          name: test1
          slug: test1
        status_code:
          - 200
          - 201
        body_format: json

実行コマンド

ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook -i inventory.ini uri.yml 

この場合は、一回限りの環境変数としての指定指定です。

他の設定と同様に、ansible.cfg にも定義できます。詳細は 公式ドキュメントを参照してください。

残ったファイルの確認

先程のように ANSIBLE_KEEP_REMOTE_FILES を有効にして Playbook を実行すると、ターゲットノードの ~/.ansible/tmp ディレクトリ配下の、実処理を実行するための Python ファイルが残されます。

通常であれば、このファイルは実行後に削除されるのですが、それを残すのが ANSIBLE_KEEP_REMOTE_FILES の設定ということです。

今回は、connection: local なので、Ansible コントロールノード自身の ~/.ansible/tmp ディレクトリ配下に残ります。

(a2100) [root@centos7 tmp]# ls -al
合計 0
drwx------. 3 root root 66 10月 17 20:10 .
drwx------. 5 root root 66 10月  7 19:48 ..
(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# ls -al
合計 156
drwx------. 3 root root     47 10月 17 20:14 .
drwx------. 3 root root     66 10月 17 20:10 ..
-rwxr--r--. 1 root root 157816 10月 17 20:10 AnsiballZ_uri.py

AnsiballZ_モジュール名.py というファイル名で残りリます。

残ったファイルを普通に実行

AnsiballZ_uri.py は、Ansible は不要で、通情の Python ファイルとして実行できます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py

{"redirected": false, "url": "http://192.168.1.145:32769/api/dcim/sites/", "status": 201, "server": "nginx", "date": "Sat, 17 Oct 2020 11:13:19 GMT", "content_type": "application/json", "content_length": "467", "connection": "close", "location": "http://192.168.1.145:32769/api/dcim/sites/9/", "vary": "Accept, Cookie, Origin", "allow": "GET, POST, HEAD, OPTIONS", "api_version": "2.9", "x_content_type_options": "nosniff", "referrer_policy": "same-origin", "x_frame_options": "SAMEORIGIN", "p3p": "CP=\"ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV\"", "cookies_string": "", "cookies": {}, "msg": "OK (467 bytes)", "elapsed": 0, "changed": false, "json": {"id": 9, "url": "http://192.168.1.145:32769/api/dcim/sites/9/", "name": "test1", "slug": "test1", "status": {"value": "active", "label": "Active"}, "region": null, "tenant": null, "facility": "", "asn": null, "time_zone": null, "description": "", "physical_address": "", "shipping_address": "", "latitude": null, "longitude": null, "contact_name": "", "contact_phone": "", "contact_email": "", "comments": "", "tags": [], "custom_fields": {}, "created": "2020-10-17", "last_updated": "2020-10-17T11:13:19.879794Z"}, "invocation": {"module_args": {"url": "http://192.168.1.145:32769/api/dcim/sites/", "method": "POST", "headers": {"Authorization": "Token 0123456789abcdef0123456789abcdef01234567", "Content-Type": "application/json"}, "body": {"name": "test1", "slug": "test1"}, "status_code": [200, 201], "body_format": "json", "force": false, "http_agent": "ansible-httpget", "use_proxy": true, "validate_certs": true, "force_basic_auth": false, "return_content": false, "follow_redirects": "safe", "timeout": 30, "remote_src": false, "unsafe_writes": false, "url_username": null, "url_password": null, "client_cert": null, "client_key": null, "dest": null, "src": null, "creates": null, "removes": null, "unix_socket": null, "mode": null, "owner": null, "group": null, "seuser": null, "serole": null, "selevel": null, "setype": null, "attributes": null}}}

ただし、中身の大半がエンコードされているためデバッグがまともにできません。

AnsiballZ_uri.py を普通に実行するのはた、ただの Playbook のタスクの再現用という感じです。

explode て展開する

AnsiballZ_uri.py 内の、エンコードされた部分を展開するのが explode オプションです。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py explode
Module expanded into:
/root/.ansible/tmp/ansible-tmp-1602933024.2020504-20448-161437222207769/debug_dir

debug_dir の中に、タスクのオプションを JSON 化した arg や、モジュールのコードの本体や、それが利用するファイル一式が含まれます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# tree debug_dir/
debug_dir/
├── ansible
│   ├── __init__.py
│   ├── __pycache__
│   │   └── __init__.cpython-36.pyc
│   ├── module_utils
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-36.pyc
│   │   │   ├── _text.cpython-36.pyc
│   │   │   ├── basic.cpython-36.pyc
│   │   │   ├── pycompat24.cpython-36.pyc
│   │   │   └── urls.cpython-36.pyc
│   │   ├── _text.py
│   │   ├── basic.py
│   │   ├── common
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   ├── _collections_compat.cpython-36.pyc
│   │   │   │   ├── _json_compat.cpython-36.pyc
│   │   │   │   ├── _utils.cpython-36.pyc
│   │   │   │   ├── collections.cpython-36.pyc
│   │   │   │   ├── file.cpython-36.pyc
│   │   │   │   ├── parameters.cpython-36.pyc
│   │   │   │   ├── process.cpython-36.pyc
│   │   │   │   ├── sys_info.cpython-36.pyc
│   │   │   │   ├── validation.cpython-36.pyc
│   │   │   │   └── warnings.cpython-36.pyc
│   │   │   ├── _collections_compat.py
│   │   │   ├── _json_compat.py
│   │   │   ├── _utils.py
│   │   │   ├── collections.py
│   │   │   ├── file.py
│   │   │   ├── parameters.py
│   │   │   ├── process.py
│   │   │   ├── sys_info.py
│   │   │   ├── text
│   │   │   │   ├── __init__.py
│   │   │   │   ├── __pycache__
│   │   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   │   ├── converters.cpython-36.pyc
│   │   │   │   │   └── formatters.cpython-36.pyc
│   │   │   │   ├── converters.py
│   │   │   │   └── formatters.py
│   │   │   ├── validation.py
│   │   │   └── warnings.py
│   │   ├── compat
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── selectors.cpython-36.pyc
│   │   │   ├── _selectors2.py
│   │   │   └── selectors.py
│   │   ├── distro
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── _distro.cpython-36.pyc
│   │   │   └── _distro.py
│   │   ├── parsing
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── convert_bool.cpython-36.pyc
│   │   │   └── convert_bool.py
│   │   ├── pycompat24.py
│   │   ├── six
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       └── __init__.cpython-36.pyc
│   │   └── urls.py
│   └── modules
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-36.pyc
│       │   └── uri.cpython-36.pyc
│       └── uri.py
└── args

18 directories, 60 files

args を編集して再実行する

args でタスクのオプションにあたる値を編集できます。今回は body の中を編集しました。

{
  "ANSIBLE_MODULE_ARGS": {
    "url": "http://192.168.1.145:32769/api/dcim/sites/",
    "method": "POST",
    "headers": {
      "Authorization": "Token 0123456789abcdef0123456789abcdef01234567"
    },
    "body": {
      "name": "test3",
      "slug": "test3"
    },
  ...(略)...
  }
}

編集した args など、debug_dir 配下のフィアルを利用して再実行するには、execute オプションを付けます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py execute
...(略)...

execute オプションを付けないと、AnsiballZ_uri.py 単体の実行となります。

ここまでが、Ansible 固有の仕様です。

VS Codeブレークポイントをはってステップ実行する

ここからは 一般的な Pythonデバッグの手法と同じです。

ここでは、モジュールの本体のファイルである debug_dir/ansible/modules/uri.pymain() 呼び出し箇所にブレークポイントをはります。

f:id:akira6592:20201017212749p:plain
ブレークポイントをはる

execute オプションを付けてデバッグを開始するために、~/.vscode/launch.json に追記します。

{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    },
    // 以下追加
    {
      "name": "Python: Current File with execute",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "args": [
        "execute"
      ],
      "console": "integratedTerminal"
    }
  ]
}

AnsiballZ_uri.py を開いている状態で、先程 launch.json に定義した定義した Python: Current File with executeデバッグを始めます。

f:id:akira6592:20201017212833p:plain
デバッグの開始

処理が始まり、ブレークポイントをはった debug_dir/ansible/modules/uri.pymain() の呼び出し箇所で止まりました。

f:id:akira6592:20201017212912p:plain
ブレークポイントで止まった

ここからステップ実行ができます。

たとえば、モジュールのオプションで与えた値は、モジュールのコード内でこのように変数に格納されます。

f:id:akira6592:20201017213048p:plain
body オプションに与えた値

途中で変数の値を変更して、処理を続けることもできます。

f:id:akira6592:20201017213329p:plain
test3(変更前)

f:id:akira6592:20201017213557p:plain
test4 (変更後)
VS Code によるデバッグは、公式ドキュメント などを参照にしてください。

モジュールの処理を細かく追いいたいときに便利だと思います。


Part20 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連ほかにも
  • connection: local ななにか
  • Ansible Toewr / AWX をコマンドがら操作する
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • parsee_cli モジュール(Part15 の続き)

[Ansible] Ansible 2.10 で pip install ansible --upgarade するとセットの collection もアップグレードされる

はじめに

このブログでも何度か触れましたが、Ansible 2.10 から Python のパッケージとしては、ansibleansible-base の2つができました。

ansible-base は基本機能と約70の標準モジュールです。ansibleansible-base と、元標準モジュール相当の collection のセットです。

ansible は 9月に 2.10.0 がリリースされ、10月に 2.10.1 がリリースされました。pip--upgrade するさいに、セットだったコレクションがどうなるか気になったのですが、試したところアップグレードされました。

あくまでも ansible-build-data の定義に基づくようです。

この記事では具体的なログを掲載します。


■ 事前状態 (ansible 2.10.0)

インストール済み Python パッケージの確認

% pip freeze
ansible==2.10.0
ansible-base==2.10.2
cffi==1.14.3
cryptography==3.1.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
pycparser==2.20
pyparsing==2.4.7
PyYAML==5.3.1
six==1.15.0

記憶が正しければ、ansible-base 2.10.2 のリリース前は、ansible 2.10.0ansible-base 2.10.1 だった気がします。

セットの collection の確認

ansible-build-dataansible-2.10.0.deps のとおりです。

(デフォルトで ~/.ansible/ansible_collection インストールされるものとは別です)

% ansible-galaxy collection list -p ~/envs/a210/lib/python3.8/site-packages/ansible_collections/

# /Users/sakana/envs/a210/lib/python3.8/site-packages/ansible_collections
Collection                Version
------------------------- -------
amazon.aws                1.2.0  
ansible.netcommon         1.2.1  
ansible.posix             1.1.1  
ansible.windows           1.0.0  
arista.eos                1.0.3  
awx.awx                   14.1.0 
azure.azcollection        1.0.0  
check_point.mgmt          1.0.6  
chocolatey.chocolatey     1.0.2  
cisco.aci                 1.0.0  
cisco.asa                 1.0.3  
cisco.intersight          1.0.8  
cisco.ios                 1.0.3  
cisco.iosxr               1.0.5  
cisco.meraki              2.0.0  
cisco.mso                 1.0.0  
cisco.nxos                1.1.0  
cisco.ucs                 1.5.0  
cloudscale_ch.cloud       1.1.0  
community.aws             1.2.0  
community.azure           1.0.0  
community.crypto          1.1.1  
community.digitalocean    1.0.0  
community.general         1.1.0  
community.grafana         1.0.0  
community.kubernetes      1.0.0  
community.libvirt         1.0.0  
community.mongodb         1.0.0  
community.mysql           1.0.0  
community.network         1.1.0  
community.proxysql        1.0.0  
community.rabbitmq        1.0.1  
community.skydive         1.0.0  
community.vmware          1.2.0  
community.windows         1.0.0  
community.zabbix          1.0.0  
containers.podman         1.2.0  
cyberark.conjur           1.0.7  
cyberark.pas              1.0.5  
dellemc.os10              1.0.1  
dellemc.os6               1.0.2  
dellemc.os9               1.0.2  
f5networks.f5_modules     1.5.0  
fortinet.fortimanager     1.0.5  
fortinet.fortios          1.0.15 
frr.frr                   1.0.3  
gluster.gluster           1.0.1  
google.cloud              1.0.0  
hetzner.hcloud            1.0.0  
ibm.qradar                1.0.3  
infinidat.infinibox       1.2.3  
junipernetworks.junos     1.1.0  
mellanox.onyx             1.0.0  
netapp.aws                20.8.0 
netapp.elementsw          20.8.0 
netapp.ontap              20.8.0 
netapp_eseries.santricity 1.0.8  
netbox.netbox             1.0.2  
ngine_io.cloudstack       1.0.1  
ngine_io.exoscale         1.0.0  
ngine_io.vultr            1.0.0  
openstack.cloud           1.1.0  
openvswitch.openvswitch   1.0.5  
ovirt.ovirt               1.1.3  
purestorage.flasharray    1.4.0  
purestorage.flashblade    1.3.0  
servicenow.servicenow     1.0.2  
splunk.es                 1.0.2  
theforeman.foreman        1.1.0  
vyos.vyos                 1.0.4  
wti.remote                1.0.1  


■ アップグレード

% pip install ansible --upgrade
Collecting ansible
  Using cached ansible-2.10.1.tar.gz (25.9 MB)
Requirement already satisfied, skipping upgrade: ansible-base<2.11,>=2.10.2 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from ansible) (2.10.2)
Requirement already satisfied, skipping upgrade: jinja2 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from ansible-base<2.11,>=2.10.2->ansible) (2.11.2)
Requirement already satisfied, skipping upgrade: PyYAML in /Users/sakana/envs/a210/lib/python3.8/site-packages (from ansible-base<2.11,>=2.10.2->ansible) (5.3.1)
Requirement already satisfied, skipping upgrade: cryptography in /Users/sakana/envs/a210/lib/python3.8/site-packages (from ansible-base<2.11,>=2.10.2->ansible) (3.1.1)
Requirement already satisfied, skipping upgrade: packaging in /Users/sakana/envs/a210/lib/python3.8/site-packages (from ansible-base<2.11,>=2.10.2->ansible) (20.4)
Requirement already satisfied, skipping upgrade: MarkupSafe>=0.23 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from jinja2->ansible-base<2.11,>=2.10.2->ansible) (1.1.1)
Requirement already satisfied, skipping upgrade: six>=1.4.1 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from cryptography->ansible-base<2.11,>=2.10.2->ansible) (1.15.0)
Requirement already satisfied, skipping upgrade: cffi!=1.11.3,>=1.8 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from cryptography->ansible-base<2.11,>=2.10.2->ansible) (1.14.3)
Requirement already satisfied, skipping upgrade: pyparsing>=2.0.2 in /Users/sakana/envs/a210/lib/python3.8/site-packages (from packaging->ansible-base<2.11,>=2.10.2->ansible) (2.4.7)
Requirement already satisfied, skipping upgrade: pycparser in /Users/sakana/envs/a210/lib/python3.8/site-packages (from cffi!=1.11.3,>=1.8->cryptography->ansible-base<2.11,>=2.10.2->ansible) (2.20)
Using legacy setup.py install for ansible, since package 'wheel' is not installed.
Installing collected packages: ansible
  Attempting uninstall: ansible
    Found existing installation: ansible 2.10.0
    Uninstalling ansible-2.10.0:
      Successfully uninstalled ansible-2.10.0
    Running setup.py install for ansible ... done
Successfully installed ansible-2.10.1
WARNING: You are using pip version 20.1.1; however, version 20.2.3 is available.
You should consider upgrading via the '/Users/sakana/envs/a210/bin/python3 -m pip install --upgrade pip' command.

インストール済み Python パッケージの確認

ansible==2.10.0 だった箇所が ansible==2.10.1 になりました。

% pip freeze                                                                                    
ansible==2.10.1
ansible-base==2.10.2
cffi==1.14.3
cryptography==3.1.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
pycparser==2.20
pyparsing==2.4.7
PyYAML==5.3.1
six==1.15.0

セットの collection の確認

ansible-build-dataansible-2.10.1.deps のとおりです。

たとえば、cisco.ios collection は 1.0.3 から 1.1.0 になりました。

% ansible-galaxy collection list -p ~/envs/a210/lib/python3.8/site-packages/ansible_collections/

# /Users/sakana/envs/a210/lib/python3.8/site-packages/ansible_collections
Collection                Version
------------------------- -------
amazon.aws                1.2.1  
ansible.netcommon         1.3.0  
ansible.posix             1.1.1  
ansible.windows           1.0.1  
arista.eos                1.1.0  
awx.awx                   14.1.0 
azure.azcollection        1.2.0  
check_point.mgmt          1.0.6  
chocolatey.chocolatey     1.0.2  
cisco.aci                 1.0.0  
cisco.asa                 1.0.3  
cisco.intersight          1.0.8  
cisco.ios                 1.1.0  
cisco.iosxr               1.1.0  
cisco.meraki              2.0.0  
cisco.mso                 1.0.0  
cisco.nxos                1.2.0  
cisco.ucs                 1.5.0  
cloudscale_ch.cloud       1.2.0  
community.aws             1.2.1  
community.azure           1.0.0  
community.crypto          1.2.0  
community.digitalocean    1.0.0  
community.general         1.2.0  
community.grafana         1.0.0  
community.kubernetes      1.1.1  
community.libvirt         1.0.0  
community.mongodb         1.0.0  
community.mysql           1.1.0  
community.network         1.2.0  
community.proxysql        1.0.0  
community.rabbitmq        1.0.1  
community.skydive         1.0.0  
community.vmware          1.3.0  
community.windows         1.1.0  
community.zabbix          1.0.0  
containers.podman         1.3.1  
cyberark.conjur           1.0.7  
cyberark.pas              1.0.5  
dellemc.os10              1.0.1  
dellemc.os6               1.0.2  
dellemc.os9               1.0.2  
f5networks.f5_modules     1.5.0  
fortinet.fortimanager     1.0.5  
fortinet.fortios          1.0.15 
frr.frr                   1.0.3  
gluster.gluster           1.0.1  
google.cloud              1.0.1  
hetzner.hcloud            1.1.0  
ibm.qradar                1.0.3  
infinidat.infinibox       1.2.3  
junipernetworks.junos     1.1.1  
mellanox.onyx             1.0.0  
netapp.aws                20.9.0 
netapp.elementsw          20.10.0
netapp.ontap              20.10.0
netapp_eseries.santricity 1.0.8  
netbox.netbox             1.1.0  
ngine_io.cloudstack       1.0.1  
ngine_io.exoscale         1.0.0  
ngine_io.vultr            1.0.0  
openstack.cloud           1.2.0  
openvswitch.openvswitch   1.0.5  
ovirt.ovirt               1.1.4  
purestorage.flasharray    1.4.0  
purestorage.flashblade    1.3.0  
servicenow.servicenow     1.0.2  
splunk.es                 1.0.2  
theforeman.foreman        1.3.0  
vyos.vyos                 1.0.5  
wti.remote                1.0.1 


おわりに

もし、本体(ansible-base)をそのままにして、collection のみをアップグレードしたい場合は、個別に ansible-galaxy collection install するようになるのかなと思います。

[NetBox] カスタムリンクでオブジェクト(obj)のどんな値を参照できるかの調べ方

はじめに

以前の記事で、NetBox にカスタムリンクを作成する方法をご紹介しました。

tekunabe.hatenablog.jp

この中で、TextURL の項目では、オブジェクトの名前などの情報を参照できることに触れました。

画面内の説明には Reference the object as {{ obj }} とあり、たしかに {{ obj }} とするとオブジェクト名(デバイスならデバイス名)を参照できました。

{{ obj.site }} のようにすると、名前だけでなくその他の値も参照できます。

この記事では、カスタムリンクの TextURL の項目で、どのようなオブジェクトの値が参照できるかを調べ方をご紹介します。

調べ方が分かった経緯

どのようなオブジェクトの値が参照できるかは、ドキュメントには掲載されていません。

どうやったら分かるかなと思ったときに、雰囲気で {{ obj }} の代わりに {{ obj.pk }} を試しました(NetBox が Django で作られいることは分かっていました)。すると、オブジェクトのID (PrimaryKey)的なものが参照できたので、ここで指定するのは View の一部のようなもなんだろうと解釈しました。

対応する model を探す

ということで、画面に対応する model を参照すれば、その obj がどのような値を持つのか分かりそうです。

バイスの場合は、netbox/dcim/models/__init__.py にデバイスの model の定義 class Device があります。(2.9系だとnetbox/dcim/models/devices.py

IPアドレスの場合は、netbox/ipam/models.pyです。

デバイスの model の定義の中身を見ていくと、name の他、platformstatus などのフィールドがあることが分かります。 これにより、カスタムリンク側から、obj.name のようにすれば良いと推測できます。

具体的な指定と値の例です。

指定 値の例
obj.platform ios
obj.status active
obj.primary_ip4 192.168.1.11/24
obj.primary_ip4.address.ip 192.168.1.11

なお、値が空になっている項目を Text にした場合はリンクが表示されません。例えば、カスタムリンクの Text で {{ obj.comments }} とだけ指定した場合、Comments が空のデバイスのを開いたときは、リンク字体が画面に表示されません。

テンプレートも参考に

View に仕様しているテンプレートも参考になります。

例えば、デバイス画面であれば、netbox/templates/dcim/device.htmlです。IPアドレスならnetbox/templates/ipam/ipaddress.htmlです。

デバイス画面のテンプレートを見ると、

{{ device.site }}

のような記載があります。カスタムリンクとしては

{{ obj.site }}

のように、deviceobj に置き換えれば参照できます。

おわりに

カスタムリンクの TextURL で、オブジェクトのどうのような値を参照できるかを調べる方法をご紹介しました。もっと効率がいい調べ方があるかもしれませんが、現在のところ私はこうやりました、という位置づけです。

なお、カスタムフィールドなど少し特殊な項目は、もうひと工夫必要そうです。

[2020/10/16 追記] カスタムフィールドは obj.cf.カスタムフィールド名 で参照できました。Export Templateの書式が参考になりました。