Creating a new vm with libvirt - am I doing this right?

Hi,
New user here, but not a new user of Ansible. I am pretty new to running things elsewhere than on bare metal, and last weekend I created a role to create a Debian virtual machine because I ran out of bare metal to play with :).
However, it does not ‘feel’ right, and my online searches do not result in practical information how to set this up. I am now using a mixture of

  • libvirt to manage the vm
  • ansible commands such as virt-customize to configure the vm
  • netplan via ansible copy to set up the vm’s network

But knowing that cloud-init makes configuration a breeze, I was hoping to use ansible to template the cloud-init configuration and be done with it, but just don’t find that option… Am I overlooking something obvious? Below the most important bits from the role:

- name: Get VMs list
  community.libvirt.virt:
    command: list_vms
  register: existing_vms
  changed_when: no

- name: Create VM if not exists
  when: "vm_name not in existing_vms.list_vms"
  block:
  - name: Download base image
    ansible.builtin.get_url:
      url: "{{ base_image_url }}"
      dest: "/tmp/{{ base_image_name }}"

  - name: Copy base image to libvirt directory
    ansible.builtin.copy:
      src: "/tmp/{{ base_image_name }}"
      dest: "{{ libvirt_pool_dir }}/{{ vm_name }}.qcow2"
      force: no
      remote_src: yes
      mode: 0660
    register: copy_results

  - name: Copy netplan config to /tmp
    ansible.builtin.copy:
      src: netplan_eth_dhcp.config
      dest: /tmp/00-installer.yaml
      force: no
      mode: '660'

  - name: Configure the image (1/3)
    ansible.builtin.command: |
      virt-customize -a {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 \
      --hostname {{ vm_name }} \
      --root-password password:{{ vm_root_pass }} \
      --ssh-inject 'root:file:{{ ssh_key }}' \
      --install openvswitch-switch-dpdk,console-data,console-setup \
      --copy-in /tmp/00-installer.yaml:/etc/netplan \
      --chmod 600:/etc/netplan/00-installer.yaml \
      --timezone {{ site.tz }} \
      --run-command 'touch /etc/default/keyboard' \
      --edit '/etc/default/keyboard: s/^XKBLAYOUT=.*/XKBLAYOUT="be"/'

  - name: Configure the image (2/3)
    ansible.builtin.command: |
      virt-customize -a {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 \
      --run-command 'ssh-keygen -A'

  - name: Configure the image (3/3)
    ansible.builtin.command: |
      virt-customize -a {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 \
      --firstboot-command 'netplan apply'

  - name: Define vm
    community.libvirt.virt:
      command: define
      xml: "{{ lookup('template', 'vm-template.xml.j2') }}"

  - name: Grow qcow2 disk size
    ansible.builtin.command: qemu-img resize {{ libvirt_pool_dir }}/{{ vm_name }}.qcow2 +{{ vm_disk_size_plus }}

- name: Ensure VM is started
  community.libvirt.virt:
    name: "{{ vm_name }}"
    state: running
  register: vm_start_results
  until: "vm_start_results is success"
  retries: 15
  delay: 2

- name: Ensure temporary file is deleted
  ansible.builtin.file:
    path: "/tmp/{{ base_image_name }}"
    state: absent
  when: cleanup_tmp | bool

- name: Ensure VM is restarted (1/2) - workaround bug
  community.libvirt.virt:
    name: "{{ vm_name }}"
    command: destroy
  register: libvirt_status
  until: libvirt_status.status is defined and libvirt_status.status == 'shutdown'
  retries: 20
  delay: 10

- name: Ensure VM is restarted (2/2) - workaround bug
  community.libvirt.virt:
    name: "{{ vm_name }}"
    command: start

Thank you!