diff --git a/README.md b/README.md index 3edfbf7..d94a839 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# role_modele +# role_proxmox -Modèle \ No newline at end of file +Manage Proxmox VE instances. diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..169970a --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,74 @@ +--- +# vars file for proxmox + +# Images list to download +proxmox_images: [] +# Example: +# - url: "https://cloud.debian.org/images/cloud/bookworm-backports/latest/debian-12-backports-genericcloud-amd64.qcow2" +# dest: /mnt/pve/pumbaa/disk_image/bookworm.qcow2 + +# Delegate tasks to Proxmox VE host (local API call !) +proxmox_delegate_to: "" + +# Informations to connect to API +proxmox_api_host: "" +proxmox_api_user: "" +proxmox_api_token_id: "" +proxmox_api_token_secret: "" + +# Type of instance (ct or vm) +proxmox_instance_type: "" + +# Instance configuration +proxmox_instance_node: "" +proxmox_instance_autostart: true +proxmox_instance_args: "" +proxmox_instance_cores: 1 +proxmox_instance_cpu: kvm64 +proxmox_instance_full: false +proxmox_instance_hotplug: + - cpu + - disk + - memory + - network + - usb +proxmox_instance_ipconfig: {} +proxmox_instance_memory: 1024 +proxmox_instance_migrate: true +proxmox_instance_net: {} +proxmox_instance_numa: true +proxmox_instance_onboot: true +proxmox_instance_protection: true +proxmox_instance_vmid: "" + +proxmox_instance_disks: [] +# Example: +# - disk: virtio0 +# storage: ssd120 +# size: 8 +# img: "{{ proxmox_images[0].dest }}" +# - disk: virtio1 +# backup: true +# mbps: 10 +# storage: nfs +# size: 10 + +proxmox_instance_cloudinit: true +proxmox_instance_cloudinit_cipassword: "" +proxmox_instance_cloudinit_ciuser: "" +proxmox_instance_cloudinit_nameservers: "" +proxmox_instance_cloudinit_searchdomains: null +proxmox_instance_cloudinit_sshkeys: "" + +# Start instance after installation +proxmox_start_instance: true + +# Reboot instance when changed +proxmox_reboot_instance: true + +# Start qemu-guest-agent.service at the end +proxmox_start_agent: true + +# Configuration to wait SSH +proxmox_instance_ssh_ip: "" +proxmox_instance_ssh_port: "" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..236cae9 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,59 @@ +--- +# handlers file for proxmox + +- name: Configure cloud-init + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + agent: "enabled=1,fstrim_cloned_disks=1" + cipassword: "{{ proxmox_instance_cloudinit_cipassword }}" + ciuser: "{{ proxmox_instance_cloudinit_ciuser }}" + ciupgrade: false + ide: + ide2: "{{ proxmox_instance_disks[0].storage }}:cloudinit" + ipconfig: "{{ proxmox_instance_ipconfig }}" + name: "{{ inventory_hostname }}" + nameservers: "{{ proxmox_instance_cloudinit_nameservers }}" + node: "{{ proxmox_instance_node }}" + searchdomains: "{{ proxmox_instance_cloudinit_searchdomains }}" + sshkeys: "{{ proxmox_instance_cloudinit_sshkeys }}" + vmid: "{{ proxmox_instance_vmid }}" + update: true + update_unsafe: true + when: proxmox_instance_cloudinit + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Start instance + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + name: "{{ inventory_hostname }}" + node: "{{ proxmox_instance_node }}" + state: started + when: + - proxmox_start_instance + - not rebooted.changed + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Wait SSH port is open + ansible.builtin.wait_for: + host: "{{ proxmox_instance_ssh_ip }}" + port: "{{ proxmox_instance_ssh_port }}" + search_regex: OpenSSH + delay: 30 + when: proxmox_start_instance + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Wait cloud-init + ansible.builtin.command: + cmd: cloud-init status + when: proxmox_instance_cloudinit + register: cloudinit_status + until: cloudinit_status.stdout.find("done") != -1 + retries: 100 + delay: 36 + ignore_errors: true diff --git a/meta/main.yml b/meta/main.yml index c58bebf..91807c6 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,7 +1,7 @@ galaxy_info: namespace: ykn author: pulsar89.5 - description: Rôle modèle + description: Manage Proxmox VE instances license: GPL-3.0-or-later @@ -10,6 +10,6 @@ galaxy_info: platforms: - name: Debian versions: - - all + - bookworm dependencies: [] diff --git a/tasks/get_images.yml b/tasks/get_images.yml new file mode 100644 index 0000000..d40d454 --- /dev/null +++ b/tasks/get_images.yml @@ -0,0 +1,24 @@ +--- +# tasks file for proxmox + +- name: Create images storage directory + ansible.builtin.file: + path: "{{ item.dest | ansible.builtin.dirname }}" + state: directory + mode: u=rwX,g=rX,o=rX + become: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + loop: "{{ proxmox_images }}" + loop_control: + label: "{{ item.dest | ansible.builtin.dirname }}" + +- name: Download images + ansible.builtin.get_url: + url: "{{ item.url }}" + dest: "{{ item.dest }}" + mode: u=rw,g=r,o=r + become: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + loop: "{{ proxmox_images }}" + loop_control: + label: "{{ item.dest | ansible.builtin.basename }}" diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..961398a --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,25 @@ +--- +# tasks file for proxmox + +- name: Install prerequisite + ansible.builtin.apt: + name: python3-proxmoxer + update_cache: true + become: true + run_once: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Import image download tasks + ansible.builtin.import_tasks: + file: get_images.yml + when: proxmox_instance_type == "vm" + +- name: Import instance creation tasks + ansible.builtin.import_tasks: + file: vm_template.yml + when: proxmox_instance_type == "vm" + +- name: Import instance configuration tasks + ansible.builtin.import_tasks: + file: vm_configuration.yml + when: proxmox_instance_type == "vm" diff --git a/tasks/vm_configuration.yml b/tasks/vm_configuration.yml new file mode 100644 index 0000000..ee2ed29 --- /dev/null +++ b/tasks/vm_configuration.yml @@ -0,0 +1,149 @@ +--- +# tasks file for proxmox + +- name: Build disks list + ansible.builtin.set_fact: + proxmox_instance_disks: "{{ proxmox_instance_disks + specific }}" + when: specific | length > 0 + loop: "{{ lookup('ansible.builtin.varnames', '^proxmox_instance_disks_.+', wantlist=True) }}" + vars: + specific: "{{ lookup('ansible.builtin.vars', item, default='') }}" + +- name: Configure disks + community.general.proxmox_disk: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + aio: "{{ item.aio | default(omit) }}" + backup: "{{ item.backup | default(omit) }}" + cache: "{{ item.cache | default(omit) }}" + disk: "{{ item.disk }}" + iothread: "{{ item.iothread | default(omit) }}" + mbps: "{{ item.mbps | default(omit) }}" + name: "{{ inventory_hostname }}" + storage: "{{ item.storage }}" + size: "{{ item.size }}" + state: present + vmid: "{{ proxmox_instance_vmid }}" + loop: "{{ proxmox_instance_disks }}" + loop_control: + label: "{{ item.disk }}" + index_var: disk_number + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Get instance informations + community.general.proxmox_vm_info: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + config: current + name: "{{ inventory_hostname }}" + vmid: "{{ proxmox_instance_vmid }}" + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + register: instance_current_infos + +- name: Grow disk size + community.general.proxmox_disk: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + aio: "{{ item.aio | default(omit) }}" + backup: "{{ item.backup | default(omit) }}" + cache: "{{ item.cache | default(omit) }}" + disk: "{{ item.disk }}" + iothread: "{{ item.iothread | default(omit) }}" + mbps: "{{ item.mbps | default(omit) }}" + name: "{{ inventory_hostname }}" + storage: "{{ item.storage }}" + size: "{{ item.size }}G" + state: resized + vmid: "{{ proxmox_instance_vmid }}" + when: + - proxmox_instance_disks | length > 0 + - instance_current_infos.proxmox_vms | length > 0 + - formated_size not in instance_current_infos.proxmox_vms[0].config[item.disk] + loop: "{{ proxmox_instance_disks }}" + loop_control: + label: "{{ item.disk }}" + index_var: disk_number + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + vars: + formated_size: "size={{ item.size }}G" + +- name: Reconfigure instance + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + agent: "enabled=1,fstrim_cloned_disks=1" + autostart: "{{ proxmox_instance_autostart }}" + cores: "{{ proxmox_instance_cores }}" + cpu: "{{ proxmox_instance_cpu }}" + hotplug: "{{ proxmox_instance_hotplug | join(',') }}" + ipconfig: "{{ proxmox_instance_ipconfig }}" + memory: "{{ proxmox_instance_memory }}" + name: "{{ inventory_hostname }}" + nameservers: "{{ proxmox_instance_cloudinit_nameservers }}" + node: "{{ proxmox_instance_node }}" + numa_enabled: "{{ proxmox_instance_numa }}" + onboot: "{{ proxmox_instance_onboot }}" + protection: "{{ proxmox_instance_protection }}" + tablet: false + vmid: "{{ proxmox_instance_vmid }}" + update: true + update_unsafe: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Get changed information about the instance + community.general.proxmox_vm_info: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + config: pending + name: "{{ inventory_hostname }}" + vmid: "{{ proxmox_instance_vmid }}" + node: "{{ proxmox_instance_node }}" + when: not create_instance.changed + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + register: instance_pending_infos + +- name: Reboot the instance + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + name: "{{ inventory_hostname }}" + node: "{{ proxmox_instance_node }}" + state: restarted + timeout: 300 + when: + - proxmox_reboot_instance + - not create_instance.changed + - instance_current_infos.proxmox_vms[0].config != instance_pending_infos.proxmox_vms[0].config + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + notify: Attendre que le port SSH soit ouvert + register: rebooted + +- name: Flush handlers + ansible.builtin.meta: flush_handlers + +- name: Ensure instance is started + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + name: "{{ inventory_hostname }}" + node: "{{ proxmox_instance_node }}" + state: started + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + notify: Attendre que le port SSH soit ouvert + +- name: Flush handlers + ansible.builtin.meta: flush_handlers diff --git a/tasks/vm_template.yml b/tasks/vm_template.yml new file mode 100644 index 0000000..12c8b4b --- /dev/null +++ b/tasks/vm_template.yml @@ -0,0 +1,71 @@ +--- +# tasks file for proxmox + +- name: Create instance + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + agent: "enabled=1,fstrim_cloned_disks=1" + autostart: "{{ proxmox_instance_autostart }}" + cores: "{{ proxmox_instance_cores }}" + cpu: "{{ proxmox_instance_cpu }}" + hotplug: "{{ proxmox_instance_hotplug | join(',') }}" + ipconfig: "{{ proxmox_instance_ipconfig }}" + memory: "{{ proxmox_instance_memory }}" + name: "{{ inventory_hostname }}" + nameservers: "{{ proxmox_instance_cloudinit_nameservers }}" + net: "{{ proxmox_instance_net }}" + node: "{{ proxmox_instance_node }}" + numa_enabled: "{{ proxmox_instance_numa }}" + onboot: "{{ proxmox_instance_onboot }}" + scsihw: virtio-scsi-single + tablet: false + vmid: "{{ proxmox_instance_vmid }}" + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + register: create_instance + notify: + - Configure cloud-init + - Start instance + - Wait SSH port is open + - Wait cloud-init + +- name: Import virtual disk + ansible.builtin.command: + cmd: >- + qm set {{ proxmox_instance_vmid }} + --{{ proxmox_instance_disks[0].disk }} + {{ proxmox_instance_disks[0].storage }}:0,import-from={{ proxmox_instance_disks[0].img }} + chdir: "{{ proxmox_instance_disks[0].img | ansible.builtin.dirname }}" + when: create_instance.changed # noqa: no-handler no-changed-when + become: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Workaround to add args to the VM + ansible.builtin.lineinfile: + path: /etc/pve/qemu-server/{{ proxmox_instance_vmid }}.conf + line: "args: {{ proxmox_instance_args }}" + state: present + when: proxmox_instance_args | length > 0 + become: true + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + +- name: Add configuration items + community.general.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + boot: order=virtio0 + name: "{{ inventory_hostname }}" + node: "{{ proxmox_instance_node }}" + serial: + serial0: socket + update: true + update_unsafe: true + vmid: "{{ proxmox_instance_vmid }}" + delegate_to: "{{ proxmox_delegate_to | default(omit) }}" + notify: + - Start instance + - Wait SSH port is open