てくなべ (tekunabe)

ansible / network automation / 学習メモ

[NetBox] ちょっとした連携に便利なカスタムリンクの作成方法

はじめに

NetBox には、各オブジェクトの画面ごとにカスタムリンクを作成できます。

netbox.readthedocs.io

f:id:akira6592:20201011143420p:plain
カスタムリンク

他のシステムへのリンクも作成できます。また、リンクやテキストにはオブジェクトの名前やJinja2構文を使って動的に変えることもできます。

活用例

例えば、以下のようなことが実現できます。

  • 例1. Git 系サービスへのリンク
    • 予め、GitHubhttps://github.com/ユーザー名/config/デバイス名.txt にデバイスのコンフィグを配置しておく
    • 予め、NetBox のデバイス画面に、現在開いているデバイス名を含む上記の URL のカスタムリンクを作成しておく
    • NetBox 利用者は、上記のカスタムリンクをクリックして現在開いているデバイスのコンフィグを GitHub 上で表示する
      • (この例に限って言えば NAPALM 連携でも似たようなことを実現可能です。)
  • 例2. デバイスの Web GUI へのリンク
    • 予め、NetBox のデバイス画面に、現在開いているデバイスの管理IPアドレス含むURL(https://{{ obj.primary_ip4.address.ip }})のカスタムリンクを作成しておく
    • NetBox 利用者は、現在開いているデバイスのWeb GUI を、上記のカスタムリンクをクリックして表示する

この記事では、カスタムリンクの作成方法をご紹介します。

  • 動作確認環境
    • NetBox 2.8.9


■ 基本的なカスタムリンクの作成方法

バイスの管理画面に、そのデバイスのコンフィグを表示するためののリンク(GitHub リポジトリ上)を作る例をもとに、カスタムリンクの作成方法を説明します。

カスタムフィールド追加画面を開く

ここでは、カスタムフィールドの追加と同様に admin ユーザーで作業します。ログイン後、右上のメニューから Admin をクリックします。

f:id:akira6592:20201011141808p:plain:w400
Admin をクリック

全体の管理画面で、Custom links をクリックします。

f:id:akira6592:20201011141936p:plain:w400
Custom links をクリック

カスタムフィールド一覧画面で ADD CUSTOM LINK をクリックします。

f:id:akira6592:20201011142025p:plain:w400
ADD CUSTOM LINK をクリック

カスタムフィールド追加画面が表示されます。

f:id:akira6592:20201011142101p:plain:w400
カスタムフィールド追加画面

カスタムリンクの内容を入力する

カスタムフィールド追加画面で内容を追加します

Content type

Content type では、カスタムリンクを作成したい対象のオブジェクトの種類を選択します。

f:id:akira6592:20201011142341p:plain:w400
対象オブジェクトを選択

例えばデバイスの画面に作成したい場合は dcim > devie を、IP アドレス画面に作成したい場合は、ipam > IP Address を選択します。

ここでは、dcim > devie を選択します。

Name

Name では、カスタムリンクの名前を入力します。リンクのテキストではなく、カスタムリンクそのものを区別するための名前です。

ここでは、config と入力します。

Group name (任意)

Group name では、このリンクを含めるグループの名前を入力します。

同じグループのリンクは、同じドロップダウンリストの 1つの項目になります。

以下の図は 、Group nameSelect link を入力して複数のカスタムリンクをまとめた場合の例です。

f:id:akira6592:20201011142508p:plain:w400
グループはドロップダウンリストになる

Group name を入力しない場合は、横並びの独立したリンクになります。

ここでは、未入力にします。

Weight

Weight では、同じ画面に複数のカスタムリンクを作成する場合の表示順を指定します。

Weight が小さいリンクが左に、大きいリンクが右に表示されます。

f:id:akira6592:20201011142615p:plain:w400
Weight 大きいものは右に

ここでは、デフォルトの 100 のままにします。

Button class

Button class では、リンクのボタンの意味合いや色を指定します。

選択できる class と実際の表示は以下の通りです。

f:id:akira6592:20201011142845p:plain
意味合いと色

ここでは、デフォルトの Default のままとします。

New window

New window では、リンクを新しいウィンドウ(タブ)で開くかどうかを指定します。デフォルトはオフなので、同じ画面で開きます。

ここでは、チェックをいれます。

Text

Text では、リンクの表示上の文字列を指定します。

オブジェクトの名前などの情報や、Jinja2 構文が利用して、動的に内容を変更できます。

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

Jinja2 のフィルターを併用して、例えば、{{ obj | replace('-', '_') }} とすると、core-rt-01core_rt_01 になります。

他にも条件式や、ユーザー、権限情報も利用できるようです。詳細は公式ドキュメントを参照してください。

他、obj でオブジェクトのどんな情報が使えるかは、以下の記事を参照してください。

tekunabe.hatenablog.jp

ここでは config と入力します。

URL

URL では、実際のリンク先の URL を指定します。

http(s):// から始めると、外部のURLになります。

/hoge のように、/ から始めると、自サイトからのルートの指定になります。 例えば、http://netbox.example.com/dcim/devices/1/ を開いている場合は、リンクは http://netbox.example.com/hoge になります。

hoge のように、単純な文字列の指定だと、現在開いている URL の末尾に付加した URL になります。 例えば、http://netbox.example.com/dcim/devices/1/ を開いている場合は、リンクは http://netbox.example.com/dcim/devices/1/hoge になります。

また,先程の Text と同様に、オブジェクトの情報や Jinja2 構文が利用できます。

ここでは、https://github.com/akira6592/tekunabe/blob/master/config/{{ obj }}.txt を入力します。

この指定により、デバイス名が rt01 であれば{{{ obj }}.txt の部分が rt01.txt になります。

確認する

先程デバイス画面に対して作成したカスタムリンクを確認します。

任意のデバイス画面を開きます。画面右にカスタムリンクがあることを確認します。

f:id:akira6592:20201011143054p:plain
カスタムリンク `config` が追加された

リンクを開くと、デバイス名を含む URL が新しい開いたことを確認します。

f:id:akira6592:20201011143153p:plain
別サイト(GitHub)で対象デバイスのコンフィグが表示された

※ このファイルはあとで削除するかもしれません。


■ おわりに

リンクがあるだけで、他システムとの連携がある程度できることもあるので、カスタムリンクは便利な機能だと思いました。

[Ansible] 「つまずき Ansible 【Part18】boolean と仲良くなりたい」ふりかえり

はじめに

2020/10/10 に、YouTube Live で「つまずき Ansible 【Part18】boolean と仲良くなりたい」という配信をしました。

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

tekunabe.connpass.com

今回は、when や assert などで特にする boolean 値 についていろいろ試します。 Ansible 2.10 で変わった挙動も扱います。

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

  • 環境
    • ansible 2.9.14
      • デフォルトからの設定変更 HOST_KEY_CHECKING のみ
    • 一部明示した箇所のみ ansible 2.10.0

動画

www.youtube.com


■ やったこと

おためし

以下のように、when にさまざまな値を与えて、そのタスクが実行されれば True、skip されれば False 扱いだと理解しながらひとつずつ試しました。

その結果をコメントとして付記します。一部は 変数未定義の undefined となりました。

---
- hosts: localhost
  gather_facts: false
  connection: local

  tasks:
    - name: 
      debug:
        msg: mathed!!!
      when:
        - "'None'"      # True
        - "None"        # False
        - "'Null'"      # True
        - "Null"        # undefined
        - Null          # True
        - "'NaN'"       # True
        - "NaN"         # undefined
        - NaN           # undefined
        - "'False'"     # True
        - 'False'       # False
        - -1            # True
        - ''            # True   * python の bool() とは違う 
        - ""            # True   * python の bool() とは違う 
        - "''"          # False
        - "'hogehoge'"  # True
        - "hogehoge"    # undefined
        - 'hogehoge'    # undefined
        - True          # True
        - TRUE          # True
        - true          # True
        - 0             # False
        - 1             # True

概ね、最終的な評価は Pythonbool() に渡した結果と同じと言えると思います。

ただ、最終的に文字列として評価されるのかどうかは意識する必要があります。たとえば、'False' という指定は文字列ではなく、"'False'" は文字列として評価されます。

2.10 との比較

Ansible 2.10 で、CONDITIONAL_BARE_VARS という設定項目のデフォルトが True から、False に変更されました(2.9のドキュメント2.10のドキュメント)。

これにより、最悪 2.9 と 2.10 とで判定が反転するケースがあります。

以下の Playbook を CONDITIONAL_BARE_VARS はデフォルトのまま、 2.9.14 と 2.10.0 で実行してためしました。結果をコメントで付記します。

---
- hosts: localhost
  gather_facts: false
  connection: local

  tasks:
    - name: 1-1
      debug:
        msg: "matched!!"
      when:
        - item
      loop:
        - 0           # 同じ(False)
        - 1           # 同じ(True
        - "0"         # 2.9 False、2.10 True
        - "1"         # 同じ(True)
        - '0'         # 2.9 False、2.10 True
        - '1'         # 同じ(True)
        - "'0'"       # 同じ(True)
        - "'1'"       # 同じ(True)

    - name: 1-2
      debug:
        msg: "matched!!"
      when:
        - item
      loop:
        - True        # 同じ(True)
        - False       # 同じ(False)
        - "True"      # 同じ(True)
        - "False"     # 2.9 False、2.10 True
        - 'True'      # 同じ(True)
        - 'False'     # 2.9 False、2.10 True
        - "'True'"    # 同じ(True)
        - "'False'"   # 同じ(True)

    - name: debug 1-3
      debug:
        msg: matched!
      when: 
        - condition  # 2.9 False、2.10 True
      vars:
        condition: "1 == 0"

どういうことか

やや推測も入りますが、こういうことかなと思います。たとえば、

      when:
        - condition

condition の中身が "1 == 0" だったとき CONDITIONAL_BARE_VARS がデフォルトで True な 2.9 では、

      when:
        - "1 == 0"

という解釈になり、結果的に False

一方で、 CONDITIONAL_BARE_VARS がデフォルトで False な 2.10 では、

      when:
        - "'1 == 0'"

という解釈になり、結果的に True。(1文字以上の文字列は True

bare vars が厳密に何を指すのかは詳細はわからないのですが、前述のように、評価式に変数名だだけが与えられることを指すようです。 このような状態のときは警告が表示されます。この警告は 2.9 でも表示されます。もし表示されたら 2.10 への移行時は特に注意したほうが良さそうです。

[DEPRECATION WARNING]: evaluating 'item' as a bare variable, this behaviour will go away and you might need to add |bool to
 the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle. This feature will be removed in version 
2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

もちろん CONDITIONAL_BARE_VARS の設定は、 ansible.cfg などで変更できます。

参考: 2.9.14 の結果(クリックして開く)

PLAY [localhost] *****************************************************************

TASK [1-1] ***********************************************************************
skipping: [localhost] => (item=0) 
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=0) 
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=0) 
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
ok: [localhost] => (item='0') => {
    "msg": "matched!!"
}
ok: [localhost] => (item='1') => {
    "msg": "matched!!"
}

TASK [1-2] ***********************************************************************
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=False) 
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=False) 
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=False) 
ok: [localhost] => (item='True') => {
    "msg": "matched!!"
}
ok: [localhost] => (item='False') => {
    "msg": "matched!!"
}

TASK [debug 1-3] *****************************************************************
skipping: [localhost]

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

参考: 2.10.0 の結果(クリックして開く)

PLAY [localhost] ***************************************************************

TASK [1-1] *********************************************************************
skipping: [localhost] => (item=0) 
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=0) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=0) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=1) => {
    "msg": "matched!!"
}
ok: [localhost] => (item='0') => {
    "msg": "matched!!"
}
ok: [localhost] => (item='1') => {
    "msg": "matched!!"
}

TASK [1-2] *********************************************************************
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
skipping: [localhost] => (item=False) 
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=False) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=True) => {
    "msg": "matched!!"
}
ok: [localhost] => (item=False) => {
    "msg": "matched!!"
}
ok: [localhost] => (item='True') => {
    "msg": "matched!!"
}
ok: [localhost] => (item='False') => {
    "msg": "matched!!"
}

TASK [debug 1-3] ***************************************************************
ok: [localhost] => {
    "msg": "matched!"
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


Part19 にむけて

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

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

[NetBox] Model のグラフ図を出力する

はじめに

NetBox には Device や Rack などの様々な管理単位のオブジェクトがあります。Django の Model で定義されいるようなので、その Model を図に出力できたら面白いなと思いました。

調べてみると、やり方がありました。

hideharaaws.hatenablog.com

django-extensions というものの機能の一部のようです。

django-extensions.readthedocs.io

早速試してみました。

  • 動作確認環境
    • NetBox 2.8.9 (docker-compose 版)

準備

以下のコンテナのうち、netbox-docker_nginx_1 に入ります。

[root@netbox ~]# docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                             NAMES
84b25dc18293        nginx:1.17-alpine               "nginx -c /etc/net..."   2 days ago          Up 2 days           80/tcp, 0.0.0.0:32778->8080/tcp   netbox-docker_nginx_1
db4e7e7ead02        netboxcommunity/netbox:latest   "/opt/netbox/docke..."   2 days ago          Up 2 days                                             netbox-docker_netbox_1
dde0687a981e        netboxcommunity/netbox:latest   "python3 /opt/netb..."   2 days ago          Up 2 days                                             netbox-docker_netbox-worker_1
cf6d29e883e9        redis:5-alpine                  "docker-entrypoint..."   2 days ago          Up 2 days           6379/tcp                          netbox-docker_redis-cache_1
dde441168569        redis:5-alpine                  "docker-entrypoint..."   2 days ago          Up 2 days           6379/tcp                          netbox-docker_redis_1
5581bc82df10        postgres:11-alpine              "docker-entrypoint..."   2 days ago          Up 2 days           5432/tcp                          netbox-docker_postgres_1
[root@netbox ~]# docker exec -it --user root netbox-docker_netbox_1 /bin/bash

graphviz と pydotplus、 django-extensions をインストールしておきます。(作業ログが飛んでしまい、少し手順があいまいです。すみません・・・。)

今回使った docker-compose 版の NetBox では /opt/netbox/netboxDjango 的なファイルやディレクトリがあります。

bash-5.0# ls -al
total 16
drwxr-xr-x    1 root     root           156 Oct  1 13:58 .
drwxr-xr-x    1 root     root            20 Aug  6 05:56 ..
drwxr-xr-x    1 root     root            36 Oct  1 13:24 circuits
drwxr-xr-x    1 root     root            50 Oct  1 13:24 dcim
drwxr-xr-x    1 root     root            85 Oct  1 13:24 extras
-rwxr-xr-x    1 root     root           305 Aug  6 05:48 generate_secret_key.py
drwxr-xr-x    1 root     root            36 Oct  1 13:24 ipam
-rwxr-xr-x    1 root     root           249 Aug  6 05:48 manage.py
drwxrwxr-x    4 root     root            56 Oct  1 13:49 media
drwxr-xr-x    1 root     root            44 Oct  1 13:24 netbox
drwxr-xr-x   13 root     root           234 Aug  6 05:48 project-static
drwxr-xr-x    2 root     root            25 Aug  6 05:48 reports
drwxr-xr-x    2 root     root            25 Aug  6 05:48 scripts
drwxr-xr-x    1 root     root            56 Oct  1 13:24 secrets
drwxrwxr-x   19 root     root          4096 Aug  7 13:52 static
drwxr-xr-x   15 root     root          4096 Aug  6 05:48 templates
drwxr-xr-x    1 root     root            36 Oct  1 13:24 tenancy
drwxr-xr-x    1 root     root            36 Oct  1 13:24 users
drwxr-xr-x    1 root     root            45 Oct  1 13:24 utilities
drwxr-xr-x    1 root     root            36 Oct  1 13:24 virtualization

このなかの netbox/setting.pyINSTALLED_APPSdjango_extensions を追記します。

INSTALLED_APPS = [
    'django.contrib.admin',
    // ...(略)...
    'django_extensions'   // 追記
]

図の生成

以下のコマンドで図を生成します。

bash-5.0# pwd
/opt/netbox/netbox
bash-5.0# python manage.py graph_models -a -o myapp_models.png
bash-5.0#

何もエラーが表示されなければ成功です。

今回はこんな図が生成されました。

github.com

おわりに

このまま読むのは厳しいので、できるのであれば出力を制限したいできるといいかもしれません。

おまけ

図ではなく --json とするとJSON になります。

bash-5.0# python manage.py graph_models -a --json
{"created_at": "2020-10-01 14:40", "cli_options": "-a --json", "disable_fields": false, "disable_abstract_fields": false, "use_subgraph": false, "graphs": [{"True": true, "False": false, "None": null, "name": "\"django.contrib.admin\"", "app_name": "django.contrib.admin", "cluster_app_name": "cluster_django_contrib_admin", "models": [{"app_name": "django_contrib_admin_models", "name": "LogEntry", "abstracts": [], "fields": [{"name": "id", "label": "id", "type": "AutoField", "blank": true, "abstract": false, "relation": false, "primary_key": true}, {"name": "content_type", "label": "content_type", "type": "ForeignKey (id)", "blank": true, "abstract": false, "relation": true, "primary_key": false}, {"name": "user", "label": "user", 
...(略)...

graph_models のヘルプ

bash-5.0# python manage.py graph_models --help
usage: manage.py graph_models [-h] [--pygraphviz] [--pydot] [--dot] [--json]
                              [--disable-fields] [--disable-abstract-fields]
                              [--group-models] [--all-applications]
                              [--output OUTPUTFILE] [--layout LAYOUT]
                              [--theme THEME] [--verbose-names]
                              [--language LANGUAGE]
                              [--exclude-columns EXCLUDE_COLUMNS]
                              [--exclude-models EXCLUDE_MODELS]
                              [--include-models INCLUDE_MODELS]
                              [--inheritance] [--no-inheritance]
                              [--hide-relations-from-fields]
                              [--disable-sort-fields] [--hide-edge-labels]
                              [--arrow-shape {box,crow,curve,icurve,diamond,dot,inv,none,normal,tee,vee}]
                              [--version] [-v {0,1,2,3}] [--settings SETTINGS]
                              [--pythonpath PYTHONPATH] [--traceback]
                              [--no-color] [--force-color] [--skip-checks]
                              [app_label [app_label ...]]

Creates a GraphViz dot file for the specified app names. You can pass multiple
app names and they will all be combined into a single model. Output is usually
directed to a dot file.

positional arguments:
  app_label

optional arguments:
  -h, --help            show this help message and exit
  --pygraphviz          Output graph data as image using PyGraphViz.
  --pydot               Output graph data as image using PyDot(Plus).
  --dot                 Output graph data as raw DOT (graph description
                        language) text data.
  --json                Output graph data as JSON
  --disable-fields, -d  Do not show the class member fields
  --disable-abstract-fields
                        Do not show the class member fields that were
                        inherited
  --group-models, -g    Group models together respective to their application
  --all-applications, -a
                        Automatically include all applications from
                        INSTALLED_APPS
  --output OUTPUTFILE, -o OUTPUTFILE
                        Render output file. Type of output dependend on file
                        extensions. Use png or jpg to render graph to image.
  --layout LAYOUT, -l LAYOUT
                        Layout to be used by GraphViz for visualization.
                        Layouts: circo dot fdp neato nop nop1 nop2 twopi
  --theme THEME, -t THEME
                        Theme to use. Supplied are 'original' and
                        'django2018'. You can create your own by creating dot
                        templates in
                        'django_extentions/graph_models/themename/' template
                        directory.
  --verbose-names, -n   Use verbose_name of models and fields
  --language LANGUAGE, -L LANGUAGE
                        Specify language used for verbose_name localization
  --exclude-columns EXCLUDE_COLUMNS, -x EXCLUDE_COLUMNS
                        Exclude specific column(s) from the graph. Can also
                        load exclude list from file.
  --exclude-models EXCLUDE_MODELS, -X EXCLUDE_MODELS
                        Exclude specific model(s) from the graph. Can also
                        load exclude list from file. Wildcards (*) are
                        allowed.
  --include-models INCLUDE_MODELS, -I INCLUDE_MODELS
                        Restrict the graph to specified models. Wildcards (*)
                        are allowed.
  --inheritance, -e     Include inheritance arrows (default)
  --no-inheritance, -E  Do not include inheritance arrows
  --hide-relations-from-fields, -R
                        Do not show relations as fields in the graph.
  --disable-sort-fields, -S
                        Do not sort fields
  --hide-edge-labels    Do not showrelations labels in the graph.
  --arrow-shape {box,crow,curve,icurve,diamond,dot,inv,none,normal,tee,vee}
                        Arrow shape to use for relations. Default is dot.
                        Available shapes: box, crow, curve, icurve, diamond,
                        dot, inv, none, normal, tee, vee.
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.
  --force-color         Force colorization of the command output.
  --skip-checks         Skip system checks.
bash-5.0#

[Ansible] 「つまずき Ansible 【Part17】ansible 2.10 まわり」ふりかえり

はじめに

2020/09/26 に、YouTube Live で「つまずき Ansible 【Part17】Ansible 2.10 まわり」という配信をしました。

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

tekunabe.connpass.com

今回は、2020/09/22 にリリースされたの Asnible 2.10.0(not ansible-base)をとりまく仕組み(collectionなど)について復習しました。

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

  • 環境
    • ansible-base 2.10.1
    • ansible 2.10.0

動画

youtu.be


■ やったこと

インストール

比較のため、ansible-base 2.10.1 (標準モジュールのみ)と、ansible 2.10.0 を別 vevn にそれぞれインストール。

ansible-base インストール

# pip install ansible-base
...(略)...
# ansible --version
ansible 2.10.1
...(略)...
# pip freeze
ansible-base==2.10.1    # ansible-base
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 インストール

$ pip install ansible-base
...(略)...
# ansible --version
ansible 2.10.1
...(略)...
# pip freeze
ansible==2.10.0         # ansible-base とは別パッケージ扱い
ansible-base==2.10.1    # ansible-base
bcrypt==3.2.0
cffi==1.14.3
cryptography==3.1.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.4
paramiko==2.7.2
pycparser==2.20
PyNaCl==1.4.0
pyparsing==2.4.7
PyYAML==5.3.1
six==1.15.0

アップデート

ansible 2.9 から 2.10 へのアップーデートは直接できない。

# pip install ansible -U
Collecting ansible
  Using cached https://files.pythonhosted.org/packages/4a/0b/44b586965bd51135d3915a02d1327fb392843630435cd41d6c89898c5f24/ansible-2.10.0.tar.gz
    Complete output from command python setup.py egg_info:
    
    
                ### ERROR ###
    
                Upgrading directly from ansible-2.9 or less to ansible-2.10 or greater with pip is
                known to cause problems.  Please uninstall the old version found at:
    
                /root/envs/a29/lib64/python3.6/site-packages/ansible/__init__.py
    
                and install the new version:
    
                    pip uninstall ansible
                    pip install ansible
    
                If you have a broken installation, perhaps because ansible-base was installed before
                ansible was upgraded, try this to resolve it:
    
                    pip install --force-reinstall ansible ansible-base
    
                If ansible is installed in a different location than you will be installing it now
                (for example, if the old version is installed by a system package manager to
                /usr/lib/python3.8/site-packages/ansible but you are installing the new version into
                ~/.local/lib/python3.8/site-packages/ansible with `pip install --user ansible`)
                or you want to install anyways and cleanup any breakage afterwards, then you may set
                the ANSIBLE_SKIP_CONFLICT_CHECK environment variable to ignore this check:
    
                    ANSIBLE_SKIP_CONFLICT_CHECK=1 pip install --user ansible
    
                ### END ERROR ###
    
    
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-p3lwq3xc/ansible/

一度アンストールしてから、再インストールする。

# pip uninstall ansible
# pip install ansible

参考: Ansible-2.10.0 has been released!

collection の操作

インストール

# ansible-galaxy collection install cisco.ios

確認

# ansible-galaxy collection list
(ログとり忘れ・・)

デフォルトでは上記のログにもあるように、~/.ansible/collections/ansible_collections/ 配下にインストールされる。(-p オプションや、COLLECTIONS_PATHS で変更可能)

pip install ansible でインストールした場合は lib/python3.6/site-packages/ansible_collections/ のように、別の Python パッケージような扱いで配置される。(COLLECTIONS_SCAN_SYS_PATHTrue ならここも探す)

collection 関連のデフォルト設定

# ansible-config dump | grep -i collection
COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH(default) = warning
COLLECTIONS_PATHS(default) = ['/root/.ansible/collections', '/usr/share/ansible/collections']
COLLECTIONS_SCAN_SYS_PATH(default) = True

先に、COLLECTIONS_PATHS を探して、なければ COLLECTIONS_SCAN_SYS_PATH に従う模様。

2.9 で書いた Playbook が動くか?

以下の Playbook をおためし。

---
- hosts: ios
  gather_facts: false
  
  tasks:
    - name: show ip route
      ios_command:      # ayashii
        commands:
          - show ip route
      register: resgister_show_ip_route

    - name: debug route
      debug:            # daijoubu
        msg: "{{ resgister_show_ip_route.stdout_lines }}"

ansible-base (collectionなし)

個別に collection をインストールしていない状態で実行。collection に移行した、ios_command がないというエラーに。

# ansible-playbook -i inventory.ini ios_show.yml 
ERROR! couldn't resolve module/action 'cisco.ios.ios_command'. This often indicates a misspelling, missing collection, or incorrect module path.

The error appears to be in '/root/general/vagrant/nwlab/stumble/ios_show.yml': line 6, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  tasks:
    - name: show ip route
      ^ here

ansible (collection)

pip install ansible して、自動でざまざまなな collection (cisco.ios含む)をインストールした環境でおためし。正常完了。

# ansible-playbook -i inventory.ini ios_show.yml 

PLAY [ios] ********************************************************************************

TASK [show ip route] **********************************************************************
ok: [rt01]
ok: [rt02]

TASK [debug route] ************************************************************************
ok: [rt01] => 
  msg:
  - - 'Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP'
    - '       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area '
    - '       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2'
    - '       E1 - OSPF external type 1, E2 - OSPF external type 2'
    - '       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2'
    - '       ia - IS-IS inter area, * - candidate default, U - per-user static route'
    - '       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP'
    - '       a - application route'
    - '       + - replicated route, % - next hop override, p - overrides from PfR'
    - ''
    - Gateway of last resort is not set
...(略)...

PLAY RECAP ********************************************************************************
rt01                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
rt02                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

なぜ動いたか?

前述のように、 pip install ansible でインストールすると、様々な collection (cisco.ios含む)がインストールされるため。

かつ、ios_command のような、装飾なしのモジュールの指定でも FQCN へ紐付けるリダイレクトデータがあるため。

ただ、これまでのバージョンアップ同様、ぎりぎり使えたオプションが使えなくなったり挙動が変わったり警告が表示されるようになったりする可能性はある。


ドキュメントはどうなった?

Module Index が Collection Index に

Collection Index — Ansible Documentation

標準のモジュール類は ansible.builtin を参照

カテゴリごとではなく、collection ごとに。標準のモジュール類は ansible.builtin

Ansible.Builtin — Ansible Documentation

モジュールの説明ページの旧リンクはリダイレクトされる

たとえば https://docs.ansible.com/ansible/latest/modules/ios_command_module.htmlhttps://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_command_module.html へリダイレクトされる。

URL に collection 名が入るのが特徴。

事前にいたご質問

質問1

質問2

何が同梱されて、何が同梱されないのかについて (ざっくり) 知りたいです! (debugはさすがに同梱されると思いますが、ipaddrフィルターとかどうなんだろう)

debugモジュール は標準同梱、ipaddr フィルターは ansible.netcommon collection 配下です。

すでに、ipaddr フィルターの説明ページでも、ansible.netcommon.ipaddr といった表記になっています。

pip install ansible でインストールした環境の場合、ansible-doc -l で表示したときに、装飾なしのモジュール名で表示されるようです。

# ansible-doc -l 
...(略)...
copy                                                                           Copy files to remote locations               
cron                                                                           Manage cron.d and crontab entries            
cyberark.pas.cyberark_account                                                  Module for CyberArk Account object creation, ...
cyberark.pas.cyberark_authentication                                           CyberArk Authentication using PAS Web Service...
cyberark.pas.cyberark_credential                                               Credential retrieval using AAM Central Creden...
cyberark.pas.cyberark_user                                                     CyberArk User Management using PAS Web Servic...
debconf                                                                        Configure a .deb package                     
debug                                                                          Print statements during execution            
dellemc.os10.base_xml_to_dict                                                  Operations for show command output ...(略)...

この方法いいですね!


Part18 にむけて

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

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

[Ansible] 内包表記のようにリストの各要素に処理して別の要素を生成する

はじめに

Ansible で、リストの中の各要素に対して一律でなにかの処理をして、別の要素を生成したいときがあります、

Python の内包表記でリストを生成するイメージです。

全く同じというわけではないのですが、map フィルターを利用すると近いことが少しできることを知りました。

少し説明しにくいので、 Python で書く場合と比較していくつかサンプルをご紹介します。

  • 動作確認環境
    • Ansible 2.9.9
    • Jinja2 2.11.2


各要素に文字列を結合する

Python

Python だとこのイメージ。

>>> ['Hello ' + i for i in ['funa', 'kingyo', 'same']]
['Hello funa', 'Hello kingyo', 'Hello same']

Ansible

Ansible だとこのような感じです。

もっとシンプルにする方法があると良いのですが、正規表現を使う方法しか思いつきませんでした。

    - name: naihou
      debug: 
        msg: "{{ ['funa', 'kingyo', 'same'] | map('regex_replace', '^(.+)$', 'Hello \\1') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        "Hello funa",
        "Hello kingyo",
        "Hello same"
    ]
}

なお、map フィルターに regex_replace を渡せることは以下の記事で知りました。ありがとうございます。 Jinja2のmapフィルタの中身に引数が必要なフィルタを指定する - Qiita


各要素を大文字にする

Python

Python だとこのイメージ。

>>> [i.upper() for i in ['funa', 'kingyo', 'same']]
['FUNA', 'KINGYO', 'SAME']

Ansible

Ansible だとこのような感じです。upper フィルターを併用します。

    - name: naihou
      debug: 
        msg: "{{ ['funa', 'kingyo', 'same'] | map('upper') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        "FUNA",
        "KINGYO",
        "SAME"
    ]
}


各要素を int にキャストする

Python

Python だとこのイメージ。

>>> [int(i) for i in ['111', '222', '333']]
[111, 222, 333]

Ansible

Ansible だとこのような感じです。

    - name: naihou
      debug: 
        msg: "{{ ['111', '222', '333'] | map('int') | list }}"
  • 結果
ok: [localhost] => {
    "msg": [
        111,
        222,
        333
    ]
}


おわりに

リスト loop で回しながら、処理して set_fact する方法でもできますが(こちらのほうが柔軟)、一つのタスクするまでもないときには、このように内包表記チックにやるのもいいと思います。

なお、Jinja2 として、内包表記そのものはサポートしていないとドキュメントに明記されています。

[Ansible] 複数の assert を一通り実施したあとで全結果を再 assert する

はじめに

Ansible には、値が期待したした条件を満たすかどうかをチェック assert モジュールがあります(標準モジュール)。

基本的には、assert 結果が fail だとその時点で Playbook の処理が中止されます。

一方で、1つ fail しただけで止めるのではなく、いくつかの assert を一通り実行しそれらの結果がすべて success なら success としたい場合もあるのではないでしょうか。

ignore_errors を併用すると実現できます。 この記事では簡単なサンプルでご紹介します。

(もっといい方法があるかも知れません)

  • 動作確認環境
    • ansible-base 2.10.1

サンプル Playbook

2つの 個別の assert があります。fail しても中止しないように、ignore_errors: true を指定します。(タスクごとに指定するのが面倒の場合は block で囲って指定) また、あとで、assert 結果を参照できるように、個別の assert には register で結果を受け取るようにします。

ここでは 1つめの assert が fail するようにしています。

---
- hosts: all
  gather_facts: false

  tasks:
    - name: assert 1   # 個別 assert
      assert:
        that: 1 == 0     # fail する
      ignore_errors: true
      register: res_assert1

    - name: assert 2  # 個別 assert
      assert:
        that: 2 == 2
      ignore_errors: true
      register: res_assert2

    - name: all assert
      assert:
        that:   # 複数指定で and 条件
          - not res_assert1.failed  # fail
          - not res_assert2.failed  # success

    - name: Finished
      debug:
        msg: Finished!


実行1(failパターン)

まずは、1つめの assert が fail するパターンです。

実行

$ ansible-playbook -i localhost, assert.yml

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

TASK [assert 1] *****************************************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "1 == 0",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}
...ignoring

TASK [assert 2] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [all assert] ***************************************************************************************************
fatal: [localhost]: FAILED! => {
    "assertion": "not res_assert1.failed",
    "changed": false,
    "evaluated_to": false,
    "msg": "Assertion failed"
}

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

中断せずに 2つめに assert しているのが分かります。また、2つの結果をまとめて assert するタスク「all assert」では fail していることが分かります。


実行2 (successパターン)

1つめの assert の条件を以下のようにして、success になるようにして再実行します。

   - name: assert 1   # 個別 assert
      assert:
        that: 1 == 1     # ★修正
      ignore_errors: true
      register: res_assert1

実行

$ ansible-playbook -i localhost, assert.yml

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

TASK [assert 1] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [assert 2] *****************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [all assert] ***************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [Finished] *****************************************************************************************************
ok: [localhost] => {
    "msg": "Finished!"
}

PLAY RECAP **********************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

個別の assert もまとめの assert も success となり、最後の debug タスクも実行されました。


応用

再 assert ではなく、結果を一覧で 表示する場合は、タスク all assert の代わりに以下のようにします。

    - name: assert results
      debug:
        msg:
          - "{{ res_assert1 }}"
          - "{{ res_assert2 }}"

実行結果(抜粋)

TASK [assert results] ***************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "assertion": "1 == 0",
            "changed": false,
            "evaluated_to": false,
            "failed": true,
            "msg": "Assertion failed"
        },
        {
            "changed": false,
            "failed": false,
            "msg": "All assertions passed"
        }
    ]
}


別解(loop の活用)

今回のように、比較する値が予めすべて取り揃っている状態から assert する場合は、loop で回すという方法もあります。 loop で assert すると、一通り assert しつつも、1つでも fail になるとそのタスクが fail となります。

---
- hosts: all
  gather_facts: false

  tasks:
    - name: assert
      assert:
        that: "{{ item }}" 
      loop:
        - 1 == 0    # fail
        - 2 == 2    # success

    - name: Finished
      debug:
        msg: Finished!

実行

$ ansible-playbook -i localhost, assert_loop.yml

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

TASK [assert] *******************************************************************************************************
failed: [localhost] (item=1 == 0) => {
    "ansible_loop_var": "item",
    "assertion": "1 == 0",
    "changed": false,
    "evaluated_to": false,
    "item": "1 == 0",
    "msg": "Assertion failed"
}
ok: [localhost] => (item=2 == 2) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "2 == 2",
    "msg": "All assertions passed"
}

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

[Ansible] ansible 2.10.0 で ini のインベントリファイルのansible_becomeのbool値解釈が修正された

はじめに

ini ファイル形式のインベントリファイルで指定された ansible_become 変数の値の解釈が ansible 2.10.0 で修正されました。

2.9 系との動作を比較して検証します。

前提となる ini 形式のインベントリファイル

[awx]
awx1

[awx:vars]
ansible_host=10.0.0.145
ansible_user=admin
ansible_become=hogehoge

become が true 扱いなら、リモートでも実行ユーザーが、root ユーザー、 false なら admin ユーザーとなります。

Ansible 2.9 系

whoami を実行する簡単なアドホックコマンドを実行します。

$ ansible --version
ansible 2.9.9
...(略)...
$ ansible -i inventory.ini awx1 -m command -a whoami
awx1 | CHANGED | rc=0 >>
admin

正常に完了し、admin と表示されました。つまり ansible_become=hogehogehogehogefalse 扱いです。

Ansible 2.10.1

先程同じアドホックコマンドを実行します。

$ ansible --version
ansible 2.10.1
...(略)...
$ ansible -i inventory.ini awx1 -m command -a whoami
awx1 | FAILED | rc=-1 >>
the field 'become' has an invalid value (hogehoge), and could not be converted to an bool.The error was: The value 'hogehoge' is not a valid boolean.  Valid booleans include: 0, 1, 'off', 'n', '1', 'y', 'true', '0', 'yes', 'no', 'f', 'on', 't', 'false'

hogehoge という値は正しい boolean 値でないというエラーになりました。有効な値は以下の通りとメッセージがあります。

0, 1, 'off', 'n', '1', 'y', 'true', '0', 'yes', 'no', 'f', 'on', 't', 'false'

試してみると、引き続き TrueFalse も(大文字はじまり)正常な boolean として扱われました。