6 changed files with 460 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||
|
[defaults] |
||||
|
# Use the provided inventory |
||||
|
inventory=inventory |
||||
|
# Enable the do keyword in Jinja2 |
||||
|
jinja2_extensions = jinja2.ext.do |
||||
@ -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(',') }} !" |
||||
@ -0,0 +1,2 @@ |
|||||
|
[rhde] |
||||
|
optiplex-7000.itix.fr ansible_user=demo ansible_become=yes |
||||
@ -0,0 +1,2 @@ |
|||||
|
instance-id: "{{ libvirt_domain.name }}" |
||||
|
local-hostname: "{{ libvirt_domain.name }}" |
||||
@ -0,0 +1,43 @@ |
|||||
|
#cloud-config |
||||
|
|
||||
|
users: |
||||
|
- name: demo |
||||
|
gecos: Demo |
||||
|
groups: wheel |
||||
|
lock_passwd: false |
||||
|
passwd: $6$pkjw0DZirHVbQuBW$U/D84I3BVGutAOyg2GmOGPcHTptM4nFhULLuzpwwUQ400eiYonbVVfaqDts7AB3VgFsPR/4P6BsMD90811n6S0 |
||||
|
ssh_authorized_keys: |
||||
|
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPR1tt58X0+vbvsCR12gMAqr+g7vjt1Fx/qqz9EiboIs nicolas@localhost.localdomain |
||||
|
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFW62WJXI1ZCMfNA4w0dMpL0fsldhbEfULNGIUB0nQui nmasse@localhost.localdomain |
||||
|
|
||||
|
write_files: |
||||
|
- path: /etc/sudoers |
||||
|
content: | |
||||
|
Defaults !visiblepw |
||||
|
Defaults always_set_home |
||||
|
Defaults match_group_by_gid |
||||
|
Defaults always_query_group_plugin |
||||
|
Defaults env_reset |
||||
|
Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS" |
||||
|
Defaults env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE" |
||||
|
Defaults env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES" |
||||
|
Defaults env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE" |
||||
|
Defaults env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY" |
||||
|
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin |
||||
|
root ALL=(ALL) ALL |
||||
|
%wheel ALL=(ALL) NOPASSWD: ALL |
||||
|
#includedir /etc/sudoers.d |
||||
|
permissions: '0440' |
||||
|
append: false |
||||
|
- path: /etc/ssh/sshd_config.d/00-demo.conf |
||||
|
content: | |
||||
|
KbdInteractiveAuthentication no |
||||
|
GSSAPIAuthentication no |
||||
|
PasswordAuthentication no |
||||
|
PermitRootLogin prohibit-password |
||||
|
KerberosAuthentication no |
||||
|
permissions: '0440' |
||||
|
append: false |
||||
|
|
||||
|
runcmd: |
||||
|
- systemctl disable --now --no-block rpcbind.socket |
||||
@ -0,0 +1,28 @@ |
|||||
|
{%- set argv = [ "virt-install", "--noautoconsole", "--name=" ~ libvirt_domain.name] -%} |
||||
|
{%- for param in libvirt_domain.virt_install -%} |
||||
|
{%- for param2, values in param.items() -%} |
||||
|
{%- set prefix = "--" ~ param2 -%} |
||||
|
{%- if values is string or values is number -%} |
||||
|
{%- do argv.append(prefix ~ "=" ~ (values)) -%} |
||||
|
{%- elif values is mapping -%} |
||||
|
{%- set arglist = [] -%} |
||||
|
{%- for item in values | dict2items -%} |
||||
|
{%- set arg = item.key -%} |
||||
|
{%- set value = item.value -%} |
||||
|
{%- if value is none -%} |
||||
|
{%- do arglist.append(arg) -%} |
||||
|
{%- elif value.__class__.__name__ == 'list' -%} |
||||
|
{%- for v in value -%} |
||||
|
{%- do arglist.append(arg ~ "=" ~ v) -%} |
||||
|
{%- endfor -%} |
||||
|
{%- else -%} |
||||
|
{%- do arglist.append(arg ~ "=" ~ value) -%} |
||||
|
{%- endif -%} |
||||
|
{%- endfor -%} |
||||
|
{%- do argv.append(prefix ~ "=" ~ arglist | join(",")) -%} |
||||
|
{%- else -%} |
||||
|
{%- do argv.append(prefix) -%} |
||||
|
{%- endif -%} |
||||
|
{%- endfor -%} |
||||
|
{%- endfor -%} |
||||
|
{{ argv }} |
||||
Loading…
Reference in new issue