207 Create DHCP jails with auto UUID, iocage_tags, alias and class

Use case

At multiple iocage hosts, create and run VNET jails with a DHCP interface from the template ansible_client. Use the dictionary iocage_tags and option inventory_hostname_tag to create inventory aliases. Group the jails by iocage hosts, states, and classes. Declare the project in a single dictionary. The dictionary keys are jails’ aliases. For example,

project:
  logserv_1:
    class: [logserv]
    vmm: iocage_01
  http_1:
    class: [http, logclient]
    vmm: iocage_02
  db_1:
    class: [db, logclient]
    vmm: iocage_02
  http_2:
    class: [http, logclient]
    vmm: iocage_04
  db_2:
    class: [db, logclient]
    vmm: iocage_04

Tree

shell> tree .
.
├── ansible.cfg
├── group_vars
│   └── all
│       └── project.yml
├── hosts
│   ├── 01_iocage.yml
│   ├── 02_iocage.yml
│   ├── 04_iocage.yml
│   └── 99_constructed.yml
├── host_vars
│   ├── iocage_01
│   │   └── iocage.yml
│   ├── iocage_02
│   │   └── iocage.yml
│   └── iocage_04
│       └── iocage.yml
├── iocage.ini
├── pb-iocage-project-create.yml
├── pb-iocage-project-destroy.yml
└── pb-test-all.yml

Synopsis

  • At three managed nodes:

    • iocage_01

    • iocage_02

    • iocage_04

    In the playbook pb-iocage-project-create.yml, use:

  • At all jails:

    In the playbook pb-test-all.yml, display:

    • variables:

      • ansible_host

      • iocage_properties.host_hostuuid

      • iocage_tags

      • iocage_classes

    • inventory groups.

Requirements

Notes

Templates created in 202 Create iocage templates. Clone DHCP jails. are used in this example.

ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml
display_skipped_hosts = false

[connection]
pipelining = true

Inventory iocage.ini

iocage_01 ansible_host=10.1.0.18
iocage_02 ansible_host=10.1.0.73
iocage_04 ansible_host=10.1.0.29

[iocage]
iocage_01
iocage_02
iocage_04

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

Templates at iocage_01

[iocage_01]# iocage list -lt
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |      NAME      | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client | off  | down  | template | 13.5-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

Templates at iocage_02

[iocage_02]# iocage list -lt
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |      NAME      | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client | off  | down  | template | 14.2-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

Templates at iocage_04

[iocage_04]# iocage list -lt
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |      NAME      | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client | off  | down  | template | 14.3-RELEASE-p1 | DHCP (not running) | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

group_vars

group_vars/all/project.yml
# The project keys are jails aliases.
project:
  logserv_1:
    class: [logserv]
    vmm: iocage_01
  http_1:
    class: [http, logclient]
    vmm: iocage_02
  db_1:
    class: [db, logclient]
    vmm: iocage_02
  http_2:
    class: [http, logclient]
    vmm: iocage_04
  db_2:
    class: [db, logclient]
    vmm: iocage_04

# Declare vmm. Dictionary of iocage hosts.
vmm_groups: "{{ dict(project | dict2items | groupby('value.vmm')) }}"
vmm: "{{ dict(vmm_groups.keys() | zip(vmm_groups.values() | map('items2dict'))) }}"

# Declare class. Dictionary of classes.
class_list: "{{ project | dict2items }}"
class_keys: "{{ class_list | map(attribute='value.class') | flatten | unique | sort }}"
class: |
  {% filter from_yaml %}
  {% for k in class_keys %}
  {{ k }}: {{ class_list | selectattr('value.class', 'contains', k) | map(attribute='key') }}
  {% endfor %}
  {% endfilter %}

# Jail properties.
properties: "bpf=1 dhcp=1 vnet=1"

host_vars

host_vars/iocage_01/iocage.yml
# In production, put the password into the vault.
ansible_become_password: admin
host_vars/iocage_02/iocage.yml
# Add iocage_02 host specific varaiables here.
host_vars/iocage_04/iocage.yml
# Add iocage_04 host specific varaiables here.

Inventory hosts

hosts/01_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.18
user: admin
cache: true
cache_plugin: ansible.builtin.jsonfile
cache_connection: /var/tmp/inventory_cache
cache_timeout: 3600
cache_prefix: iocage_01_
get_properties: true
inventory_hostname_tag: alias
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
hosts/02_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.73
user: admin
cache: true
cache_plugin: ansible.builtin.jsonfile
cache_connection: /var/tmp/inventory_cache
cache_timeout: 3600
cache_prefix: iocage_02_
get_properties: true
inventory_hostname_tag: alias
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
hosts/04_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.29
user: admin
cache: true
cache_plugin: ansible.builtin.jsonfile
cache_connection: /var/tmp/inventory_cache
cache_timeout: 3600
cache_prefix: iocage_04_
get_properties: true
inventory_hostname_tag: alias
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
hosts/99_constructed.yml
plugin: ansible.builtin.constructed
compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)
  iocage_tags: dict(iocage_properties.notes | split
                                            | select('match', '^.*=.*$')
                                            | map('split', '='))
  iocage_classes: iocage_properties.notes | split
                                          | select('match', '^class=.*$') | first
                                          | split('=') | last
                                          | split(',')
groups:
  db: "'db' in iocage_classes"
  http: "'http' in iocage_classes"
  logclient: "'logclient' in iocage_classes"
  logserv: "'logserv' in iocage_classes"
keyed_groups:
  - prefix: state
    key: iocage_state
  - prefix: vmm
    key: iocage_tags.vmm

Note

This example is tested with present jails

[iocage_01]# iocage list -h
- test_1  down    13.5-RELEASE    -
[iocage_02]# iocage list -h
- test_2  down    14.2-RELEASE    -
[iocage_04]# iocage list -h
- test_4  down    14.3-RELEASE    -

Playbook pb-iocage-project-create.yml

- name: Create and start project jails.
  hosts: iocage

  vars:

    debug: false

  tasks:

    - name: Display vars.
      when: debug | bool
      vars:
        project_jails_present: "{{ project | intersect(groups.all) }}"
        project_jails_absent: "{{ project | difference(groups.all) }}"
      run_once: true
      debug:
        msg: |
          vmm:
            {{ vmm | to_yaml | indent(2) }}
          class:
            {{ class | to_yaml | indent(2) }}
          groups.all: {{ groups.all }}
          project_jails_present: {{ project_jails_present }}
          project_jails_absent: {{ project_jails_absent }}

    - name: Create and start jails.
      vars:
        iocage_jails_absent: "{{ vmm[inventory_hostname] | difference(groups.all) }}"
        iocage_jails_created: "{{ out.stdout_lines | map('split') | map('first') }}"
      block:

        - name: All jails already created. End host.
          when: iocage_jails_absent | length == 0
          meta: end_host

        - name: Create jails.
          register: out
          command: >
            iocage create
            --short
            --template ansible_client
            --count {{ iocage_jails_absent | length }}
            {{ properties }}

        - name: Set notes.
          vars:
            alias: "{{ iocage_jails_absent[idx] }}"
            iocage_tags: >-
              alias={{ alias }}
              class={{ vmm[inventory_hostname][alias]['class'] | join(',') }}
              vmm={{ inventory_hostname }}
          command: >
            iocage set
            notes="{{ iocage_tags }}"
            {{ item }}
          loop: "{{ iocage_jails_created }}"
          loop_control:
            index_var: idx
            label: "{{ alias }} {{ iocage_tags }}"

        - name: Start created jails.
          command: >
            iocage start
            {{ iocage_jails_created | join(' ') }}

Note

The inventory -i hosts provides the group of all created jails. The play can use it and create only the missing project jails. This makes the play idempotent despite the module ansible.builtin.command and iocage option --count being used.

Playbook output - Create and start project jails

(env) > ansible-playbook -i hosts -i iocage.ini pb-iocage-project-create.yml -e debug=true
PLAY [Create and start project jails.] *****************************************

TASK [Display vars.] ***********************************************************
ok: [iocage_01] => 
    msg: |-
        vmm:
          iocage_01:
            logserv_1:
              class: [logserv]
              vmm: iocage_01
          iocage_02:
            db_1:
              class: [db, logclient]
              vmm: iocage_02
            http_1:
              class: [http, logclient]
              vmm: iocage_02
          iocage_04:
            db_2:
              class: [db, logclient]
              vmm: iocage_04
            http_2:
              class: [http, logclient]
              vmm: iocage_04

        class:
          db: [db_1, db_2]
          http: [http_1, http_2]
          logclient: [http_1, db_1, http_2, db_2]
          logserv: [logserv_1]

        groups.all: ['test_1', 'test_2', 'test_4', 'iocage_01', 'iocage_02', 'iocage_04']
        project_jails_present: []
        project_jails_absent: ['http_2', 'db_1', 'db_2', 'logserv_1', 'http_1']

TASK [Create jails.] ***********************************************************
changed: [iocage_04]
changed: [iocage_01]
changed: [iocage_02]

TASK [Set notes.] **************************************************************
changed: [iocage_04] => (item=db_2 alias=db_2 class=db,logclient vmm=iocage_04)
changed: [iocage_04] => (item=http_2 alias=http_2 class=http,logclient vmm=iocage_04)
changed: [iocage_01] => (item=logserv_1 alias=logserv_1 class=logserv vmm=iocage_01)
changed: [iocage_02] => (item=db_1 alias=db_1 class=db,logclient vmm=iocage_02)
changed: [iocage_02] => (item=http_1 alias=http_1 class=http,logclient vmm=iocage_02)

TASK [Start created jails.] ****************************************************
changed: [iocage_01]
changed: [iocage_04]
changed: [iocage_02]

PLAY RECAP *********************************************************************
iocage_01                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iocage_02                  : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iocage_04                  : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Playbook pb-test-all.yml

- name: Display all groups.
  hosts: all

  tasks:

    - debug:
        msg: |
          ansible_host: {{ ansible_host | d('UNDEFINED') }}
          iocage_properties.host_hostuuid: {{ iocage_properties.host_hostuuid | d('UNDEFINED') }}
          iocage_classes: {{ iocage_classes | d([]) | to_yaml }}
          iocage_tags: {{ iocage_tags | d({}) | to_yaml }}

    - debug:
        msg: |
          {% for group in groups %}
          {{ group }}: {{ groups[group] }}
          {% endfor %}
      run_once: true

Note

  • The inventory configuration files hosts/*.yml enable cache.

  • Flush the cache. Otherwise, the jails created in the previous play won’t be included.

See also

ansible-playbook

Playbook output - Display all groups

(env) > ansible-playbook -i hosts pb-test-all.yml --flush-cache
PLAY [Display all groups.] *****************************************************

TASK [debug] *******************************************************************
ok: [test_1] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_1
        iocage_classes: []

        iocage_tags: {}
ok: [logserv_1] => 
    msg: |-
        ansible_host: 10.1.0.117
        iocage_properties.host_hostuuid: 1ef0fca1
        iocage_classes: [logserv]

        iocage_tags: {alias: logserv_1, class: logserv, vmm: iocage_01}
ok: [test_2] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_2
        iocage_classes: []

        iocage_tags: {}
ok: [db_1] => 
    msg: |-
        ansible_host: 10.1.0.240
        iocage_properties.host_hostuuid: 439932b3
        iocage_classes: [db, logclient]

        iocage_tags: {alias: db_1, class: 'db,logclient', vmm: iocage_02}
ok: [http_1] => 
    msg: |-
        ansible_host: 10.1.0.105
        iocage_properties.host_hostuuid: c2e8b384
        iocage_classes: [http, logclient]

        iocage_tags: {alias: http_1, class: 'http,logclient', vmm: iocage_02}
ok: [test_4] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_4
        iocage_classes: []

        iocage_tags: {}
ok: [db_2] => 
    msg: |-
        ansible_host: 10.1.0.211
        iocage_properties.host_hostuuid: 44b87670
        iocage_classes: [db, logclient]

        iocage_tags: {alias: db_2, class: 'db,logclient', vmm: iocage_04}
ok: [http_2] => 
    msg: |-
        ansible_host: 10.1.0.225
        iocage_properties.host_hostuuid: 454300ce
        iocage_classes: [http, logclient]

        iocage_tags: {alias: http_2, class: 'http,logclient', vmm: iocage_04}

TASK [debug] *******************************************************************
ok: [test_1] => 
    msg: |-
        all: ['test_1', 'logserv_1', 'test_2', 'db_1', 'http_1', 'test_4', 'db_2', 'http_2']
        ungrouped: []
        state_down: ['test_1', 'test_2', 'test_4']
        logserv: ['logserv_1']
        state_up: ['logserv_1', 'db_1', 'http_1', 'db_2', 'http_2']
        vmm_iocage_01: ['logserv_1']
        db: ['db_1', 'db_2']
        logclient: ['db_1', 'http_1', 'db_2', 'http_2']
        vmm_iocage_02: ['db_1', 'http_1']
        http: ['http_1', 'http_2']
        vmm_iocage_04: ['db_2', 'http_2']

PLAY RECAP *********************************************************************
db_1                       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
db_2                       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
http_1                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
http_2                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
logserv_1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_1                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_2                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_4                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Playbook pb-iocage-project-destroy.yml

Destroy the project if you don’t need it anymore.

- name: Stop and destroy jails.
  hosts: iocage

  vars:

    debug: false

  tasks:

    - name: Display vars.
      when: debug | bool
      vars:
        project_jails_present: "{{ project | intersect(groups.all) }}"
      run_once: true
      debug:
        msg: |
          vmm:
            {{ vmm | to_yaml | indent(2) }}
          class:
            {{ class | to_yaml | indent(2) }}
          groups.all: {{ groups.all }}
          project_jails_present: {{ project_jails_present }}

    - name: Stop and destroy project jails.
      vars:
        iocage_jails_present: "{{ vmm[inventory_hostname] | intersect(groups.all) }}"
        uuids: "{{ iocage_jails_present | map('extract', hostvars, ['iocage_properties', 'host_hostuuid']) }}"
      block:

        - name: All jails already destroyed. End host.
          when: iocage_jails_present | length == 0
          meta: end_host

        - name: Stop jails.
          command: >
            iocage stop
            {{ uuids | join(' ') }}

        - name: Destroy jails.
          command: >
            iocage destroy
            --force
            {{ uuids | join(' ') }}

Playbook output - Stop and destroy jails

(env) > ansible-playbook -i hosts -i iocage.ini pb-iocage-project-destroy.yml -e debug=true
PLAY [Stop and destroy jails.] *************************************************

TASK [Display vars.] ***********************************************************
ok: [iocage_01] => 
    msg: |-
        vmm:
          iocage_01:
            logserv_1:
              class: [logserv]
              vmm: iocage_01
          iocage_02:
            db_1:
              class: [db, logclient]
              vmm: iocage_02
            http_1:
              class: [http, logclient]
              vmm: iocage_02
          iocage_04:
            db_2:
              class: [db, logclient]
              vmm: iocage_04
            http_2:
              class: [http, logclient]
              vmm: iocage_04

        class:
          db: [db_1, db_2]
          http: [http_1, http_2]
          logclient: [http_1, db_1, http_2, db_2]
          logserv: [logserv_1]

        groups.all: ['logserv_1', 'test_1', 'db_1', 'http_1', 'test_2', 'db_2', 'http_2', 'test_4', 'iocage_01', 'iocage_02', 'iocage_04']
        project_jails_present: ['http_1', 'db_1', 'http_2', 'db_2', 'logserv_1']

TASK [Stop jails.] *************************************************************
changed: [iocage_01]
changed: [iocage_04]
changed: [iocage_02]

TASK [Destroy jails.] **********************************************************
changed: [iocage_04]
changed: [iocage_01]
changed: [iocage_02]

PLAY RECAP *********************************************************************
iocage_01                  : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iocage_02                  : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iocage_04                  : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Playbook output - Display all groups

(env) > ansible-playbook -i hosts pb-test-all.yml --flush-cache
PLAY [Display all groups.] *****************************************************

TASK [debug] *******************************************************************
ok: [test_1] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_1
        iocage_classes: []

        iocage_tags: {}
ok: [test_2] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_2
        iocage_classes: []

        iocage_tags: {}
ok: [test_4] => 
    msg: |-
        ansible_host: -
        iocage_properties.host_hostuuid: test_4
        iocage_classes: []

        iocage_tags: {}

TASK [debug] *******************************************************************
ok: [test_1] => 
    msg: |-
        all: ['test_1', 'test_2', 'test_4']
        ungrouped: []
        state_down: ['test_1', 'test_2', 'test_4']

PLAY RECAP *********************************************************************
test_1                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_2                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test_4                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0