206 Create DHCP and fixed IP jails from template

Extending example 203 Create DHCP jails from template. Auto UUID, iocage_tags.

Use case

In the inventory plugin vbotka.freebsd.iocage configuration file, use the option hooks_results to get the DHCP IP address. This option is common for all jails

hooks_results:
  - /var/db/dhclient-hook.address.epair0b

It will silently fail in jails with fixed IP addresses. If the item fails the result is a dash character ‘-’

iocage_hooks:
  - '-'

This use-case demonstrates the advantage of silently ignoring failed items over the potential explicit error handling. Let the option compose pick what is needed

compose:
  ansible_host: (iocage_hooks.0 == '-') | ternary(iocage_ip4, iocage_hooks.0)

Fixed IP

In this example, one jail with fixed IP will be created from the template ansible_client

clones:
  test_111:
    clone_from: ansible_client
    properties:
      ip4_addr: "em0|10.1.0.111/24"
      notes: "swarm=sw_01"

Automatically generated UUID

Two DHCP jails with generated UUID will be created from the template ansible_client

swarms:
  sw_01:
    count: 3
    template: ansible_client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

Note

The clone test_111 belongs to the swarm sw_01. Set count: 3 to create two more jails in the swarm sw_01.

The module vbotka.freebsd.iocage doesn’t work with multiple names. We will use ansible.builtin.command instead. Anyway, such a task is not idempotent if the UUID is generated automatically. Example of the commands

iocage create --short --template ansible_client --count 2  bpf=1 dhcp=1 vnet=1 notes="vmm=iocage_02 swarm=sw_01"
iocage start cd31c2a2 d254f889

The variable iocage_tags

The inventory plugin composes the variable iocage_tags

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

For example,

iocage_tags:
  vmm: iocage_02
  swarm: sw_01

This option is used to create groups from iocage_tags

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

Tree

shell> tree .
.
├── ansible.cfg
├── hosts
│   └── 02_iocage.yml
├── host_vars
│   └── iocage_02
│       └── iocage.yml
├── iocage-hosts.ini
├── pb-iocage-ansible-clients -> ../../../../playbooks/pb-iocage-ansible-clients
├── pb-iocage-ansible-clients.yml -> ../../../../playbooks/pb-iocage-ansible-clients.yml
└── pb-test-01.yml

Synopsis

  • On one iocage hosts:

    • iocage_02

    In the playbook pb-iocage-ansible-clients.yml, use the module:

    • vbotka.freebsd.iocage to:

      • create one jail with fixed IP

      • start the jail

    • ansible.builtin.command to:

      • create two DHCP jails with generated UUID

      • start the jails

  • On all created jails:

    In the playbook pb-test-01.yml:

    • connect created jails

    • display basic configuration of the jails.

Requirements

Notes

See also

List 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.1-RELEASE-p6 | em0|10.1.0.199/24 | -   | -        | no       |
+------+----------------+------+-------+----------+-----------------+-------------------+-----+----------+----------+

Configuration ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml

[connection]
pipelining = true

host_vars/iocage_02/iocage.yml

ansible_python_interpreter: /usr/local/bin/python3.11

freebsd_iocage_runner_env:
  CRYPTOGRAPHY_OPENSSL_NO_LEGACY: 1

properties:
  notes: "vmm={{ inventory_hostname }}"

clones:
  test_111:
    clone_from: ansible_client
    properties:
      ip4_addr: "em0|10.1.0.111/24"
      notes: "swarm=sw_01"
start: [test_111]

swarms:
  sw_01:
    count: 3
    template: ansible_client
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1

Inventory iocage-hosts.ini

iocage_01 ansible_host=10.1.0.18
iocage_02 ansible_host=10.1.0.73
iocage_03 ansible_host=10.1.0.17

[iocage]
iocage_01
iocage_02
iocage_03

[iocage:vars]
ansible_user=admin
ansible_become=true

Playbook pb-iocage-ansible-clients.yml

- name: Create and start jails. Optionally stop and destroy jails.
  hosts: iocage
  environment: "{{ freebsd_iocage_runner_env | d({}) }}"

  vars:

    debug: false
    debug2: false
    dry_run: false

    swarm: false
    swarm_destroy: false
    clone: false
    clone_destroy: false

    _tags: "{{ dict(iocage_jails.keys()
                    | zip(iocage_jails.values()
                          | map(attribute='properties.notes')
                          | map('split')
                          | map('map', 'split', '=')
                          | map('community.general.dict'))) }}"
    _started: "{{ iocage_jails | dict2items
                               | selectattr('value.state', 'eq', 'up')
                               | map(attribute='key') }}"

  tasks:

    - name: Create and start swarms
      when: swarm | bool
      tags: swarm
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/swarm.yml

    - name: Stop and destroy swarms
      when: swarm_destroy | bool
      tags: swarm_destroy
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/swarm_destroy.yml

    - name: Create and start clones
      when: clone | bool
      tags: clone
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/clone.yml

    - name: Stop and destroy clones
      when: clone_destroy | bool
      tags: clone_destroy
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/clone_destroy.yml

    - name: Display iocage_jails
      when: debug | bool or debug2 | bool
      tags: list
      ansible.builtin.import_tasks: pb-iocage-ansible-clients/list.yml

Create and start clones

(env) > ansible-playbook pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                                       -l iocage_02 \
						       -t clone \
						       -e clone=true

PLAY [Create and start jails. Optionally stop and destroy jails.] ******************************

TASK [Create clones from template] *************************************************************
changed: [iocage_02] => (item=test_111 ansible_client)

TASK [Debug clones from template debug2=False] *************************************************
skipping: [iocage_02]

TASK [Start clones] ****************************************************************************
changed: [iocage_02] => (item=test_111)

TASK [Debug start clones debug2=False] *********************************************************
skipping: [iocage_02]

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

Create and start swarms

(env) > ansible-playbook pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                                       -l iocage_02 \
						       -t swarm \
						       -e swarm=true \
						       -e debug=true

PLAY [Create and start jails. Optionally stop and destroy jails.] ******************************

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

TASK [Debug iocage_jails] **********************************************************************
skipping: [iocage_02]

TASK [Debug _tags] *****************************************************************************
ok: [iocage_02] => 
    _tags:
        test_111:
            swarm: sw_01
            vmm: iocage_02

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

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

TASK [Debug create swarms] *********************************************************************
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
            - '2'
            - notes=vmm=iocage_02  swarm=sw_01
            - bpf=1
            - dhcp=1
            - vnet=1
            delta: '0:00:03.748798'
            end: '2025-02-09 15:09:33.960064'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage create --short --template ansible_client --count 2 notes="vmm=iocage_02  swarm=sw_01" bpf=1 dhcp=1 vnet=1
                    _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
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-02-09 15:09:30.211266'
            stderr: ''
            stderr_lines: []
            stdout: |-
                7509aed0 successfully created!
                e3c34e4f successfully created!
            stdout_lines:
            - 7509aed0 successfully created!
            - e3c34e4f successfully created!
        skipped: false

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

TASK [Debug iocage_jails] **********************************************************************
skipping: [iocage_02]

TASK [Debug cmd_start] *************************************************************************
ok: [iocage_02] => (item=sw_01) => 
    msg: |-
        iocage start 7509aed0 e3c34e4f

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

TASK [Debug start swarms] **********************************************************************
ok: [iocage_02] => 
    out:
        changed: true
        msg: All items completed
        results:
        -   ansible_loop_var: item
            changed: true
            cmd:
            - iocage
            - start
            - 7509aed0
            - e3c34e4f
            delta: '0:00:25.409272'
            end: '2025-02-09 15:10:20.381974'
            failed: false
            invocation:
                module_args:
                    _raw_params: |-
                        iocage start 7509aed0 e3c34e4f
                    _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
                    properties:
                        bpf: 1
                        dhcp: 1
                        vnet: 1
                    template: ansible_client
            msg: ''
            rc: 0
            start: '2025-02-09 15:09:54.972702'
            stderr: |-
                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.
            stdout: |-
                * Starting 7509aed0
                  + 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.156/24
                * Starting e3c34e4f
                  + 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.243/24
            stdout_lines:
            - '* Starting 7509aed0'
            - '  + 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.156/24'
            - '* Starting e3c34e4f'
            - '  + 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.243/24'
        skipped: false

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

List jails at iocage_02

[iocage_02]# iocage list -l
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| JID |   NAME   | BOOT | STATE | TYPE |     RELEASE     |        IP4         | IP6 |    TEMPLATE    | BASEJAIL |
+=====+==========+======+=======+======+=================+====================+=====+================+==========+
| 88  | 7509aed0 | off  | up    | jail | 14.1-RELEASE-p6 | epair0b|10.1.0.156 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 89  | e3c34e4f | off  | up    | jail | 14.1-RELEASE-p6 | epair0b|10.1.0.243 | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+
| 87  | test_111 | off  | up    | jail | 14.1-RELEASE-p6 | em0|10.1.0.111/24  | -   | ansible_client | no       |
+-----+----------+------+-------+------+-----------------+--------------------+-----+----------------+----------+

Inventory hosts/02_iocage.yml

plugin: vbotka.freebsd.iocage
host: 10.1.0.73
user: admin
env:
  CRYPTOGRAPHY_OPENSSL_NO_LEGACY: 1
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', '='))
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:
  |  |--7509aed0
  |  |--e3c34e4f
  |  |--test_111
  |--@vmm_iocage_02:
  |  |--7509aed0
  |  |--e3c34e4f
  |  |--test_111

Playbook pb-test-01.yml

- hosts: swarm_sw_01
  remote_user: admin

  vars:

    ansible_python_interpreter: auto_silent
    
  tasks:

    - ansible.builtin.command: hostname
      register: out

    - ansible.builtin.debug:
        msg: |
          out.stdout: {{ out.stdout }}
          ansible_host: {{ ansible_host }}
          iocage_hooks: {{ iocage_hooks }}
          iocage_tags:
            {{ iocage_tags | to_nice_yaml(2) | indent(2) }}

Playbook output

(env) > ansible-playbook pb-test-01.yml -i hosts

PLAY [swarm_sw_01] ****************************************************************************

TASK [ansible.builtin.command] ****************************************************************
changed: [test_111]
changed: [e3c34e4f]
changed: [7509aed0]

TASK [ansible.builtin.debug] ******************************************************************
ok: [7509aed0] => 
    msg: |-
        out.stdout: 7509aed0
        ansible_host: 10.1.0.156
        iocage_hooks: ['10.1.0.156']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_02
ok: [e3c34e4f] => 
    msg: |-
        out.stdout: e3c34e4f
        ansible_host: 10.1.0.243
        iocage_hooks: ['10.1.0.243']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_02
ok: [test_111] => 
    msg: |-
        out.stdout: test-111
        ansible_host: 10.1.0.111
        iocage_hooks: ['-']
        iocage_tags:
          swarm: sw_01
          vmm: iocage_02

PLAY RECAP ************************************************************************************
7509aed0: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
e3c34e4f: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test_111: 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 pb-iocage-ansible-clients.yml -i iocage-hosts.ini \
                                               -l iocage_02 \
                                               -t swarm_destroy \
                                               -e swarm_destroy=true