443 Plugin ansible-zero

Use case

Clone multiple jails from the iocage plugin ansible-zero. Use the connection plugin vbotka.freebsd.jailexec to connect the jails.

Tree

shell> tree .
.
├── ansible.cfg
├── hosts
│   ├── 05_iocage.yml
│   └── 99_constructed.yml
├── host_vars
│   └── iocage_05.yml
├── iocage.ini
└── pb-test.yml

Synopsis

Requirements

Notes

See also

ansible.cfg

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

[connection]
pipelining = true

Inventory iocage.ini

iocage_05

[iocage]
iocage_05

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

host_vars

host_vars/iocage_05.yml
properties:
  bpf: 1
  dhcp: 1
  vnet: 1
  notes: "vmm={{ inventory_hostname }}"
  type: jail

swarms:
  sw_01:
    count: 3
    plugin: ansible-zero

plugins:
  ansible-zero:
    git: https://github.com/vbotka/iocage-plugins-devel
    branch: main
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1
      boot: 0

Note

By default, the jails cloned from the plugins inherit the iocage property type pluginv2. Change it to jail.

Playbook output - Fetch plugins

(env) > ansible-playbook vbotka.freebsd.pb_iocage_plugins.yml \
                         -i iocage.ini \
                         -t swarm_plugins \
                         -e debug=true
PLAY [Fetch and stop iocage plugins.] ******************************************

TASK [One tag is required.] ****************************************************
ok: [iocage_05]

TASK [Test the tag is known.] **************************************************
ok: [iocage_05]

TASK [Get already fetched plugins.] ********************************************
ok: [iocage_05]

TASK [Debug fetched plugins debug=true] ****************************************
ok: [iocage_05] => 
    msg: |-
        iocage_plugins:
          ansible-zero:
              boot: 'off'
              doc_url: '-'
              ip4: '-'
              ip4_dict:
                  ip4: []
                  msg: DHCP (not running)
              ip6: '-'
              jid: None
              portal: '-'
              release: 15.0-RELEASE
              state: down
              template: '-'
              type: pluginv2

        plugins:
          ansible-zero:
              branch: main
              git: https://github.com/vbotka/iocage-plugins-devel
              properties:
                  boot: 0
                  bpf: 1
                  dhcp: 1
                  vnet: 1

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

Note

The “Testing ansible-zero’s DNSSEC response to pkg.FreeBSD.org” by iocage fetch may take some time.

Plugins at iocage_05

[iocage_05]# iocage list -P
+------+--------------+------+-------+----------+--------------+--------------------+-----+----------+--------+---------+
| JID  |     NAME     | BOOT | STATE |   TYPE   |   RELEASE    |        IP4         | IP6 | TEMPLATE | PORTAL | DOC_URL |
+======+==============+======+=======+==========+==============+====================+=====+==========+========+=========+
| None | ansible-zero | off  | down  | pluginv2 | 15.0-RELEASE | DHCP (not running) | -   | -        | -      | -       |
+------+--------------+------+-------+----------+--------------+--------------------+-----+----------+--------+---------+

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_05]

TASK [Reject jails with empty notes] *******************************************
ok: [iocage_05]

TASK [Debug cmd_create debug=true] *********************************************
ok: [iocage_05] => (item=sw_01) => 
    msg: |-
        iocage clone ansible-zero --newmac --count 3 bpf=1 dhcp=1 notes="vmm=iocage_05 swarm=sw_01" type=jail vnet=1

TASK [Create swarms] ***********************************************************
ok: [iocage_05] => (item=sw_01)

TASK [Debug create swarms debug=true] ******************************************
ok: [iocage_05] => 
    out:
        changed: false
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: false
            cmd:
            - iocage
            - clone
            - ansible-zero
            - --newmac
            - --count
            - '3'
            - bpf=1
            - dhcp=1
            - notes=vmm=iocage_05 swarm=sw_01
            - type=jail
            - vnet=1
            delta: '0:00:00.945023'
            end: '2026-05-13 11:20:33.799872'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage clone ansible-zero --newmac --count 3 bpf=1 dhcp=1 notes="vmm=iocage_05 swarm=sw_01" type=jail vnet=1
                    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
                    plugin: ansible-zero
            msg: ''
            rc: 0
            start: '2026-05-13 11:20:32.854849'
            stderr: ''
            stderr_lines: []
            stdout: |-
                53fa80de-b537-462f-9925-a3b17a122271 successfully cloned!
                67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae successfully cloned!
                a87e3dfe-ab10-46b9-9921-1f1adc2011fe successfully cloned!
            stdout_lines:
            - 53fa80de-b537-462f-9925-a3b17a122271 successfully cloned!
            - 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae successfully cloned!
            - a87e3dfe-ab10-46b9-9921-1f1adc2011fe successfully cloned!
        skipped: false

TASK [Get iocage facts] ********************************************************
ok: [iocage_05]

TASK [Reject jails with empty notes] *******************************************
ok: [iocage_05]

TASK [Debug cmd_start debug=true] **********************************************
ok: [iocage_05] => (item=sw_01) => 
    msg: |-
        iocage start 53fa80de-b537-462f-9925-a3b17a122271 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae a87e3dfe-ab10-46b9-9921-1f1adc2011fe

TASK [Start swarms] ************************************************************
ok: [iocage_05] => (item=sw_01)

TASK [Debug start swarms debug=true] *******************************************
ok: [iocage_05] => 
    out:
        changed: false
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: false
            cmd:
            - iocage
            - start
            - 53fa80de-b537-462f-9925-a3b17a122271
            - 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae
            - a87e3dfe-ab10-46b9-9921-1f1adc2011fe
            delta: '0:00:07.520716'
            end: '2026-05-13 11:20:48.315514'
            failed: false
            invocation:
                module_args:
                    _raw_params: null
                    _uses_shell: false
                    argv: null
                    chdir: null
                    cmd: |-
                        iocage start 53fa80de-b537-462f-9925-a3b17a122271 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae a87e3dfe-ab10-46b9-9921-1f1adc2011fe
                    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
                    plugin: ansible-zero
            msg: ''
            rc: 0
            start: '2026-05-13 11:20:40.794798'
            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 53fa80de-b537-462f-9925-a3b17a122271
                  + 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.10.99.180/24
                * Starting 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae
                  + 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.10.99.181/24
                * Starting a87e3dfe-ab10-46b9-9921-1f1adc2011fe
                  + 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.10.99.182/24
            stdout_lines:
            - '* Starting 53fa80de-b537-462f-9925-a3b17a122271'
            - '  + 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.10.99.180/24'
            - '* Starting 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae'
            - '  + 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.10.99.181/24'
            - '* Starting a87e3dfe-ab10-46b9-9921-1f1adc2011fe'
            - '  + 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.10.99.182/24'
        skipped: false

PLAY RECAP *********************************************************************
iocage_05                  : ok=10   changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

Jails at iocage_05

[iocage_05]# iocage list -l
+------+--------------------------------------+------+-------+----------+--------------+----------------------+-----+--------------+----------+
| JID  |                 NAME                 | BOOT | STATE |   TYPE   |   RELEASE    |         IP4          | IP6 |   TEMPLATE   | BASEJAIL |
+======+======================================+======+=======+==========+==============+======================+=====+==============+==========+
| 39   | 53fa80de-b537-462f-9925-a3b17a122271 | off  | up    | jail     | 15.0-RELEASE | epair0b|10.10.99.180 | -   | ansible-zero | yes      |
+------+--------------------------------------+------+-------+----------+--------------+----------------------+-----+--------------+----------+
| 40   | 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae | off  | up    | jail     | 15.0-RELEASE | epair0b|10.10.99.181 | -   | ansible-zero | yes      |
+------+--------------------------------------+------+-------+----------+--------------+----------------------+-----+--------------+----------+
| 41   | a87e3dfe-ab10-46b9-9921-1f1adc2011fe | off  | up    | jail     | 15.0-RELEASE | epair0b|10.10.99.182 | -   | ansible-zero | yes      |
+------+--------------------------------------+------+-------+----------+--------------+----------------------+-----+--------------+----------+
| None | ansible-zero                         | off  | down  | pluginv2 | 15.0-RELEASE | DHCP (not running)   | -   | -            | yes      |
+------+--------------------------------------+------+-------+----------+--------------+----------------------+-----+--------------+----------+

Inventory hosts

hosts/05_iocage.yml
plugin: vbotka.freebsd.iocage
host: iocage_05
user: admin
sudo: true
get_properties: true
compose:
  ansible_connection: "'vbotka.freebsd.jailexec'"
  ansible_jail_host: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)')).vmm | d('none')
  ansible_jail_name: iocage_jid
  ansible_jail_privilege_escalation: "'sudo'"
  ansible_user: "'admin'"
  ansible_python_interpreter: "'auto_silent'"
  iocage_tags: dict(iocage_properties.notes | regex_findall('(\w+)=([\w\-]+)'))

Note

iocage name doesn’t work with ansible_jail_name. iocage jid must be used instead.

hosts/99_constructed.yml
plugin: ansible.builtin.constructed
keyed_groups:
  - prefix: swarm
    key: iocage_tags.swarm
  - prefix: vmm
    key: iocage_tags.vmm

Display inventory

(env) > ansible-inventory -i hosts --graph
@all:
  |--@ungrouped:
  |  |--ansible-zero
  |--@swarm_sw_01:
  |  |--53fa80de-b537-462f-9925-a3b17a122271
  |  |--67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae
  |  |--a87e3dfe-ab10-46b9-9921-1f1adc2011fe
  |--@vmm_iocage_05:
  |  |--53fa80de-b537-462f-9925-a3b17a122271
  |  |--67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae
  |  |--a87e3dfe-ab10-46b9-9921-1f1adc2011fe

Playbook pb-test.yml

- name: Test the connection plugin jailexec.
  hosts: swarm_sw_01
  gather_facts: false
    
  tasks:

    - name: Get uname  # noqa: no-changed-when
      ansible.builtin.command: uname -a
      register: out

    - name: Debug
      ansible.builtin.debug:
        msg: |
          ansible_connection: {{ ansible_connection }}
          ansible_host: {{ ansible_host }}
          ansible_user: {{ ansible_user }}

          ansible_jail_host: {{ ansible_jail_host }}
          ansible_jail_privilege_escalation: {{ ansible_jail_privilege_escalation }}

          iocage_ip4: {{ iocage_ip4 }}
          iocage_tags: {{ iocage_tags }}

          uname:
            {{ out.stdout }}

Playbook output - Test connection plugin jailexec

(env) > ansible-playbook pb-test.yml -i hosts
PLAY [Test the connection plugin jailexec.] ************************************

TASK [Get uname] ***************************************************************
changed: [53fa80de-b537-462f-9925-a3b17a122271]
changed: [67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae]
changed: [a87e3dfe-ab10-46b9-9921-1f1adc2011fe]

TASK [Debug] *******************************************************************
ok: [53fa80de-b537-462f-9925-a3b17a122271] => 
    msg: |-
        ansible_connection: vbotka.freebsd.jailexec
        ansible_host: 53fa80de-b537-462f-9925-a3b17a122271
        ansible_user: admin

        ansible_jail_host: iocage_05
        ansible_jail_privilege_escalation: sudo

        iocage_ip4: 10.10.99.180
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}

        uname:
          FreeBSD 53fa80de-b537-462f-9925-a3b17a122271 15.0-RELEASE FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC amd64
ok: [67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae] => 
    msg: |-
        ansible_connection: vbotka.freebsd.jailexec
        ansible_host: 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae
        ansible_user: admin

        ansible_jail_host: iocage_05
        ansible_jail_privilege_escalation: sudo

        iocage_ip4: 10.10.99.181
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}

        uname:
          FreeBSD 67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae 15.0-RELEASE FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC amd64
ok: [a87e3dfe-ab10-46b9-9921-1f1adc2011fe] => 
    msg: |-
        ansible_connection: vbotka.freebsd.jailexec
        ansible_host: a87e3dfe-ab10-46b9-9921-1f1adc2011fe
        ansible_user: admin

        ansible_jail_host: iocage_05
        ansible_jail_privilege_escalation: sudo

        iocage_ip4: 10.10.99.182
        iocage_tags: {'vmm': 'iocage_05', 'swarm': 'sw_01'}

        uname:
          FreeBSD a87e3dfe-ab10-46b9-9921-1f1adc2011fe 15.0-RELEASE FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC amd64

PLAY RECAP *********************************************************************
53fa80de-b537-462f-9925-a3b17a122271 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
67f95bed-b2b8-41e3-a7b1-d7476cb5a5ae : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
a87e3dfe-ab10-46b9-9921-1f1adc2011fe : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Hint

The below play 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