Skip to content

Add Cloud-init and TPM Support #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Requirements
The host should have Virtualization Technology (VT) enabled and should
be preconfigured with libvirt/KVM.

`genisoimage` is required for cloud-init support.

`swtpm` and `swtpm-tools` packages are required for TPM support.

Role Variables
--------------

Expand Down Expand Up @@ -61,6 +65,9 @@ Role Variables
This gets mapped to the `trustGuestRxFilters` attribute of VM interfaces.
Default is `false`

- `libvirt_vm_cloud_init_dir`: Directory in which cloud-init files are
stored. Default is '{{ libvirt_volume_default_images_path }}/cloud-init'.

- `libvirt_vms`: list of VMs to be created/destroyed. Each one may have the
following attributes:

Expand Down Expand Up @@ -172,10 +179,16 @@ Role Variables
interfaces. Default is `libvirt_vm_trust_guest_rx_filters`.
- `model`: The name of the interface model. Eg. `e1000` or `ne2k_pci`, if undefined
it defaults to `virtio`.
- `alias`: An optional interface alias. This can be used to tie specific network
configuration to persistent network devices via name. The user defined alias is
always prefixed with `ua-` to be compliant (aliases without `ua-` are ignored by libvirt.
If undefined it defaults to libvirt managed `vnetX`.
- `alias`: An optional interface alias. When cloud-init is enabled, this is required
and used as the interface name in the guest. The alias is
always prefixed with `ua-` to be compliant (aliases without `ua-` are ignored by libvirt).
If undefined it defaults to libvirt managed `vnetX`.
- `address`: Optional static IP address in CIDR notation (e.g., "192.168.1.100/24").
Requires cloud-init to be enabled.
- `gateway`: Optional gateway IP address for the interface.
Requires cloud-init to be enabled.
- `nameservers`: Optional list of DNS nameservers.
Requires cloud-init to be enabled.
- `console_log_enabled`: if `true`, log console output to a file at the
path specified by `console_log_path`, **instead of** to a PTY. If
`false`, direct terminal output to a PTY at serial port 0. Default is
Expand All @@ -192,6 +205,21 @@ Role Variables

- `boot_firmware`: Can be one of: `bios`, or `efi`. Defaults to `bios`.

- `tpm_enabled`: Whether to enable TPM for this VM. Default is `false`.

- `tpm_version`: TPM version to use. Can be '1.2' or '2.0'. Default is '2.0'.

- `cloud_init_enabled`: Whether to enable cloud-init for this VM. Default is `false`.

- `cloud_init_user_data`: Cloud-init user configuration in YAML format.
You can find examples of the format here: [Cloud-init User Data Examples](https://docs.cloud-init.io/en/latest/reference/examples.html)

- `cloud_init_meta_data`: Optional cloud-init metadata in YAML format.

- `cloud_init_network_config`: Optional custom network configuration in YAML format.
If not specified, network configuration will be generated from interface settings.
You can find examples of the format here: [Cloud-init Network Configuration](https://docs.cloud-init.io/en/latest/reference/network-config-format-v2.html)

- `xml_file`: Optionally supply a modified XML template. Base customisation
off the default `vm.xml.j2` template so as to include the expected jinja
expressions the role uses.
Expand Down Expand Up @@ -274,14 +302,35 @@ Example Playbook
type: 'file'
file_path: '/srv/cloud/images'
capacity: '900GB'
tpm_enabled: true
tpm_version: "2.0"
cloud_init_enabled: true
cloud_init_user_data:
users:
- name: myuser
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-rsa AAAAB...
cloud_init_meta_data:
foo: bar
interfaces:
- type: 'direct'
source:
dev: 'eth123'
mode: 'private'
mac: '00:11:22:33:44:55'
alias: 'eth0'
address: '192.168.122.10/24'
gateway: '192.168.122.1'
nameservers:
- '8.8.8.8'
- '8.8.4.4'
- type: 'bridge'
source:
dev: 'br-datacentre'
mac: '00:11:22:33:44:56'
alias: 'eth1'
# This interface will use dhcp to get an ip address


Author Information
Expand Down
3 changes: 3 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ libvirt_vm_sudo: true
# Default CPU mode if libvirt_vm_cpu_mode or vm.cpu_mode is undefined
libvirt_cpu_mode_default: "{{ 'host-passthrough' if libvirt_vm_engine == 'kvm' else 'host-model' }}"

# Path where cloud-init files will be stored
libvirt_vm_cloud_init_dir: "{{ libvirt_volume_default_images_path }}/cloud-init"

### DEPRECATED ###
# Use the above settings for each item within `libvirt_vms`, instead of the
# below deprecated variables.
Expand Down
12 changes: 12 additions & 0 deletions tasks/check-interface.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@
- interface.type == 'direct'
- interface.source is not defined or
interface.source.dev is not defined

- name: Validate network configuration requirements
ansible.builtin.fail:
msg: >
{% if vm.cloud_init_enabled | default(false) | bool and not (interface.alias is defined and interface.mac is defined) %}
When cloud-init is enabled, both 'alias' and 'mac' must be defined for all interfaces
{% elif not (vm.cloud_init_enabled | default(false) | bool) and (interface.address is defined or interface.gateway is defined or interface.nameservers is defined) %}
Cloud-init must be enabled when configuring network settings (address, gateway, or nameservers)
{% endif %}
when:
- vm.cloud_init_enabled | default(false) | bool and not (interface.alias is defined and interface.mac is defined) or
not (vm.cloud_init_enabled | default(false) | bool) and (interface.address is defined or interface.gateway is defined or interface.nameservers is defined)
57 changes: 57 additions & 0 deletions tasks/cloud-init.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# Tasks for generating cloud-init configuration and ISO

- name: Get VM UUID
ansible.builtin.command:
cmd: virsh -q domuuid {{ vm.name }}
register: vm_uuid
changed_when: false
failed_when: false

- name: Ensure VM-specific cloud-init directory exists
ansible.builtin.file:
path: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}"
state: directory
mode: '0755'
become: "{{ libvirt_vm_sudo }}"

- name: Generate cloud-init meta-data file
ansible.builtin.template:
src: cloud-init/meta-data.j2
dest: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/meta-data"
mode: '0644'
become: "{{ libvirt_vm_sudo }}"
register: meta_data_task

- name: Generate cloud-init user-data file
ansible.builtin.template:
src: cloud-init/user-data.j2
dest: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/user-data"
mode: '0644'
become: "{{ libvirt_vm_sudo }}"
register: user_data_task

- name: Generate cloud-init network-config file
ansible.builtin.template:
src: cloud-init/network-config.j2
dest: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/network-config"
mode: '0644'
become: "{{ libvirt_vm_sudo }}"
register: network_config_task

- name: Check if cloud-init ISO exists
ansible.builtin.stat:
path: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/cloud-init.iso"
become: "{{ libvirt_vm_sudo }}"
register: cloud_init_iso_exists

- name: Generate cloud-init ISO
ansible.builtin.command:
cmd: >-
genisoimage -output {{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/cloud-init.iso
-volid cidata -joliet -rock -input-charset utf-8
{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/meta-data
{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/user-data
{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/network-config
become: "{{ libvirt_vm_sudo }}"
when: meta_data_task.changed or user_data_task.changed or network_config_task.changed or not cloud_init_iso_exists.stat.exists
8 changes: 7 additions & 1 deletion tasks/destroy-vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
register: result
become: true

- name: Destory the VM is existing
- name: Destroy the VM if existing
block:
- name: Ensure the VM is absent
community.libvirt.virt:
Expand All @@ -33,4 +33,10 @@
{{ vm.name }}
become: true
changed_when: true

- name: Remove cloud-init directory
ansible.builtin.file:
path: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}"
state: absent
become: true
when: vm.name in result.list_vms
13 changes: 13 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@
loop_var: vm
when: (vm.state | default('present', true)) == 'present'

- name: Remove cloud-init directory if disabled
ansible.builtin.file:
path: "{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}"
state: absent
mode: '0755'
become: "{{ libvirt_vm_sudo }}"
with_items: "{{ libvirt_vms }}"
loop_control:
loop_var: vm
when: >-
not (vm.cloud_init_enabled | default(false) | bool)
and (vm.state | default('present', true)) == 'present'

- include_tasks: destroy-vm.yml
vars:
boot_firmware: "{{ vm.boot_firmware | default('bios', true) | lower }}"
Expand Down
4 changes: 4 additions & 0 deletions tasks/vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
uri: "{{ libvirt_vm_uri | default(omit, true) }}"
become: "{{ libvirt_vm_sudo }}"

- name: Include cloud-init tasks
ansible.builtin.include_tasks: cloud-init.yml
when: vm.cloud_init_enabled | default(false) | bool

- name: Ensure the VM is running and started at boot
community.libvirt.virt:
name: "{{ vm.name }}"
Expand Down
7 changes: 7 additions & 0 deletions templates/cloud-init/meta-data.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% if vm_uuid.rc == 0 %}
instance-id: {{ vm_uuid.stdout }}
{% else %}
instance-id: {{ vm.name }}
{% endif %}
local-hostname: {{ vm.name }}
{{ vm.cloud_init_meta_data | default({}) | to_nice_yaml(indent=2) }}
32 changes: 32 additions & 0 deletions templates/cloud-init/network-config.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
version: 2
{% if vm.cloud_init_network_config is defined %}
{{ vm.cloud_init_network_config | to_nice_yaml(indent=2) }}
{% else %}
ethernets:
{% for interface in interfaces %}
{% if interface.alias is defined %}
{{ interface.alias }}:
match:
macaddress: "{{ interface.mac }}"
set-name: {{ interface.alias }}
{% if interface.address is defined %}
addresses:
- {{ interface.address }}
{% if interface.gateway is defined %}
routes:
- to: default
via: {{ interface.gateway }}
{% endif %}
{% if interface.nameservers is defined %}
nameservers:
addresses: {{ interface.nameservers }}
{% endif %}
{% else %}
dhcp4: true
{% endif %}
{% else %}
eth{{ loop.index0 }}:
dhcp4: true
{% endif %}
{% endfor %}
{% endif %}
2 changes: 2 additions & 0 deletions templates/cloud-init/user-data.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#cloud-config
{{ vm.cloud_init_user_data | default({}) | to_nice_yaml(indent=2) }}
15 changes: 14 additions & 1 deletion templates/vm.xml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,20 @@
<source pool='{{ volume.pool }}' volume='{{ volume.name }}'/>
{% endif %}
{% if volume.target is undefined %}
<target dev='vd{{ 'abcdefghijklmnopqrstuvwxyz'[loop.index - 1] }}' {% if volume.device | default(libvirt_volume_default_device) == 'cdrom' %}bus='sata'{% endif %}/>
<target dev='vd{{ 'abcdefghijklmnopqrstuvwxy'[loop.index - 1] }}' {% if volume.device | default(libvirt_volume_default_device) == 'cdrom' %}bus='sata'{% endif %}/>
{% else %}
<target dev='{{ volume.target }}' {% if volume.device | default(libvirt_volume_default_device) == 'cdrom' %}bus='sata'{% endif %}/>
{% endif %}
</disk>
{% endfor %}
{% if vm.cloud_init_enabled | default(false) | bool %}
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='{{ libvirt_vm_cloud_init_dir }}/{{ vm.name }}/cloud-init.iso'/>
<target dev='vdz' bus='sata'/>
<readonly/>
</disk>
{% endif %}
{% for interface in interfaces %}
{% if interface.type is defined and interface.type == 'direct' %}
<interface type='direct' {% if interface.trust_guest_rx_filters | default(libvirt_vm_trust_guest_rx_filters) | bool %}trustGuestRxFilters='yes'{% endif %}>
Expand Down Expand Up @@ -137,6 +145,11 @@
</source>
</hostdev>
{% endfor %}
{% if vm.tpm_enabled | default(false) | bool %}
<tpm model='tpm-tis'>
<backend type='emulator' version='{{ vm.tpm_version | default("2.0") }}'/>
</tpm>
{% endif %}
<rng model="virtio"><backend model="random">/dev/urandom</backend></rng>
</devices>
</domain>