1 changed files with 348 additions and 0 deletions
@ -0,0 +1,348 @@ |
|||
## |
|||
## Pre-requisites on the Ansible Controller |
|||
## |
|||
## $ dnf install -y ansible python3-jmespath python3-netaddr |
|||
## |
|||
- name: Create a VM with cloud-init and using an OCI artefact as disk source |
|||
hosts: rhde |
|||
gather_facts: false |
|||
## |
|||
## Pre-requisites on the managed node |
|||
## |
|||
## $ dnf install -y genisoimage |
|||
## |
|||
vars: |
|||
## |
|||
## Default values |
|||
## |
|||
libvirt_images_path: /var/lib/libvirt/images |
|||
libvirt_domains_debug: '{{ debug | default(True) | bool }}' |
|||
has_podman_artifact_extract: false |
|||
libvirt_domain_mac_address: "{{ '04:00:' ~ (libvirt_domain_parameters.ipv4_address | ansible.utils.ipv4('address') | ansible.utils.ip4_hex(':')) }}" |
|||
|
|||
## |
|||
## Parameters of the VM to create |
|||
## |
|||
|
|||
libvirt_domain_parameters: |
|||
name: "{( vm_name )}" |
|||
ipv4_address: "{( vm_ip )}"/24 |
|||
ipv4_gateway: 192.168.122.1 |
|||
ipv4_nameserver: 192.168.122.1 |
|||
network: default |
|||
rhel_version: "{( rhel_version )}" |
|||
ram: "{( vm_ram )}" |
|||
vcpu: "{( vm_vcpu )}" |
|||
disk: "{( vm_disk )}" |
|||
architecture: x86_64 |
|||
|
|||
## |
|||
## Template of the VM to create |
|||
## |
|||
|
|||
libvirt_domain: |
|||
name: '{{ libvirt_domain_parameters.name }}' |
|||
state: '{{ state | default("present") }}' |
|||
# Cloud-init sources |
|||
cloud_init: |
|||
templates: |
|||
- src: cloud-init/user-data.j2 |
|||
dest: user-data |
|||
- src: cloud-init/meta-data.j2 |
|||
dest: meta-data |
|||
- src: cloud-init/network-config.j2 |
|||
dest: network-config |
|||
# Root disk source |
|||
root_disk: |
|||
## |
|||
## HEADS UP ! The RHEL qcow2 is stored in an OCI registry as a Podman artifact |
|||
## |
|||
## $ podman artifact add edge-registry.itix.fr/demo-edge-retail/rhel9:x86_64 rhel-9.6-x86_64-kvm.qcow2 |
|||
## $ podman artifact push edge-registry.itix.fr/demo-edge-retail/rhel9:x86_64 |
|||
## $ podman artifact rm edge-registry.itix.fr/demo-edge-retail/rhel9:x86_64 |
|||
## |
|||
src: edge-registry.itix.fr/demo-edge-retail/rhel{{ libvirt_domain_parameters.rhel_version }}:{{ libvirt_domain_parameters.architecture }} |
|||
dest: root.qcow2 |
|||
# virt-install parameters |
|||
virt_install: |
|||
- autostart: |
|||
- cpu: host-passthrough |
|||
- vcpus: '{{ libvirt_domain_parameters.vcpu }}' |
|||
- ram: '{{ libvirt_domain_parameters.ram }}' |
|||
- os-variant: 'rhel{{ libvirt_domain_parameters.rhel_version }}-unknown' |
|||
- disk: |
|||
path: '{{ libvirt_images_path }}/{{ libvirt_domain_parameters.name }}/root.qcow2' |
|||
bus: virtio |
|||
serial: root |
|||
size: '{{ libvirt_domain_parameters.disk }}' |
|||
- network: |
|||
network: '{{ libvirt_domain_parameters.network }}' |
|||
mac.address: '{{ libvirt_domain_mac_address }}' |
|||
- console: |
|||
pty: |
|||
target.type: virtio |
|||
- serial: |
|||
pty: |
|||
- graphics: none |
|||
- disk: |
|||
path: '{{ libvirt_images_path }}/{{ libvirt_domain_parameters.name }}/cloud-init.iso' |
|||
readonly: on |
|||
- import: |
|||
- sysinfo: |
|||
system.serial: ds=nocloud |
|||
# Post-install: add_host parameters |
|||
add_host: |
|||
group: vm |
|||
ansible_user: demo |
|||
ansible_become: yes |
|||
ansible_ssh_common_args: -J {{ ansible_user }}@{{ inventory_hostname }} -o StrictHostKeyChecking=no |
|||
|
|||
tasks: |
|||
|
|||
## |
|||
## 1. Variable setup and debug |
|||
## |
|||
|
|||
- debug: |
|||
var: libvirt_domain |
|||
when: libvirt_domains_debug|bool |
|||
|
|||
- set_fact: |
|||
libvirt_domain_name: '{{ libvirt_domain.name }}' |
|||
libvirt_domain_path: '/var/lib/libvirt/images/{{ libvirt_domain.name }}' |
|||
libvirt_domain_action: none |
|||
|
|||
- community.libvirt.virt: |
|||
command: list_vms |
|||
register: virsh_list |
|||
|
|||
- community.libvirt.virt: |
|||
command: list_vms |
|||
state: running |
|||
register: virsh_list_running |
|||
|
|||
- set_fact: |
|||
libvirt_existing_domains: '{{ virsh_list.list_vms }}' |
|||
libvirt_running_domains: '{{ virsh_list_running.list_vms }}' |
|||
|
|||
- debug: |
|||
var: variables |
|||
vars: |
|||
variables: |
|||
libvirt_domain_name: '{{ libvirt_domain_name }}' |
|||
libvirt_domain_path: '{{ libvirt_domain_path }}' |
|||
libvirt_existing_domains: '{{ libvirt_existing_domains }}' |
|||
when: libvirt_domains_debug | bool |
|||
|
|||
## |
|||
## 2. Delete the VM if state is "absent" |
|||
## |
|||
|
|||
- block: |
|||
- name: Shutdown Libvirt domain |
|||
community.libvirt.virt: |
|||
name: '{{ libvirt_domain_name }}' |
|||
command: destroy |
|||
when: libvirt_domain_name in libvirt_running_domains |
|||
|
|||
- name: Delete Libvirt domain |
|||
community.libvirt.virt: |
|||
name: '{{ libvirt_domain_name }}' |
|||
command: undefine |
|||
|
|||
- name: Remove the domain files |
|||
ansible.builtin.file: |
|||
path: '{{ libvirt_domain_path }}' |
|||
state: absent |
|||
when: > |
|||
libvirt_domain.state | default("present") == "absent" |
|||
and libvirt_domain_name in libvirt_existing_domains |
|||
|
|||
## |
|||
## 3. Create the VM if state is "present" and the VM does not already exist |
|||
## |
|||
|
|||
- block: |
|||
## |
|||
## 3.1. Pre-requisites |
|||
## |
|||
- name: Ensure the domain root directory exists |
|||
file: |
|||
path: '{{ libvirt_domain_path }}' |
|||
owner: qemu |
|||
group: qemu |
|||
mode: 0700 |
|||
state: directory |
|||
|
|||
## |
|||
## 3.2. Cloud-init setup |
|||
## |
|||
- name: Create a directory for cloud-init |
|||
file: |
|||
path: '{{ libvirt_domain_path }}/cloud-init' |
|||
owner: qemu |
|||
group: qemu |
|||
mode: 0700 |
|||
state: directory |
|||
|
|||
- name: Template all cloud-init files |
|||
template: |
|||
dest: '{{ libvirt_domain_path }}/cloud-init/{{ cloud_init_template.dest }}' |
|||
src: '{{ cloud_init_template.src }}' |
|||
loop: '{{ libvirt_domain.cloud_init.templates }}' |
|||
loop_control: |
|||
label: '{{ cloud_init_template.dest }}' |
|||
loop_var: cloud_init_template |
|||
|
|||
- name: Create the cloud-init.iso |
|||
command: |
|||
cmd: genisoimage -output {{ target_file }} -volid cidata -joliet -rock {{ source_files | join(" ") }} |
|||
chdir: '{{ libvirt_domain_path }}/cloud-init' |
|||
creates: '{{ target_file }}' |
|||
vars: |
|||
target_file: '{{ libvirt_domain_path }}/cloud-init.iso' |
|||
source_files: '{{ [ libvirt_domain_path ~ "/cloud-init/" ] | product(libvirt_domain.cloud_init.templates | map(attribute="dest")) | map("join") | list }}' |
|||
|
|||
- name: Fix permissions on the cloud-init image |
|||
file: |
|||
path: '{{ libvirt_domain_path }}/cloud-init.iso' |
|||
owner: qemu |
|||
group: qemu |
|||
mode: 0600 |
|||
|
|||
## |
|||
## 3.3. Root disk setup |
|||
## |
|||
- name: Check if domain root disk exists |
|||
ansible.builtin.stat: |
|||
path: '{{ libvirt_domain_path }}/{{ libvirt_domain.root_disk.dest }}' |
|||
register: domain_root_disk |
|||
|
|||
- block: |
|||
- name: Pull the OCI artefact |
|||
command: |
|||
cmd: podman artifact pull {{ libvirt_domain.root_disk.src }} |
|||
environment: |
|||
REGISTRY_AUTH_FILE: /etc/ostree/auth.json |
|||
|
|||
- name: Extract the OCI artefact |
|||
command: |
|||
cmd: podman artifact extract {{ libvirt_domain.root_disk.src }} {{ libvirt_domain_path }}/{{ libvirt_domain.root_disk.dest }} |
|||
creates: '{{ libvirt_domain_path }}/{{ libvirt_domain.root_disk.dest }}' |
|||
environment: |
|||
REGISTRY_AUTH_FILE: /etc/ostree/auth.json |
|||
when: has_podman_artifact_extract | bool |
|||
|
|||
- block: |
|||
- name: Get artifact metadata |
|||
ansible.builtin.command: "podman artifact inspect {{ libvirt_domain.root_disk.src }}" |
|||
register: artifact_inspect_result |
|||
changed_when: false |
|||
|
|||
- name: Find blob in podman storage |
|||
ansible.builtin.find: |
|||
paths: /var/lib/containers/storage |
|||
patterns: "{{ blob_hash }}" |
|||
recurse: true |
|||
register: find_blob_result |
|||
|
|||
- name: Blob not found |
|||
ansible.builtin.fail: |
|||
msg: "Blob with hash {{ blob_hash }} cannot be found." |
|||
when: find_blob_result.matched == 0 |
|||
|
|||
- name: Copy blob to final destination |
|||
ansible.builtin.copy: |
|||
src: "{{ find_blob_result.files[0].path }}" |
|||
dest: '{{ libvirt_domain_path }}/{{ libvirt_domain.root_disk.dest }}' |
|||
remote_src: true |
|||
owner: qemu |
|||
group: qemu |
|||
mode: 0600 |
|||
|
|||
when: not has_podman_artifact_extract | bool |
|||
vars: |
|||
artifact_manifest: "{{ artifact_inspect_result.stdout | from_json }}" |
|||
layer_info: "{{ artifact_manifest.Manifest.layers[0] }}" |
|||
blob_hash: "{{ layer_info.digest | replace('sha256:', '') }}" |
|||
|
|||
when: not domain_root_disk.stat.exists |
|||
|
|||
## |
|||
## 3.4. VM creation |
|||
## |
|||
- block: |
|||
- debug: |
|||
var: virt_install_commandline_s |
|||
when: libvirt_domains_debug | bool |
|||
vars: |
|||
virt_install_commandline_s: '{{ virt_install_commandline | join(" ") }}' |
|||
|
|||
- name: Run virt-install |
|||
command: |
|||
argv: '{{ virt_install_commandline }}' |
|||
|
|||
- set_fact: |
|||
libvirt_domain_action: created |
|||
vars: |
|||
virt_install_commandline: '{{ lookup("template", "virt-install-cmdline.j2") }}' |
|||
|
|||
when: > |
|||
libvirt_domain.state | default("present") == "present" |
|||
and libvirt_domain_name not in libvirt_existing_domains |
|||
|
|||
## |
|||
## 4. Post-installation tasks |
|||
## |
|||
|
|||
- block: |
|||
## |
|||
## 4.1. Fetch the IP address of the VM |
|||
## |
|||
- name: Fetch addresses from the Qemu Guest agent |
|||
command: virsh domifaddr {{ libvirt_domain_name }} --source agent --full |
|||
environment: |
|||
LANG: C |
|||
LC_ALL: C |
|||
register: virsh_domifaddr |
|||
failed_when: > |
|||
virsh_domifaddr.rc != 0 or virsh_domifaddr.stdout_lines | length < 5 |
|||
or external_ip | length < 1 |
|||
until: "virsh_domifaddr is not failed" |
|||
retries: 100 |
|||
delay: 5 |
|||
|
|||
- set_fact: |
|||
libvirt_domain_domifaddr: '{{ addresses }}' |
|||
libvirt_domain_domifaddr_external_ip: '{{ external_ip }}' |
|||
|
|||
- debug: |
|||
var: variables |
|||
vars: |
|||
variables: |
|||
libvirt_domain_domifaddr: '{{ libvirt_domain_domifaddr }}' |
|||
libvirt_domain_domifaddr_external_ip: '{{ libvirt_domain_domifaddr_external_ip }}' |
|||
when: libvirt_domains_debug | bool |
|||
|
|||
vars: |
|||
domifaddr_output: '{{ virsh_domifaddr.stdout_lines }}' |
|||
addresses: > |
|||
{{ domifaddr_output[2:] | map("split", None) | community.general.json_query('[*].{"ifname": [0], "mac": [1], "proto": [2], "address": [3]}') }} |
|||
external_ip: '{{ addresses | rejectattr("ifname", "eq", "lo") | selectattr("proto", "eq", "ipv4") | map(attribute="address") | ansible.utils.ipaddr("address") | list }}' |
|||
when: > |
|||
libvirt_domain.state | default("present") == "present" |
|||
|
|||
## |
|||
## 4.2. Update SSH known_hosts |
|||
## |
|||
- name: Remove IP / hostnames of the previous VM from the SSH known_hosts file |
|||
local_action: |
|||
module: command |
|||
args: ssh-keygen -R {{ hostname }} |
|||
vars: |
|||
ansible_become: false |
|||
loop: '{{ libvirt_domain_domifaddr_external_ip | default([]) }}' |
|||
loop_control: |
|||
loop_var: hostname |
|||
label: '{{ hostname }}' |
|||
when: libvirt_domain_action == "created" |
|||
Loading…
Reference in new issue