203 Create DHCP jails with auto UUID and iocage_tags

Extending example 202 Create iocage templates. Clone DHCP jails..

Use case

Automatically generated UUID

Automatically generate the jails UUID names. At each iocage host, create three jails from the template ansible_client

swarms:
  sw_01:
    count: 3
    template: ansible_client

The module vbotka.freebsd.iocage doesn’t work with multiple names. Use ansible.builtin.command instead. If the UUID is generated automatically, such a task is not idempotent anyway. Example of the commands

iocage create --short --template ansible_client --count 3  bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_01 swarm=sw_01"
iocage start cd31c2a2 d254f889 158ef36d

The variable iocage_tags

In the inventory plugin, compose the variable iocage_tags

iocage_tags: dict(iocage_properties.notes | split | map('split', '='))

For example,

iocage_tags:
  vmm: iocage_01
  swarm: sw_01

Create groups from iocage_tags

keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Tree

shell> tree .
.
├── ansible.cfg
├── group_vars
│   └── all
│       └── iocage.yml
├── hosts
│   ├── 02_iocage.yml
│   ├── 04_iocage.yml
│   └── 99_constructed.yml
├── iocage.ini
└── pb-test.yml

Synopsis

  • At two managed nodes:

    • iocage_02

    • iocage_04

    In the playbook vbotka.freebsd.pb_iocage_ansible_clients.yml, use:

    • module vbotka.freebsd.iocage to:

      • create facts only

    • module ansible.builtin.command to:

      • create jails

      • start jails

      • optionally, stop and destroy the jails.

  • At all created jails:

    In the playbook pb-test.yml:

    • connect to the created jails

    • display basic configuration of the jails.

Requirements

Notes

See also

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       |
+------+----------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+

ansible.cfg

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

[connection]
pipelining = true

Inventory iocage.ini

iocage_02 ansible_host=10.1.0.73
iocage_04 ansible_host=10.1.0.29

[iocage]
iocage_02
iocage_04

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

group_vars

group_vars/all/iocage.yml
properties:
  bpf: 1
  dhcp: 1
  vnet: 1
  notes: "vmm={{ inventory_hostname }}"

swarms:
  sw_01:
    count: 3
    template: ansible_client

Playbook output - Create and start swarms

(env) > ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                         -i iocage.ini \
                         -t swarm \
                         -e swarm=true \
                         -e debug=true
PLAY [Create and start jails. Optionally stop and destroy jails.] **************

TASK [Get iocage facts] ********************************************************
ok: [iocage_04]
ok: [iocage_02]

TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_02] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02  swarm=sw_01"
ok: [iocage_04] => (item=sw_01) => 
    msg: |-
        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_04  swarm=sw_01"

TASK [Create swarms] ***********************************************************
changed: [iocage_04] => (item=sw_01)
changed: [iocage_02] => (item=sw_01)

TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_02] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible_client
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_02  swarm=sw_01
            delta: '0:00:04.232484'
            end: '2025-08-07 14:40:29.010408'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02  swarm=sw_01"
                    _uses_shell: false
                    argv: null
                    chdir: null
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-08-07 14:40:24.777924'
            stderr: ''
            stderr_lines: []
            stdout: |-
                4ed73386 successfully created!
                2695b74b successfully created!
                8b76865c successfully created!
            stdout_lines:
            - 4ed73386 successfully created!
            - 2695b74b successfully created!
            - 8b76865c successfully created!
        skipped: false
ok: [iocage_04] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - create
            - --short
            - --template
            - ansible_client
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - vnet=1
            - notes=vmm=iocage_04  swarm=sw_01
            delta: '0:00:00.913272'
            end: '2025-08-07 14:40:37.425573'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage create --short --template ansible_client --count 3 bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_04  swarm=sw_01"
                    _uses_shell: false
                    argv: null
                    chdir: null
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-08-07 14:40:36.512301'
            stderr: ''
            stderr_lines: []
            stdout: |-
                a36580a7 successfully created!
                9bd6522d successfully created!
                f30e0189 successfully created!
            stdout_lines:
            - a36580a7 successfully created!
            - 9bd6522d successfully created!
            - f30e0189 successfully created!
        skipped: false

TASK [Get iocage facts] ********************************************************
ok: [iocage_04]
ok: [iocage_02]

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_02] => (item=sw_01) => 
    msg: |-
        iocage start 4ed73386 2695b74b 8b76865c
ok: [iocage_04] => (item=sw_01) => 
    msg: |-
        iocage start 9bd6522d a36580a7 f30e0189

TASK [Start swarms] ************************************************************
changed: [iocage_04] => (item=sw_01)
changed: [iocage_02] => (item=sw_01)

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_02] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 4ed73386
            - 2695b74b
            - 8b76865c
            delta: '0:00:33.947911'
            end: '2025-08-07 14:41:23.188957'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage start 4ed73386 2695b74b 8b76865c
                    _uses_shell: false
                    argv: null
                    chdir: null
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-08-07 14:40:49.241046'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting 4ed73386
                  + Started OK
                  + Using devfs_ruleset: 1000 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.203/24
                * Starting 2695b74b
                  + Started OK
                  + Using devfs_ruleset: 1001 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.113/24
                * Starting 8b76865c
                  + Started OK
                  + Using devfs_ruleset: 1002 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.247/24
            stdout_lines:
            - '* Starting 4ed73386'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1000 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.203/24'
            - '* Starting 2695b74b'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1001 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.113/24'
            - '* Starting 8b76865c'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1002 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.247/24'
        skipped: false
ok: [iocage_04] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 9bd6522d
            - a36580a7
            - f30e0189
            delta: '0:00:22.904182'
            end: '2025-08-07 14:41:23.912600'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage start 9bd6522d a36580a7 f30e0189
                    _uses_shell: false
                    argv: null
                    chdir: null
                    creates: null
                    executable: null
                    expand_argument_vars: true
                    removes: null
                    stdin: null
                    stdin_add_newline: true
                    strip_empty_ends: true
            item:
                key: sw_01
                value:
                    count: 3
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-08-07 14:41:01.008418'
            stderr: |-
                No default gateway found for ipv6.
                No default gateway found for ipv6.
                No default gateway found for ipv6.
            stderr_lines:
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            - No default gateway found for ipv6.
            stdout: |-
                * Starting 9bd6522d
                  + Started OK
                  + Using devfs_ruleset: 1003 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.202/24
                * Starting a36580a7
                  + Started OK
                  + Using devfs_ruleset: 1004 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.182/24
                * Starting f30e0189
                  + Started OK
                  + Using devfs_ruleset: 1005 (iocage generated default)
                  + Configuring VNET OK
                  + Using IP options: vnet
                  + Starting services OK
                  + Executing poststart OK
                  + DHCP Address: 10.1.0.233/24
            stdout_lines:
            - '* Starting 9bd6522d'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1003 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.202/24'
            - '* Starting a36580a7'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1004 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.182/24'
            - '* Starting f30e0189'
            - '  + Started OK'
            - '  + Using devfs_ruleset: 1005 (iocage generated default)'
            - '  + Configuring VNET OK'
            - '  + Using IP options: vnet'
            - '  + Starting services OK'
            - '  + Executing poststart OK'
            - '  + DHCP Address: 10.1.0.233/24'
        skipped: false

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

Jails at iocage_02

[iocage_02]# iocage list -l
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+====================+=====+================+==========+
| 66  | 2695b74b | off  | up    | jail | 14.2-RELEASE-p3 | epair0b|10.1.0.113 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 65  | 4ed73386 | off  | up    | jail | 14.2-RELEASE-p3 | epair0b|10.1.0.203 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 67  | 8b76865c | off  | up    | jail | 14.2-RELEASE-p3 | epair0b|10.1.0.247 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+

Jails at iocage_04

[iocage_04]# iocage list -l
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+====================+=====+================+==========+
| 67  | 9bd6522d | off  | up    | jail | 14.3-RELEASE-p1 | epair0b|10.1.0.202 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 68  | a36580a7 | off  | up    | jail | 14.3-RELEASE-p1 | epair0b|10.1.0.182 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 69  | f30e0189 | off  | up    | jail | 14.3-RELEASE-p1 | epair0b|10.1.0.233 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+

Inventory hosts

hosts/02_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.73
user: admin
get_properties: True
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)
  iocage_tags: dict(iocage_properties.notes | split | map('split', '='))
hosts/04_iocage.yml
plugin: vbotka.freebsd.iocage
host: 10.1.0.29
user: admin
get_properties: True
hooks_results:
  - /var/db/dhclient-hook.address.epair0b
compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)
  iocage_tags: dict(iocage_properties.notes | split | map('split', '='))
hosts/99_constructed.yml
plugin: ansible.builtin.constructed
keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Note

The option get_properties: True is needed to get the dictionary iocage_properties

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |--@swarm_sw_01:
  |  |--2695b74b
  |  |--4ed73386
  |  |--8b76865c
  |  |--9bd6522d
  |  |--a36580a7
  |  |--f30e0189
  |--@vmm_iocage_02:
  |  |--2695b74b
  |  |--4ed73386
  |  |--8b76865c
  |--@vmm_iocage_04:
  |  |--9bd6522d
  |  |--a36580a7
  |  |--f30e0189

Playbook pb-test.yml

- name: Connect to the group test.
  hosts: swarm_sw_01
  gather_facts: false
  remote_user: admin

  vars:

    ansible_python_interpreter: auto_silent
    
  tasks:

    - ansible.builtin.command: hostname
      register: out_host
    
    - ansible.builtin.debug:
        msg: >
          ansible_host={{ ansible_host }}
          iocage_tags={{ iocage_tags | to_yaml }}

Playbook output - Display iocage_tags

(env) > ansible-playbook pb-test.yml -i hosts
PLAY [Connect to the group test.] **********************************************

TASK [ansible.builtin.command] *************************************************
changed: [9bd6522d]
changed: [a36580a7]
changed: [f30e0189]
changed: [4ed73386]
changed: [2695b74b]
changed: [8b76865c]

TASK [ansible.builtin.debug] ***************************************************
ok: [2695b74b] => 
    msg: |-
        ansible_host=10.1.0.113 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [4ed73386] => 
    msg: |-
        ansible_host=10.1.0.203 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [8b76865c] => 
    msg: |-
        ansible_host=10.1.0.247 iocage_tags={swarm: sw_01, vmm: iocage_02}
ok: [9bd6522d] => 
    msg: |-
        ansible_host=10.1.0.202 iocage_tags={swarm: sw_01, vmm: iocage_04}
ok: [a36580a7] => 
    msg: |-
        ansible_host=10.1.0.182 iocage_tags={swarm: sw_01, vmm: iocage_04}
ok: [f30e0189] => 
    msg: |-
        ansible_host=10.1.0.233 iocage_tags={swarm: sw_01, vmm: iocage_04}

PLAY RECAP *********************************************************************
2695b74b                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
4ed73386                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
8b76865c                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
9bd6522d                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
a36580a7                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
f30e0189                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Hint

The below command stops and destroys the jails in swarms

ansible-playbook vbotka.freebsd.pb_iocage_ansible_clients.yml \
                 -i iocage.ini \
                 -t swarm_destroy \
                 -e swarm_destroy=true