From 1f778a7aaa5f0831e898eb68f91499d530425b3e Mon Sep 17 00:00:00 2001 From: Sandwich Date: Thu, 12 Mar 2026 07:43:51 +0100 Subject: [PATCH] feat(bootstrap): add rescue block with VM cleanup on failure --- main.yml | 89 +++++++++++++++++++++------ roles/virtualization/tasks/delete.yml | 79 ++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 roles/virtualization/tasks/delete.yml diff --git a/main.yml b/main.yml index 573fe91..f9a31f9 100644 --- a/main.yml +++ b/main.yml @@ -110,32 +110,81 @@ ansible.builtin.import_role: name: system_check - roles: - - role: virtualization - when: system_cfg.type == "virtual" - become: false - vars: - ansible_connection: local + tasks: + - name: Bootstrap pipeline + block: + - name: Record that no pre-existing VM was found + ansible.builtin.set_fact: + _vm_absent_before_bootstrap: true - - role: environment - vars: - ansible_connection: "{{ 'vmware_tools' if hypervisor_type == 'vmware' else 'ssh' }}" + - name: Create virtual machine + when: system_cfg.type == "virtual" + ansible.builtin.include_role: + name: virtualization + public: true + vars: + ansible_connection: local + ansible_become: false - - role: partitioning - vars: - partitioning_boot_partition_suffix: 1 - partitioning_main_partition_suffix: 2 + - name: Configure environment + ansible.builtin.include_role: + name: environment + public: true - - role: bootstrap + - name: Partition disks + ansible.builtin.include_role: + name: partitioning + public: true + vars: + partitioning_boot_partition_suffix: 1 + partitioning_main_partition_suffix: 2 - - role: configuration + - name: Install base system + ansible.builtin.include_role: + name: bootstrap + public: true - - role: cis - when: system_cfg.features.cis.enabled | bool + - name: Apply system configuration + ansible.builtin.include_role: + name: configuration + public: true - - role: cleanup - when: system_cfg.type in ["virtual", "physical"] - become: false + - name: Apply CIS hardening + when: system_cfg.features.cis.enabled | bool + ansible.builtin.include_role: + name: cis + public: true + + - name: Clean up and finalize + when: system_cfg.type in ["virtual", "physical"] + ansible.builtin.include_role: + name: cleanup + public: true + vars: + ansible_become: false + + rescue: + - name: Delete VM on bootstrap failure + when: + - _vm_absent_before_bootstrap | default(false) | bool + - virtualization_vm_created_in_run | default(false) | bool + - system_cfg.type == "virtual" + ansible.builtin.include_role: + name: virtualization + tasks_from: delete + vars: + ansible_connection: local + ansible_become: false + tags: + - rescue_cleanup + + - name: Fail host after bootstrap rescue + ansible.builtin.fail: + msg: >- + Bootstrap failed for {{ hostname }}. + {{ 'VM was deleted to allow clean retry.' + if (virtualization_vm_created_in_run | default(false)) + else 'VM was not created in this run (kept).' }} post_tasks: - name: Set post-reboot connection flags diff --git a/roles/virtualization/tasks/delete.yml b/roles/virtualization/tasks/delete.yml new file mode 100644 index 0000000..85bc12a --- /dev/null +++ b/roles/virtualization/tasks/delete.yml @@ -0,0 +1,79 @@ +--- +- name: Delete VMware VM + when: hypervisor_type == "vmware" + delegate_to: localhost + community.vmware.vmware_guest: + hostname: "{{ hypervisor_cfg.url }}" + username: "{{ hypervisor_cfg.username }}" + password: "{{ hypervisor_cfg.password }}" + validate_certs: "{{ hypervisor_cfg.certs | bool }}" + datacenter: "{{ hypervisor_cfg.datacenter }}" + name: "{{ hostname }}" + folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}" + state: absent + force: true + no_log: true + +- name: Delete Proxmox VM + when: hypervisor_type == "proxmox" + delegate_to: localhost + community.proxmox.proxmox_kvm: + api_host: "{{ hypervisor_cfg.url }}" + api_user: "{{ hypervisor_cfg.username }}" + api_password: "{{ hypervisor_cfg.password }}" + node: "{{ hypervisor_cfg.node }}" + vmid: "{{ system_cfg.id | default(omit, true) }}" + name: "{{ hostname }}" + state: absent + force: true + no_log: true + +- name: Destroy libvirt VM + when: hypervisor_type == "libvirt" + delegate_to: localhost + block: + - name: Stop libvirt VM + community.libvirt.virt: + name: "{{ hostname }}" + state: destroyed + uri: "{{ libvirt_uri | default('qemu:///system') }}" + failed_when: false + + - name: Undefine libvirt VM + community.libvirt.virt: + name: "{{ hostname }}" + command: undefine + uri: "{{ libvirt_uri | default('qemu:///system') }}" + failed_when: false + + - name: Remove libvirt disk images + ansible.builtin.file: + path: "{{ item.path }}" + state: absent + loop: "{{ virtualization_libvirt_disks | default([]) }}" + loop_control: + label: "{{ item.path | default('unknown') }}" + + - name: Remove libvirt cloud-init disk + ansible.builtin.file: + path: "{{ virtualization_libvirt_cloudinit_path | default('/dev/null') }}" + state: absent + when: virtualization_libvirt_cloudinit_path is defined + +- name: Destroy Xen VM + when: hypervisor_type == "xen" + delegate_to: localhost + block: + - name: Stop Xen VM + ansible.builtin.command: + argv: + - xl + - destroy + - "{{ hostname }}" + failed_when: false + + - name: Remove Xen VM config + ansible.builtin.file: + path: "/etc/xen/{{ hostname }}.cfg" + state: absent + failed_when: false