From 0696218eb6fcc337136d34f241b366d16dd2fd89 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 10:16:05 +0400 Subject: [PATCH 1/6] feat(pb,task): moves input validation into separated tasks files. Play fails immediately if a validation fails --- .../deploy_and_set_up_lxc_on_proxmox.yaml | 62 ++++--------------- tasks/hostname_validation.yaml | 23 +++++++ tasks/human_admin_user_validation.yaml | 22 +++++++ tasks/ip_validation.yaml | 20 ++++++ tasks/lxc_privileged_validation.yaml | 22 +++++++ tasks/password_validation.yaml | 38 ++++++++++++ 6 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 tasks/hostname_validation.yaml create mode 100644 tasks/human_admin_user_validation.yaml create mode 100644 tasks/ip_validation.yaml create mode 100644 tasks/lxc_privileged_validation.yaml create mode 100644 tasks/password_validation.yaml diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index 86440ca..d264115 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -10,65 +10,27 @@ ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" - vars_prompt: - - name: lxc_hostname - prompt: "Hostname for the container (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)" - default: "new-lxc-test" - private: false + pre_tasks: + - name: Include input and validation for hostname + ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml + + - name: Include input and validation for password + ansible.builtin.include_tasks: ../tasks/password_validation.yaml - - name: lxc_root_password - prompt: "Root password" - default: "new-lxc-test" - private: true - confirm: true - encrypt: sha512_crypt + - name: Include input and validation for IP + ansible.builtin.include_tasks: ../tasks/ip_validation.yaml - - name: lxc_ip_address - prompt: "IP address for the container (in x.x.x.x/x formant)" - default: "192.168.0.90/24" - private: false + - name: Include input and validation for LXC privilege + ansible.builtin.include_tasks: ../tasks/lxc_privileged_validation.yaml - - name: lxc_is_privileged - prompt: "Unprivileged LXC (true/false)?" - default: true - private: false + - name: Include input and validation for human admin user creation + ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml - - name: lxc_create_human_admin_user - prompt: "Create human admin user? (true/false)?" - default: true - private: false - - pre_tasks: - - name: Validate hostname - ansible.builtin.fail: - msg: | - Invalid hostname: "{{ lxc_hostname }}" - Hostname must: - - contain only letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) - - not start or end with a hyphen or dot - - have each label between 1 and 63 characters - - not exceed 253 characters in total - when: > - lxc_hostname | length > 253 or - lxc_hostname is not regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') - run_once: true - - - name: Validate IP address - ansible.builtin.fail: - msg: | - Invalid IP address: "{{ lxc_ip_address }}" - IP address must be in x.x.x.x/x format (e.g., 192.168.0.90/24). - when: not lxc_ip_address is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(\\/(3[0-2]|[12]?\\d))?$') - run_once: true roles: - ../roles/deploy_lxc_on_proxmox tasks: - - name: Make the prompted hostname available to the whole playbook - ansible.builtin.set_fact: - fact_lxc_hostname: "{{ lxc_hostname }}" - - name: Make the prompted IP available to the whole playbook ansible.builtin.set_fact: fact_lxc_ip_address: "{{ lxc_ip_address.split('/') | first }}" diff --git a/tasks/hostname_validation.yaml b/tasks/hostname_validation.yaml new file mode 100644 index 0000000..7ff704c --- /dev/null +++ b/tasks/hostname_validation.yaml @@ -0,0 +1,23 @@ +--- + +- name: Set default hostname + ansible.builtin.set_fact: + lxc_hostname: "new-lxc-test" + +- name: Prompt for hostname with immediate validation + ansible.builtin.pause: + prompt: "Enter hostname (letters, digits, hyphens, dots; max 253 chars) [new-lxc-test]" + register: hostname_result + until: > + hostname_result.user_input | length == 0 or + ( + hostname_result.user_input | length <= 253 and + hostname_result.user_input is regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') + ) + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + lxc_hostname: "{{ hostname_result.user_input }}" + when: hostname_result.user_input | length > 0 diff --git a/tasks/human_admin_user_validation.yaml b/tasks/human_admin_user_validation.yaml new file mode 100644 index 0000000..df6d04b --- /dev/null +++ b/tasks/human_admin_user_validation.yaml @@ -0,0 +1,22 @@ +--- + +- name: Set default human admin user + ansible.builtin.set_fact: + lxc_human_admin_user: true + +- name: Prompt for human admin user creation with immediate validation + ansible.builtin.pause: + prompt: "Create human admin user? (true/false) [true]" + register: human_admin_user_result + until: > + human_admin_user_result.user_input | length == 0 or + ( + human_admin_user_result.user_input in ['true', 'false'] + ) + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + lxc_human_admin_user: "{{ human_admin_user_result.user_input }}" + when: human_admin_user_result.user_input | length > 0 diff --git a/tasks/ip_validation.yaml b/tasks/ip_validation.yaml new file mode 100644 index 0000000..bbbd866 --- /dev/null +++ b/tasks/ip_validation.yaml @@ -0,0 +1,20 @@ +--- + +- name: Set default ip + ansible.builtin.set_fact: + lxc_ip_address: "192.168.0.90/24" + +- name: Prompt for IP with immediate validation + ansible.builtin.pause: + prompt: "Enter ip in x.x.x.x/x format [192.168.0.90/24]" + register: ip_result + until: > + ip_result.user_input | length == 0 or + ip_result.user_input is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)/(3[0-2]|[12]?\\d)$') + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + lxc_ip_address: "{{ ip_result.user_input }}" + when: ip_result.user_input | length > 0 diff --git a/tasks/lxc_privileged_validation.yaml b/tasks/lxc_privileged_validation.yaml new file mode 100644 index 0000000..5b4c3af --- /dev/null +++ b/tasks/lxc_privileged_validation.yaml @@ -0,0 +1,22 @@ +--- + +- name: Set default privileged + ansible.builtin.set_fact: + lxc_privileged: false + +- name: Prompt for privileged with immediate validation + ansible.builtin.pause: + prompt: "Privileged LXC? (true/false) [false]" + register: privileged_result + until: > + privileged_result.user_input | length == 0 or + ( + privileged_result.user_input in ['true', 'false'] + ) + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + lxc_privileged: "{{ privileged_result.user_input }}" + when: privileged_result.user_input | length > 0 diff --git a/tasks/password_validation.yaml b/tasks/password_validation.yaml new file mode 100644 index 0000000..e40e8bd --- /dev/null +++ b/tasks/password_validation.yaml @@ -0,0 +1,38 @@ +--- + +- name: Set default password + ansible.builtin.set_fact: + lxc_root_password: "new-lxc-test" + +- name: Prompt for password + ansible.builtin.pause: + prompt: "Enter password (letters, digits, hyphens, dots; max 253 chars) [new-lxc-test]" + echo: false + register: password_result_1 + until: > + password_result_1.user_input | length == 0 or + ( + password_result_1.user_input | length <= 253 and + password_result_1.user_input is regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') + ) + retries: 3 + delay: 0 + +- name: Prompt for password (confirmation) + ansible.builtin.pause: + prompt: "Confirm your new password" + echo: no + register: password_result_2 + +- name: Ensure passwords match + ansible.builtin.assert: + that: + - password_result_1.user_input == password_result_2.user_input + fail_msg: "Passwords do not match!" + success_msg: "Password confirmed." + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + lxc_root_password: "{{ password_result_1.user_input }}" + when: password_result_1.user_input | length > 0 + -- 2.52.0 From daa582657340c45faa7d61109c62eb61dbe86142 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 10:50:46 +0400 Subject: [PATCH 2/6] feat(pb,role,task): completes the LXC deployment workflow using new validation tasks --- playbooks/deploy_and_set_up_lxc_on_proxmox.yaml | 14 +++----------- roles/deploy_lxc_on_proxmox/tasks/main.yaml | 4 ++-- tasks/ip_validation.yaml | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index d264115..28011a1 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -31,17 +31,9 @@ - ../roles/deploy_lxc_on_proxmox tasks: - - name: Make the prompted IP available to the whole playbook - ansible.builtin.set_fact: - fact_lxc_ip_address: "{{ lxc_ip_address.split('/') | first }}" - - - name: Make the "human admin user" selection available to the whole playbook - ansible.builtin.set_fact: - fact_lxc_create_human_admin_user: "{{ lxc_create_human_admin_user }}" - - name: Add the target host to the inventory ansible.builtin.add_host: - name: "{{ fact_lxc_ip_address }}" + name: "{{ lxc_ip_address }}" groups: new_host ansible_user: ansible ansible_become_password: "{{ ansible_become_passwd }}" @@ -66,10 +58,10 @@ - name: Include human admin user role ansible.builtin.include_role: name: ../roles/human_admin_user - when: "hostvars['localhost']['fact_lxc_create_human_admin_user'] | bool" + when: "hostvars['localhost']['lxc_human_admin_user'] | bool" - name: Include update configs role ansible.builtin.include_role: name: ../roles/update_configs - when: "hostvars['localhost']['fact_lxc_create_human_admin_user'] | bool" + when: "hostvars['localhost']['lxc_human_admin_user'] | bool" diff --git a/roles/deploy_lxc_on_proxmox/tasks/main.yaml b/roles/deploy_lxc_on_proxmox/tasks/main.yaml index b396bd4..e513c6a 100644 --- a/roles/deploy_lxc_on_proxmox/tasks/main.yaml +++ b/roles/deploy_lxc_on_proxmox/tasks/main.yaml @@ -14,7 +14,7 @@ password: "{{ lxc_root_password }}" hostname: "{{ lxc_hostname }}" ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz' - unprivileged: "{{ lxc_is_privileged }}" + unprivileged: "{{ lxc_privileged }}" memory: 2048 cores: 2 state: present @@ -26,7 +26,7 @@ storage: local pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" netif: - net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }},bridge=vmbr0" + net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }}/{{ lxc_netmask }},bridge=vmbr0" - name: Make sure the container has started become: false diff --git a/tasks/ip_validation.yaml b/tasks/ip_validation.yaml index bbbd866..a3cf44d 100644 --- a/tasks/ip_validation.yaml +++ b/tasks/ip_validation.yaml @@ -2,7 +2,11 @@ - name: Set default ip ansible.builtin.set_fact: - lxc_ip_address: "192.168.0.90/24" + lxc_ip_address: "192.168.0.90" + +- name: Set default network mask + ansible.builtin.set_fact: + lxc_netmask: "24" - name: Prompt for IP with immediate validation ansible.builtin.pause: @@ -14,7 +18,12 @@ retries: 2 delay: 0 -- name: Use user input only if provided, otherwise keep the default +- name: Use user input only if provided, otherwise keep the default (IP) ansible.builtin.set_fact: - lxc_ip_address: "{{ ip_result.user_input }}" + lxc_ip_address: "{{ ip_result.user_input.split('/') | first }}" + when: ip_result.user_input | length > 0 + +- name: Use user input only if provided, otherwise keep the default (netmask) + ansible.builtin.set_fact: + lxc_netmask: "{{ ip_result.user_input.split('/') | second }}" when: ip_result.user_input | length > 0 -- 2.52.0 From f29de423973a918a231bf2cb265a031d3ce4eff6 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 11:16:18 +0400 Subject: [PATCH 3/6] feat(pb,role,task): generalize names of common pre_tasks and their variables, specifies LXC-specific ones --- playbooks/deploy_and_set_up_lxc_on_proxmox.yaml | 10 +++++----- roles/deploy_lxc_on_proxmox/tasks/main.yaml | 6 +++--- tasks/hostname_validation.yaml | 4 ++-- tasks/human_admin_user_validation.yaml | 4 ++-- tasks/ip_validation.yaml | 8 ++++---- ...lidation.yaml => lxc_root_password_validation.yaml} | 0 6 files changed, 16 insertions(+), 16 deletions(-) rename tasks/{password_validation.yaml => lxc_root_password_validation.yaml} (100%) diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index 28011a1..40d8c51 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -14,8 +14,8 @@ - name: Include input and validation for hostname ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml - - name: Include input and validation for password - ansible.builtin.include_tasks: ../tasks/password_validation.yaml + - name: Include input and validation for LXC root password + ansible.builtin.include_tasks: ../tasks/lxc_root_password_validation.yaml - name: Include input and validation for IP ansible.builtin.include_tasks: ../tasks/ip_validation.yaml @@ -33,7 +33,7 @@ tasks: - name: Add the target host to the inventory ansible.builtin.add_host: - name: "{{ lxc_ip_address }}" + name: "{{ guest_ip_address }}" groups: new_host ansible_user: ansible ansible_become_password: "{{ ansible_become_passwd }}" @@ -58,10 +58,10 @@ - name: Include human admin user role ansible.builtin.include_role: name: ../roles/human_admin_user - when: "hostvars['localhost']['lxc_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" - name: Include update configs role ansible.builtin.include_role: name: ../roles/update_configs - when: "hostvars['localhost']['lxc_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" diff --git a/roles/deploy_lxc_on_proxmox/tasks/main.yaml b/roles/deploy_lxc_on_proxmox/tasks/main.yaml index e513c6a..d3e6261 100644 --- a/roles/deploy_lxc_on_proxmox/tasks/main.yaml +++ b/roles/deploy_lxc_on_proxmox/tasks/main.yaml @@ -12,7 +12,7 @@ cmode: "shell" password: "{{ lxc_root_password }}" - hostname: "{{ lxc_hostname }}" + hostname: "{{ guest_hostname }}" ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz' unprivileged: "{{ lxc_privileged }}" memory: 2048 @@ -26,7 +26,7 @@ storage: local pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" netif: - net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }}/{{ lxc_netmask }},bridge=vmbr0" + net0: "name=eth0,gw=192.168.0.1,ip={{ guest_ip_address }}/{{ guest_netmask }},bridge=vmbr0" - name: Make sure the container has started become: false @@ -38,7 +38,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - hostname: "{{ lxc_hostname }}" + hostname: "{{ guest_hostname }}" state: started retries: 3 delay: 15 diff --git a/tasks/hostname_validation.yaml b/tasks/hostname_validation.yaml index 7ff704c..252ce87 100644 --- a/tasks/hostname_validation.yaml +++ b/tasks/hostname_validation.yaml @@ -2,7 +2,7 @@ - name: Set default hostname ansible.builtin.set_fact: - lxc_hostname: "new-lxc-test" + guest_hostname: "new-lxc-test" - name: Prompt for hostname with immediate validation ansible.builtin.pause: @@ -19,5 +19,5 @@ - name: Use user input only if provided, otherwise keep the default ansible.builtin.set_fact: - lxc_hostname: "{{ hostname_result.user_input }}" + guest_hostname: "{{ hostname_result.user_input }}" when: hostname_result.user_input | length > 0 diff --git a/tasks/human_admin_user_validation.yaml b/tasks/human_admin_user_validation.yaml index df6d04b..849ba7b 100644 --- a/tasks/human_admin_user_validation.yaml +++ b/tasks/human_admin_user_validation.yaml @@ -2,7 +2,7 @@ - name: Set default human admin user ansible.builtin.set_fact: - lxc_human_admin_user: true + create_human_admin_user: true - name: Prompt for human admin user creation with immediate validation ansible.builtin.pause: @@ -18,5 +18,5 @@ - name: Use user input only if provided, otherwise keep the default ansible.builtin.set_fact: - lxc_human_admin_user: "{{ human_admin_user_result.user_input }}" + create_human_admin_user: "{{ human_admin_user_result.user_input }}" when: human_admin_user_result.user_input | length > 0 diff --git a/tasks/ip_validation.yaml b/tasks/ip_validation.yaml index a3cf44d..eabcd78 100644 --- a/tasks/ip_validation.yaml +++ b/tasks/ip_validation.yaml @@ -2,11 +2,11 @@ - name: Set default ip ansible.builtin.set_fact: - lxc_ip_address: "192.168.0.90" + guest_ip_address: "192.168.0.90" - name: Set default network mask ansible.builtin.set_fact: - lxc_netmask: "24" + guest_netmask: "24" - name: Prompt for IP with immediate validation ansible.builtin.pause: @@ -20,10 +20,10 @@ - name: Use user input only if provided, otherwise keep the default (IP) ansible.builtin.set_fact: - lxc_ip_address: "{{ ip_result.user_input.split('/') | first }}" + guest_ip_address: "{{ ip_result.user_input.split('/') | first }}" when: ip_result.user_input | length > 0 - name: Use user input only if provided, otherwise keep the default (netmask) ansible.builtin.set_fact: - lxc_netmask: "{{ ip_result.user_input.split('/') | second }}" + guest_netmask: "{{ ip_result.user_input.split('/') | second }}" when: ip_result.user_input | length > 0 diff --git a/tasks/password_validation.yaml b/tasks/lxc_root_password_validation.yaml similarity index 100% rename from tasks/password_validation.yaml rename to tasks/lxc_root_password_validation.yaml -- 2.52.0 From 24f34198ff117e10328a03bc3be30940f9e165bc Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 14:29:23 +0400 Subject: [PATCH 4/6] feat(pb,role,task): makes VM deployment playbook use new validation pre_tasks, become_method and os_template tasks are shared now --- playbooks/deploy_vm_from_cloud_init.yaml | 99 ++++--------------- .../deploy_vm_on_proxmox/tasks/create_vm.yaml | 12 +-- roles/deploy_vm_on_proxmox/tasks/main.yaml | 2 +- .../deploy_vm_on_proxmox/tasks/start_vm.yaml | 2 +- tasks/become_method_validation.yaml | 22 +++++ tasks/os_template_validation.yaml | 22 +++++ 6 files changed, 69 insertions(+), 90 deletions(-) create mode 100644 tasks/become_method_validation.yaml create mode 100644 tasks/os_template_validation.yaml diff --git a/playbooks/deploy_vm_from_cloud_init.yaml b/playbooks/deploy_vm_from_cloud_init.yaml index 3053194..9f28a50 100644 --- a/playbooks/deploy_vm_from_cloud_init.yaml +++ b/playbooks/deploy_vm_from_cloud_init.yaml @@ -10,98 +10,33 @@ ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" - vars_prompt: - - name: vm_hostname - prompt: "Hostname for the VM (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)" - default: "new-vm-test" - private: false - - - name: vm_os_template - prompt: "Select OS template. Available: debian-13 | alpine" - default: "debian-13" - private: false - - - name: vm_become_method - prompt: "Select become method. Available: sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl" - default: "sudo" - private: false - - - name: vm_create_human_admin_user - prompt: "Create human admin user? (true/false)?" - default: true - private: false - - - name: vm_ip_address - prompt: "IP address for the VM (in x.x.x.x/x formant)" - default: "192.168.0.89/24" - private: false - pre_tasks: - - name: Validate hostname - ansible.builtin.fail: - msg: | - Invalid hostname: "{{ lxc_hostname }}" - Hostname must: - - contain only letters (a-z, A-Z), digits (0-9), hyphens (-), and dots (.) - - not start or end with a hyphen or dot - - have each label between 1 and 63 characters - - not exceed 253 characters in total - when: > - lxc_hostname | length > 253 or - lxc_hostname is not regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') - run_once: true - - - name: Validate IP address - ansible.builtin.fail: - msg: | - Invalid IP address: "{{ vm_ip_address }}" - IP address must be in x.x.x.x/x format (e.g., 192.168.0.89/24). - when: not vm_ip_address is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(\\/(3[0-2]|[12]?\\d))?$') - run_once: true - - - name: Validate OS template - ansible.builtin.fail: - msg: | - Invalid OS template: "{{ vm_os_template }}" - Select from available options: debian-13, alpine - when: vm_os_template not in ['debian-13', 'alpine'] - run_once: true + - name: Include input and validation for hostname + ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml - - name: Validate become method - ansible.builtin.fail: - msg: | - Invalid become method: "{{ vm_become_method }}" - Select from available options: sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl - when: vm_become_method not in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl'] - run_once: true + - name: Include input and validation for IP + ansible.builtin.include_tasks: ../tasks/ip_validation.yaml + + - name: Include input and validation for human admin user creation + ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml + + - name: Include input and validation for OS template + ansible.builtin.include_tasks: ../tasks/os_template_validation.yaml + + - name: Include input and validation for become method + ansible.builtin.include_tasks: ../tasks/become_method_validation.yaml roles: - ../roles/deploy_vm_on_proxmox tasks: - - name: Make the hostname available to the whole playbook - ansible.builtin.set_fact: - fact_vm_hostname: "{{ vm_hostname }}" - - - name: Make the OS template available to the whole playbook - ansible.builtin.set_fact: - fact_vm_os_template: "{{ vm_os_template }}" - - - name: Make the IP available to the whole playbook - ansible.builtin.set_fact: - fact_vm_ip_address: "{{ vm_ip_address.split('/') | first }}" - - - name: Make the "human admin user" selection available to the whole playbook - ansible.builtin.set_fact: - fact_vm_create_human_admin_user: "{{ vm_create_human_admin_user }}" - - name: Add the target host to the inventory ansible.builtin.add_host: - name: "{{ fact_vm_ip_address }}" + name: "{{ guest_ip_address }}" groups: new_host ansible_user: ansible ansible_become_password: "{{ ansible_become_passwd }}" - ansible_become_method: "{{ vm_become_method }}" + ansible_become_method: "{{ guest_become_method }}" - name: Configure VM @@ -123,10 +58,10 @@ - name: Include human admin user role ansible.builtin.include_role: name: ../roles/human_admin_user - when: "hostvars['localhost']['fact_vm_create_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" - name: Include update configs role ansible.builtin.include_role: name: ../roles/update_configs - when: "hostvars['localhost']['fact_vm_create_human_admin_user'] | bool" + when: "hostvars['localhost']['create_human_admin_user'] | bool" diff --git a/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml b/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml index 4de80a0..effc35c 100644 --- a/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/create_vm.yaml @@ -10,8 +10,8 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - clone: "{{ vm_os_template }}-cloud-init-template" - name: "{{ vm_hostname }}" + clone: "{{ guest_os_template }}-cloud-init-template" + name: "{{ guest_hostname }}" storage: main format: qcow2 full: true @@ -33,7 +33,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" # Cloud-init parameters update: true ide: @@ -41,7 +41,7 @@ ciuser: "ansible" cipassword: "{{ ansible_become_passwd }}" ipconfig: - ipconfig0: "ip={{ vm_ip_address }},gw=192.168.0.1" + ipconfig0: "ip={{ guest_ip_address }}/{{ guest_netmask }},gw=192.168.0.1" - name: Tweak the hardware settings become: false @@ -53,7 +53,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" cores: 4 memory: 4096 # Minimal value for debian based OS for smooth workflow update: true @@ -68,5 +68,5 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: started diff --git a/roles/deploy_vm_on_proxmox/tasks/main.yaml b/roles/deploy_vm_on_proxmox/tasks/main.yaml index d54ec6d..7c17320 100644 --- a/roles/deploy_vm_on_proxmox/tasks/main.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/main.yaml @@ -9,7 +9,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: current ignore_errors: true register: vm_status diff --git a/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml b/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml index 8140a72..4bc6f4e 100644 --- a/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/start_vm.yaml @@ -10,7 +10,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - name: "{{ vm_hostname }}" + name: "{{ guest_hostname }}" state: started - name: Sleep for 120 seconds to ensure that Proxmox has cloned the VM diff --git a/tasks/become_method_validation.yaml b/tasks/become_method_validation.yaml new file mode 100644 index 0000000..412a7c1 --- /dev/null +++ b/tasks/become_method_validation.yaml @@ -0,0 +1,22 @@ +--- + +- name: Set default become method + ansible.builtin.set_fact: + guest_become_method: "sudo" + +- name: Prompt for become method with immediate validation + ansible.builtin.pause: + prompt: "Choose become method, available: sudo, su, pbrun, pfexec, doas, dzdo, ksu, runas, machinectl [sudo]" + register: become_method_result + until: > + become_method_result.user_input | length == 0 or + ( + become_method_result.user_input in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl'] + ) + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + guest_become_method: "{{ become_method_result.user_input }}" + when: become_method_result.user_input | length > 0 diff --git a/tasks/os_template_validation.yaml b/tasks/os_template_validation.yaml new file mode 100644 index 0000000..27f819b --- /dev/null +++ b/tasks/os_template_validation.yaml @@ -0,0 +1,22 @@ +--- + +- name: Set default OS template + ansible.builtin.set_fact: + guest_os_template: "debian-13" + +- name: Prompt for OS template with immediate validation + ansible.builtin.pause: + prompt: "Choose OS template, available: debian-13, alpine [debian-13]" + register: os_template_result + until: > + os_template_result.user_input | length == 0 or + ( + os_template_result.user_input in ['debian-13', 'alpine'] + ) + retries: 2 + delay: 0 + +- name: Use user input only if provided, otherwise keep the default + ansible.builtin.set_fact: + guest_os_template: "{{ os_template_result.user_input }}" + when: os_template_result.user_input | length > 0 -- 2.52.0 From 93cee75103c4a3af5fae5f0f424496e13d406539 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 17:59:08 +0400 Subject: [PATCH 5/6] fix(task): minor names and typos fixes --- tasks/hostname_validation.yaml | 4 ++-- tasks/lxc_root_password_validation.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/hostname_validation.yaml b/tasks/hostname_validation.yaml index 252ce87..9dd9f81 100644 --- a/tasks/hostname_validation.yaml +++ b/tasks/hostname_validation.yaml @@ -2,11 +2,11 @@ - name: Set default hostname ansible.builtin.set_fact: - guest_hostname: "new-lxc-test" + guest_hostname: "test-guest" - name: Prompt for hostname with immediate validation ansible.builtin.pause: - prompt: "Enter hostname (letters, digits, hyphens, dots; max 253 chars) [new-lxc-test]" + prompt: "Enter hostname (letters, digits, hyphens (but not leading and trailing), dots; max 253 chars) [{{ guest_hostname }}]" register: hostname_result until: > hostname_result.user_input | length == 0 or diff --git a/tasks/lxc_root_password_validation.yaml b/tasks/lxc_root_password_validation.yaml index e40e8bd..cf946b6 100644 --- a/tasks/lxc_root_password_validation.yaml +++ b/tasks/lxc_root_password_validation.yaml @@ -2,11 +2,11 @@ - name: Set default password ansible.builtin.set_fact: - lxc_root_password: "new-lxc-test" + lxc_root_password: "test-guest" - name: Prompt for password ansible.builtin.pause: - prompt: "Enter password (letters, digits, hyphens, dots; max 253 chars) [new-lxc-test]" + prompt: "Enter password (letters, digits, hyphens, dots; max 253 chars) [{{ lxc_root_password }}]" echo: false register: password_result_1 until: > -- 2.52.0 From 130fcb5069530c5b593927e9adad6678a9340045 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Thu, 17 Sep 2026 19:13:46 +0400 Subject: [PATCH 6/6] fix(taks): allows to use more complex passwords --- tasks/lxc_root_password_validation.yaml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tasks/lxc_root_password_validation.yaml b/tasks/lxc_root_password_validation.yaml index cf946b6..845a49f 100644 --- a/tasks/lxc_root_password_validation.yaml +++ b/tasks/lxc_root_password_validation.yaml @@ -6,22 +6,17 @@ - name: Prompt for password ansible.builtin.pause: - prompt: "Enter password (letters, digits, hyphens, dots; max 253 chars) [{{ lxc_root_password }}]" + prompt: "Enter your password [{{ lxc_root_password }}]" echo: false register: password_result_1 - until: > - password_result_1.user_input | length == 0 or - ( - password_result_1.user_input | length <= 253 and - password_result_1.user_input is regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') - ) - retries: 3 + until: password_result_1.user_input | length == 0 + retries: 2 delay: 0 - name: Prompt for password (confirmation) ansible.builtin.pause: prompt: "Confirm your new password" - echo: no + echo: false register: password_result_2 - name: Ensure passwords match -- 2.52.0