From d5cf6f656e9b8c1400df7d170215d2e13242416a Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 28 Jun 2026 13:42:41 -0400 Subject: [PATCH] refactor(proxmox_lxc_provision): centralize module_defaults so tasks_from works without setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the community.proxmox.proxmox / proxmox_vm_info module_defaults were defined inline on the outer block in main.yml. Invoking individual task files via 'tasks_from: stop' (or delete/convert/etc.) bypassed main.yml, leaving the API parameters unset and producing 'missing required arguments: api_host, api_user' errors. The README worked around this by telling callers to repeat the module_defaults block at the play level — easy to forget, and duplicated config. Extract the defaults dict into _proxmox_module_defaults in defaults/main.yml (using a YAML anchor to share between the two modules), and wrap every task file that calls a Proxmox module in a block that references it. Callers only need the proxmox_* connection vars in scope (typically group_vars/all/) — both 'roles:' and 'tasks_from:' invocations now configure the API consistently. Files wrapped: check-exists, create, clone, update, start, stop, delete, convert. wait/post-clone/edit-config don't call Proxmox modules and are unchanged. main.yml's now-redundant outer module_defaults is removed. README updated to drop the 'Using Standalone Tasks' workaround boilerplate. --- roles/proxmox_lxc_provision/README.md | 28 +---- roles/proxmox_lxc_provision/defaults/main.yml | 13 +++ .../tasks/check-exists.yml | 27 ++--- roles/proxmox_lxc_provision/tasks/clone.yml | 53 +++++----- roles/proxmox_lxc_provision/tasks/convert.yml | 13 ++- roles/proxmox_lxc_provision/tasks/create.yml | 49 +++++---- roles/proxmox_lxc_provision/tasks/delete.yml | 23 ++-- roles/proxmox_lxc_provision/tasks/main.yml | 100 ++++++++---------- roles/proxmox_lxc_provision/tasks/start.yml | 21 ++-- roles/proxmox_lxc_provision/tasks/stop.yml | 23 ++-- roles/proxmox_lxc_provision/tasks/update.yml | 43 ++++---- 11 files changed, 200 insertions(+), 193 deletions(-) diff --git a/roles/proxmox_lxc_provision/README.md b/roles/proxmox_lxc_provision/README.md index 194d91d..0f080a4 100755 --- a/roles/proxmox_lxc_provision/README.md +++ b/roles/proxmox_lxc_provision/README.md @@ -144,26 +144,17 @@ The role includes idempotency checking. If a container with the specified `lxc_v ### Using Standalone Tasks -When using individual task files via `tasks_from`, you must set `module_defaults` at the play level since the tasks bypass the role's main entry point: +Individual task files (`stop`, `start`, `delete`, `convert`, etc.) can be invoked via `tasks_from` directly — each task file wraps its work in a block with the role's shared `module_defaults`, so the Proxmox API connection is configured automatically as long as the `proxmox_*` connection variables are in scope (typically from `group_vars/all/`). ```yaml - name: Convert container to a template hosts: localhost - module_defaults: - community.proxmox.proxmox: - api_host: "{{ proxmox_api_host }}" - api_port: "{{ proxmox_api_port }}" - api_user: "{{ proxmox_api_user }}" - api_token_id: "{{ proxmox_api_token_id }}" - api_token_secret: "{{ proxmox_api_token_secret }}" - validate_certs: "{{ proxmox_api_validate_certs }}" - node: "{{ proxmox_node }}" - vars: - lxc_hostname: "{{ lxc_hostname }}" tasks: - include_role: name: proxmox_lxc_provision tasks_from: convert + vars: + lxc_hostname: my-container ``` ### Creating an LXC Container and Converting it to a Template @@ -185,19 +176,10 @@ When using individual task files via `tasks_from`, you must set `module_defaults - name: Convert the created container to a template hosts: localhost - module_defaults: - community.proxmox.proxmox: - api_host: "{{ proxmox_api_host }}" - api_port: "{{ proxmox_api_port }}" - api_user: "{{ proxmox_api_user }}" - api_token_id: "{{ proxmox_api_token_id }}" - api_token_secret: "{{ proxmox_api_token_secret }}" - validate_certs: "{{ proxmox_api_validate_certs }}" - node: "{{ proxmox_node }}" - vars: - lxc_hostname: "{{ lxc_hostname }}" tasks: - include_role: name: proxmox_lxc_provision tasks_from: convert + vars: + lxc_hostname: "{{ lxc_hostname }}" ``` diff --git a/roles/proxmox_lxc_provision/defaults/main.yml b/roles/proxmox_lxc_provision/defaults/main.yml index 43b697a..5c823a9 100755 --- a/roles/proxmox_lxc_provision/defaults/main.yml +++ b/roles/proxmox_lxc_provision/defaults/main.yml @@ -5,6 +5,19 @@ proxmox_api_validate_certs: false # Host to delegate pct commands to (use inventory hostname for become_password to work) proxmox_delegate_host: "{{ proxmox_api_host }}" +# Shared module_defaults applied by every task file that calls the Proxmox API. +# Override the underlying proxmox_* vars (e.g. from group_vars/all/) to customize. +_proxmox_module_defaults: + community.proxmox.proxmox: &_proxmox_api_args + api_host: "{{ proxmox_api_host }}" + api_port: "{{ proxmox_api_port }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + validate_certs: "{{ proxmox_api_validate_certs }}" + node: "{{ proxmox_node }}" + community.proxmox.proxmox_vm_info: *_proxmox_api_args + # LXC defaults lxc_template: "local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst" lxc_cores: 4 diff --git a/roles/proxmox_lxc_provision/tasks/check-exists.yml b/roles/proxmox_lxc_provision/tasks/check-exists.yml index 1d811d7..831e991 100755 --- a/roles/proxmox_lxc_provision/tasks/check-exists.yml +++ b/roles/proxmox_lxc_provision/tasks/check-exists.yml @@ -1,14 +1,17 @@ --- -- name: Query Proxmox for existing LXCs - community.proxmox.proxmox_vm_info: - type: lxc - register: proxmox_lxcs +- name: Check if LXC exists + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Query Proxmox for existing LXCs + community.proxmox.proxmox_vm_info: + type: lxc + register: proxmox_lxcs -- name: Check if LXC already exists - ansible.builtin.set_fact: - lxc_exists: >- - {{ - (lxc_vmid is defined and lxc_vmid | int in (proxmox_lxcs.proxmox_vms | map(attribute='vmid') | list)) - or - (lxc_hostname is defined and (proxmox_lxcs.proxmox_vms | selectattr('name', 'equalto', lxc_hostname) | list | length > 0)) - }} + - name: Check if LXC already exists + ansible.builtin.set_fact: + lxc_exists: >- + {{ + (lxc_vmid is defined and lxc_vmid | int in (proxmox_lxcs.proxmox_vms | map(attribute='vmid') | list)) + or + (lxc_hostname is defined and (proxmox_lxcs.proxmox_vms | selectattr('name', 'equalto', lxc_hostname) | list | length > 0)) + }} diff --git a/roles/proxmox_lxc_provision/tasks/clone.yml b/roles/proxmox_lxc_provision/tasks/clone.yml index 74fd0d4..5f1cd97 100755 --- a/roles/proxmox_lxc_provision/tasks/clone.yml +++ b/roles/proxmox_lxc_provision/tasks/clone.yml @@ -1,28 +1,31 @@ --- -- name: Create a full clone of the container - community.proxmox.proxmox: - vmid: "{{ lxc_vmid | default(0) }}" - clone: "{{ lxc_clone_from }}" - clone_type: "{{ lxc_clone_type }}" - hostname: "{{ lxc_hostname }}" - storage: "{{ lxc_storage }}" - register: clone_result +- name: Clone LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Create a full clone of the container + community.proxmox.proxmox: + vmid: "{{ lxc_vmid | default(0) }}" + clone: "{{ lxc_clone_from }}" + clone_type: "{{ lxc_clone_type }}" + hostname: "{{ lxc_hostname }}" + storage: "{{ lxc_storage }}" + register: clone_result -- name: Add bind mounts via pct - become: yes - ansible.builtin.shell: | - pct set {{ clone_result.vmid | default(lxc_vmid) }} {% for key, value in lxc_mounts.items() %}-{{ key }} {{ value }} {% endfor %} - delegate_to: "{{ proxmox_delegate_host }}" - when: lxc_mounts is defined + - name: Add bind mounts via pct + become: yes + ansible.builtin.shell: | + pct set {{ clone_result.vmid | default(lxc_vmid) }} {% for key, value in lxc_mounts.items() %}-{{ key }} {{ value }} {% endfor %} + delegate_to: "{{ proxmox_delegate_host }}" + when: lxc_mounts is defined -- name: Resize rootfs after clone - ansible.builtin.command: - cmd: "pct resize {{ clone_result.vmid }} rootfs {{ lxc_size }}G" - delegate_to: "{{ proxmox_delegate_host }}" - become: yes - register: resize_result - changed_when: resize_result.rc == 0 and 'already at specified size' not in resize_result.stderr - failed_when: - - resize_result.rc != 0 - - "'already at specified size' not in resize_result.stderr" - when: lxc_size is defined + - name: Resize rootfs after clone + ansible.builtin.command: + cmd: "pct resize {{ clone_result.vmid }} rootfs {{ lxc_size }}G" + delegate_to: "{{ proxmox_delegate_host }}" + become: yes + register: resize_result + changed_when: resize_result.rc == 0 and 'already at specified size' not in resize_result.stderr + failed_when: + - resize_result.rc != 0 + - "'already at specified size' not in resize_result.stderr" + when: lxc_size is defined diff --git a/roles/proxmox_lxc_provision/tasks/convert.yml b/roles/proxmox_lxc_provision/tasks/convert.yml index dd96d06..60fba0b 100755 --- a/roles/proxmox_lxc_provision/tasks/convert.yml +++ b/roles/proxmox_lxc_provision/tasks/convert.yml @@ -1,7 +1,10 @@ --- -- ansible.builtin.include_tasks: stop.yml +- name: Convert LXC container to template + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - ansible.builtin.include_tasks: stop.yml -- name: Convert container to template - community.proxmox.proxmox: - hostname: "{{ lxc_hostname }}" - state: template + - name: Convert container to template + community.proxmox.proxmox: + hostname: "{{ lxc_hostname }}" + state: template diff --git a/roles/proxmox_lxc_provision/tasks/create.yml b/roles/proxmox_lxc_provision/tasks/create.yml index 10e3f3a..46361d1 100755 --- a/roles/proxmox_lxc_provision/tasks/create.yml +++ b/roles/proxmox_lxc_provision/tasks/create.yml @@ -1,24 +1,27 @@ --- -- name: Create an LXC container - community.proxmox.proxmox: - vmid: "{{ lxc_vmid | default(omit) }}" - hostname: "{{ lxc_hostname }}" - password: "{{ lxc_root_password | default(omit) }}" - ostemplate: "{{ lxc_template }}" - cores: "{{ lxc_cores }}" - memory: "{{ lxc_memory }}" - swap: "{{ lxc_swap }}" - disk: "{{ lxc_disk }}" - mounts: "{{ lxc_mounts | default(omit) }}" - netif: >- - {"net0": "name={{ lxc_iface_name }},bridge={{ lxc_bridge }},ip={{ lxc_ipv4 }},gw={{ lxc_gateway }},ip6={{ lxc_ipv6 }}{% if lxc_vlan_tag is defined %},tag={{ lxc_vlan_tag }}{% endif %}"} - pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}" - onboot: "{{ lxc_onboot | default(false) }}" - startup: "{{ lxc_startup | default(omit) }}" - unprivileged: "{{ lxc_unprivileged | default(true) }}" - features: "{{ lxc_features | default(omit) }}" - timezone: "{{ lxc_timezone | default(omit) }}" - nameserver: "{{ lxc_nameserver | default(omit) }}" - state: present - tags: "{{ lxc_tags | default(omit) }}" - register: lxc_result +- name: Create LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Create an LXC container + community.proxmox.proxmox: + vmid: "{{ lxc_vmid | default(omit) }}" + hostname: "{{ lxc_hostname }}" + password: "{{ lxc_root_password | default(omit) }}" + ostemplate: "{{ lxc_template }}" + cores: "{{ lxc_cores }}" + memory: "{{ lxc_memory }}" + swap: "{{ lxc_swap }}" + disk: "{{ lxc_disk }}" + mounts: "{{ lxc_mounts | default(omit) }}" + netif: >- + {"net0": "name={{ lxc_iface_name }},bridge={{ lxc_bridge }},ip={{ lxc_ipv4 }},gw={{ lxc_gateway }},ip6={{ lxc_ipv6 }}{% if lxc_vlan_tag is defined %},tag={{ lxc_vlan_tag }}{% endif %}"} + pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}" + onboot: "{{ lxc_onboot | default(false) }}" + startup: "{{ lxc_startup | default(omit) }}" + unprivileged: "{{ lxc_unprivileged | default(true) }}" + features: "{{ lxc_features | default(omit) }}" + timezone: "{{ lxc_timezone | default(omit) }}" + nameserver: "{{ lxc_nameserver | default(omit) }}" + state: present + tags: "{{ lxc_tags | default(omit) }}" + register: lxc_result diff --git a/roles/proxmox_lxc_provision/tasks/delete.yml b/roles/proxmox_lxc_provision/tasks/delete.yml index 90d23a2..6b4428d 100755 --- a/roles/proxmox_lxc_provision/tasks/delete.yml +++ b/roles/proxmox_lxc_provision/tasks/delete.yml @@ -1,12 +1,15 @@ --- -- ansible.builtin.include_tasks: stop.yml +- name: Delete LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - ansible.builtin.include_tasks: stop.yml -- name: Delete a container - community.proxmox.proxmox: - vmid: "{{ lxc_vmid | default(omit) }}" - hostname: "{{ lxc_hostname | default(omit) }}" - state: absent - register: delete_result - failed_when: | - delete_result.failed and - ('does not exist' not in delete_result.msg) + - name: Delete a container + community.proxmox.proxmox: + vmid: "{{ lxc_vmid | default(omit) }}" + hostname: "{{ lxc_hostname | default(omit) }}" + state: absent + register: delete_result + failed_when: | + delete_result.failed and + ('does not exist' not in delete_result.msg) diff --git a/roles/proxmox_lxc_provision/tasks/main.yml b/roles/proxmox_lxc_provision/tasks/main.yml index 7d37223..94111b9 100755 --- a/roles/proxmox_lxc_provision/tasks/main.yml +++ b/roles/proxmox_lxc_provision/tasks/main.yml @@ -1,63 +1,51 @@ --- -- name: Proxmox LXC provision - module_defaults: - community.proxmox.proxmox: &proxmox_defaults - api_host: "{{ proxmox_api_host }}" - api_port: "{{ proxmox_api_port }}" - api_user: "{{ proxmox_api_user }}" - api_token_id: "{{ proxmox_api_token_id }}" - api_token_secret: "{{ proxmox_api_token_secret }}" - validate_certs: "{{ proxmox_api_validate_certs }}" - node: "{{ proxmox_node }}" - community.proxmox.proxmox_vm_info: *proxmox_defaults +- name: Check if container exists + ansible.builtin.include_tasks: + file: check-exists.yml + +- name: Skip if container already exists + meta: end_host + when: lxc_exists | bool + +- name: Container source must be defined (lxc_clone_from or lxc_template) + ansible.builtin.fail: + msg: "Neither lxc_clone_from or lxc_template are defined" + when: lxc_clone_from is undefined and lxc_template is undefined + +- name: Clone container from another container or template, then update + when: lxc_clone_from is defined block: - - name: Check if container exists + - name: Clone from template ansible.builtin.include_tasks: - file: check-exists.yml + file: clone.yml + register: clone_result - - name: Skip if container already exists - meta: end_host - when: lxc_exists | bool - - - name: Container source must be defined (lxc_clone_from or lxc_template) - ansible.builtin.fail: - msg: "Neither lxc_clone_from or lxc_template are defined" - when: lxc_clone_from is undefined and lxc_template is undefined - - - name: Clone container from another container or template, then update - when: lxc_clone_from is defined - block: - - name: Clone from template - ansible.builtin.include_tasks: - file: clone.yml - register: clone_result - - - name: Update container - ansible.builtin.include_tasks: - file: update.yml - vars: - lxc_vmid: "{{ clone_result.vmid }}" - register: lxc_result - - - name: Create the new container + - name: Update container ansible.builtin.include_tasks: - file: create.yml - when: lxc_template is defined and lxc_clone_from is undefined - - - name: Start the created container and wait for ssh + file: update.yml vars: - lxc_vmid: "{{ lxc_result.vmid }}" - ansible.builtin.include_tasks: - file: "{{ item }}" - loop: - - start.yml - - wait.yml - when: lxc_start + lxc_vmid: "{{ clone_result.vmid }}" + register: lxc_result - - name: Post clone updates - when: lxc_clone_from is defined - delegate_to: "{{ lxc_hostname }}" - block: - - name: Include post-clone tasks - ansible.builtin.include_tasks: - file: post-clone.yml +- name: Create the new container + ansible.builtin.include_tasks: + file: create.yml + when: lxc_template is defined and lxc_clone_from is undefined + +- name: Start the created container and wait for ssh + vars: + lxc_vmid: "{{ lxc_result.vmid }}" + ansible.builtin.include_tasks: + file: "{{ item }}" + loop: + - start.yml + - wait.yml + when: lxc_start + +- name: Post clone updates + when: lxc_clone_from is defined + delegate_to: "{{ lxc_hostname }}" + block: + - name: Include post-clone tasks + ansible.builtin.include_tasks: + file: post-clone.yml diff --git a/roles/proxmox_lxc_provision/tasks/start.yml b/roles/proxmox_lxc_provision/tasks/start.yml index 1466d2a..c695ea4 100755 --- a/roles/proxmox_lxc_provision/tasks/start.yml +++ b/roles/proxmox_lxc_provision/tasks/start.yml @@ -1,10 +1,13 @@ --- -- name: Start the LXC container - community.proxmox.proxmox: - vmid: "{{ lxc_result.vmid }}" - state: started - register: start_result - retries: 3 - delay: 5 - until: start_result is success - failed_when: start_result.failed and ('already running' not in start_result.msg) +- name: Start LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Start the LXC container + community.proxmox.proxmox: + vmid: "{{ lxc_result.vmid }}" + state: started + register: start_result + retries: 3 + delay: 5 + until: start_result is success + failed_when: start_result.failed and ('already running' not in start_result.msg) diff --git a/roles/proxmox_lxc_provision/tasks/stop.yml b/roles/proxmox_lxc_provision/tasks/stop.yml index ea99e72..9524907 100755 --- a/roles/proxmox_lxc_provision/tasks/stop.yml +++ b/roles/proxmox_lxc_provision/tasks/stop.yml @@ -1,11 +1,14 @@ --- -- name: Stop container if it is running - community.proxmox.proxmox: - vmid: "{{ lxc_vmid | default(omit) }}" - hostname: "{{ lxc_hostname | default(omit) }}" - state: stopped - register: stop_result - failed_when: |- - stop_result.failed and - ('not running' not in stop_result.msg) and - ('does not exist' not in stop_result.msg) +- name: Stop LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Stop container if it is running + community.proxmox.proxmox: + vmid: "{{ lxc_vmid | default(omit) }}" + hostname: "{{ lxc_hostname | default(omit) }}" + state: stopped + register: stop_result + failed_when: |- + stop_result.failed and + ('not running' not in stop_result.msg) and + ('does not exist' not in stop_result.msg) diff --git a/roles/proxmox_lxc_provision/tasks/update.yml b/roles/proxmox_lxc_provision/tasks/update.yml index d1dc360..7eaf396 100755 --- a/roles/proxmox_lxc_provision/tasks/update.yml +++ b/roles/proxmox_lxc_provision/tasks/update.yml @@ -1,21 +1,24 @@ --- -- name: Update an LXC container - community.proxmox.proxmox: - vmid: "{{ lxc_vmid }}" - hostname: "{{ lxc_hostname }}" - password: "{{ lxc_root_password | default(omit) }}" - cores: "{{ lxc_cores }}" - memory: "{{ lxc_memory }}" - swap: "{{ lxc_swap }}" - disk: "{{ lxc_disk }}" - netif: '{"net0": "name={{ lxc_iface_name }},bridge={{ lxc_bridge }},ip={{ lxc_ipv4 }},gw={{ lxc_gateway }},ip6={{ lxc_ipv6 }}{% if lxc_vlan_tag is defined %},tag={{ lxc_vlan_tag }}{% endif %}"}' - pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}" - onboot: "{{ lxc_onboot | default(false) }}" - startup: "{{ lxc_startup | default(omit) }}" - features: "{{ lxc_features | default(omit) }}" - timezone: "{{ lxc_timezone | default(omit) }}" - nameserver: "{{ lxc_nameserver | default(omit) }}" - state: present - tags: "{{ lxc_tags | default(omit) }}" - update: true - register: lxc_result +- name: Update LXC container + module_defaults: "{{ _proxmox_module_defaults }}" + block: + - name: Update an LXC container + community.proxmox.proxmox: + vmid: "{{ lxc_vmid }}" + hostname: "{{ lxc_hostname }}" + password: "{{ lxc_root_password | default(omit) }}" + cores: "{{ lxc_cores }}" + memory: "{{ lxc_memory }}" + swap: "{{ lxc_swap }}" + disk: "{{ lxc_disk }}" + netif: '{"net0": "name={{ lxc_iface_name }},bridge={{ lxc_bridge }},ip={{ lxc_ipv4 }},gw={{ lxc_gateway }},ip6={{ lxc_ipv6 }}{% if lxc_vlan_tag is defined %},tag={{ lxc_vlan_tag }}{% endif %}"}' + pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}" + onboot: "{{ lxc_onboot | default(false) }}" + startup: "{{ lxc_startup | default(omit) }}" + features: "{{ lxc_features | default(omit) }}" + timezone: "{{ lxc_timezone | default(omit) }}" + nameserver: "{{ lxc_nameserver | default(omit) }}" + state: present + tags: "{{ lxc_tags | default(omit) }}" + update: true + register: lxc_result