|
|
|
@ -0,0 +1,380 @@ |
|
|
|
## |
|
|
|
## 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 |
|
|
|
|
|
|
|
## |
|
|
|
## Parameters of the VM to create |
|
|
|
## |
|
|
|
|
|
|
|
libvirt_domain_parameters: |
|
|
|
name: test-vm |
|
|
|
mac: 52:54:00:6b:3c:58 |
|
|
|
network: default |
|
|
|
rhel_version: 9 |
|
|
|
ram: 2048 |
|
|
|
vcpu: 2 |
|
|
|
disk: 20 |
|
|
|
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 |
|
|
|
# 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_parameters.mac }}' |
|
|
|
- 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] }}" |
|
|
|
digest_with_algo: "{{ layer_info.digest }}" |
|
|
|
destination_filename: "{{ layer_info.annotations['org.opencontainers.image.title'] }}" |
|
|
|
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" |
|
|
|
|
|
|
|
## |
|
|
|
## 4.3. Add the VM to Ansible inventory |
|
|
|
## |
|
|
|
- block: |
|
|
|
- debug: |
|
|
|
var: add_host_params |
|
|
|
when: libvirt_domains_debug | bool |
|
|
|
|
|
|
|
- name: Add the VM to Ansible inventory |
|
|
|
ansible.builtin.add_host: '{{ add_host_params }}' |
|
|
|
|
|
|
|
vars: |
|
|
|
add_host_params: '{{ original_args | combine(extra_arg_ip) | combine(extra_arg_hostname) }}' |
|
|
|
extra_arg_hostname: '{{ {"hostname": libvirt_domain_name } if "name" not in original_args and "hostname" not in original_args and "host" not in original_args else {} }}' |
|
|
|
extra_arg_ip: '{{ {"ansible_ssh_host": vm_ip } if "ansible_ssh_host" not in original_args else {} }}' |
|
|
|
vm_ip: '{{ libvirt_domain_domifaddr_external_ip | ansible.utils.ipaddr("ipv4") | list | first }}' |
|
|
|
original_args: '{{ libvirt_domain.add_host }}' |
|
|
|
when: > |
|
|
|
libvirt_domain.state | default("present") == "present" |
|
|
|
|
|
|
|
## |
|
|
|
## 5. Configure the VM |
|
|
|
## |
|
|
|
|
|
|
|
- name: Configure a RHEL VM created with cloud-init |
|
|
|
hosts: vm |
|
|
|
gather_facts: no |
|
|
|
tasks: |
|
|
|
- wait_for_connection: |
|
|
|
|
|
|
|
- ansible.builtin.setup: |
|
|
|
|
|
|
|
- debug: |
|
|
|
msg: "Hello from {{ inventory_hostname }} in {{ group_names | join(',') }} !" |