From: Chuck Lever <cel@kernel.org>
To: <kdevops@lists.linux.dev>
Cc: Chuck Lever <chuck.lever@oracle.com>
Subject: [PATCH v2 07/12] Add a base-image role
Date: Fri, 30 May 2025 13:52:24 -0400 [thread overview]
Message-ID: <20250530175229.489925-8-cel@kernel.org> (raw)
In-Reply-To: <20250530175229.489925-1-cel@kernel.org>
From: Chuck Lever <chuck.lever@oracle.com>
Add a base-image role that ensures a base OS image exists for
libvirt to use when provisioning guests. Copy the steps from
scripts/bringup_guestfs.sh.
This procedure is maintained outside of the guestfs role in
order to reduce the complexity of guestfs and base-image.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
playbooks/base_image.yml | 7 +
playbooks/roles/base_image/README.md | 45 +++++
playbooks/roles/base_image/defaults/main.yml | 7 +
.../roles/base_image/tasks/base-image.yml | 80 +++++++++
.../roles/base_image/tasks/custom-image.yml | 159 ++++++++++++++++++
playbooks/roles/base_image/tasks/main.yml | 19 +++
.../base_image/templates/custom-index.j2 | 2 +
.../base_image/templates/custom-source.j2 | 3 +
.../base_image/templates/virt-builder.j2 | 77 +++++++++
9 files changed, 399 insertions(+)
create mode 100644 playbooks/base_image.yml
create mode 100644 playbooks/roles/base_image/README.md
create mode 100644 playbooks/roles/base_image/defaults/main.yml
create mode 100644 playbooks/roles/base_image/tasks/base-image.yml
create mode 100644 playbooks/roles/base_image/tasks/custom-image.yml
create mode 100644 playbooks/roles/base_image/tasks/main.yml
create mode 100644 playbooks/roles/base_image/templates/custom-index.j2
create mode 100644 playbooks/roles/base_image/templates/custom-source.j2
create mode 100644 playbooks/roles/base_image/templates/virt-builder.j2
diff --git a/playbooks/base_image.yml b/playbooks/base_image.yml
new file mode 100644
index 000000000000..41126844ab30
--- /dev/null
+++ b/playbooks/base_image.yml
@@ -0,0 +1,7 @@
+---
+- name: Create a libvirt base OS image
+ gather_facts: false
+ connection: local
+ hosts: localhost
+ roles:
+ - role: base_image
diff --git a/playbooks/roles/base_image/README.md b/playbooks/roles/base_image/README.md
new file mode 100644
index 000000000000..e279005e9c44
--- /dev/null
+++ b/playbooks/roles/base_image/README.md
@@ -0,0 +1,45 @@
+base_image
+==========
+
+The base_image role manages libvirt base OS images. These images
+contain an installed operating system and are used to quickly
+create new libvirt guests with virt-sysprep.
+
+Requirements
+------------
+
+Network access to the public libvirt image repositories. The
+virt-builder program must be installed.
+
+Role Variables
+--------------
+
+ * base_image_os_version: OS to install on the image
+ * base_image_pathname: pathname of local file to contain the image
+
+Dependencies
+------------
+
+None.
+
+Example Playbook
+----------------
+
+Below is an example playbook task:
+
+```
+- name: Create /test/nfs if needed
+ ansible.builtin.import_role:
+ name: base_image
+ vars:
+ base_image_os_version: "fedora-39"
+ base_image_pathname: "/var/lib/libvirt/images/kdevops/base-images/fedora-39.raw"
+```
+
+For further examples refer to one of this role's users, the
+[https://github.com/linux-kdevops/kdevops](kdevops) project.
+
+License
+-------
+
+copyleft-next-0.3.1
diff --git a/playbooks/roles/base_image/defaults/main.yml b/playbooks/roles/base_image/defaults/main.yml
new file mode 100644
index 000000000000..dc9e8f6617d3
--- /dev/null
+++ b/playbooks/roles/base_image/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+libvirt_uri_system: false
+
+guestfs_copy_sources_from_host_to_guest: false
+guestfs_has_custom_raw_image: false
+kdevops_uid: ""
+update_grub_cmd: "/usr/sbin/update-grub2"
diff --git a/playbooks/roles/base_image/tasks/base-image.yml b/playbooks/roles/base_image/tasks/base-image.yml
new file mode 100644
index 000000000000..84971b611ec0
--- /dev/null
+++ b/playbooks/roles/base_image/tasks/base-image.yml
@@ -0,0 +1,80 @@
+---
+- name: Gather facts
+ ansible.builtin.gather_facts:
+
+- name: Get the UID of the kdevops user on the control host
+ ansible.builtin.command:
+ cmd: "id -u kdevops"
+ register: id_output
+ changed_when: false
+ failed_when: false
+
+- name: Set the kdevops UID in the base image
+ ansible.builtin.set_fact:
+ kdevops_uid: "-u {{ id_output.stdout }}"
+ when:
+ - id_output.rc == 0
+
+- name: Select the grub command for the base image (Red Hat)
+ ansible.builtin.set_fact:
+ update_grub_cmd: "/usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg"
+ when:
+ - base_image_os_version is match("^(rhel|fedora|centos)")
+
+- name: Create a temporary file for virt-builder commands
+ ansible.builtin.tempfile:
+ state: file
+ register: command_file
+
+- name: Construct the virt-builder command file
+ ansible.builtin.template:
+ src: "{{ role_path }}/templates/virt-builder.j2"
+ dest: "{{ command_file.path }}"
+ mode: "u=rw"
+
+- name: Generate a new base image for {{ base_image_os_version }}
+ become: true
+ become_method: ansible.builtin.sudo
+ ansible.builtin.command:
+ argv:
+ - "virt-builder"
+ - "{{ base_image_os_version }}"
+ - "--arch"
+ - "{{ ansible_machine }}"
+ - "-o"
+ - "{{ base_image_pathname }}"
+ - "--size"
+ - "20G"
+ - "--format"
+ - "raw"
+ - "--commands-from-file"
+ - "{{ command_file.path }}"
+ creates: "{{ base_image_pathname }}"
+ when:
+ - libvirt_uri_system|bool
+
+- name: Generate a new base image for {{ base_image_os_version }}
+ ansible.builtin.command:
+ argv:
+ - "virt-builder"
+ - "{{ base_image_os_version }}"
+ - "--arch"
+ - "{{ ansible_machine }}"
+ - "-o"
+ - "{{ base_image_pathname }}"
+ - "--size"
+ - "20G"
+ - "--format"
+ - "raw"
+ - "--commands-from-file"
+ - "{{ command_file.path }}"
+ creates: "{{ base_image_pathname }}"
+ when:
+ - not libvirt_uri_system|bool
+
+- name: Clean up the virt-builder command file
+ ansible.builtin.file:
+ path: "{{ command_file.path }}"
+ state: absent
+ when:
+ - command_file.path is defined
diff --git a/playbooks/roles/base_image/tasks/custom-image.yml b/playbooks/roles/base_image/tasks/custom-image.yml
new file mode 100644
index 000000000000..ede44bd7da4d
--- /dev/null
+++ b/playbooks/roles/base_image/tasks/custom-image.yml
@@ -0,0 +1,159 @@
+---
+- name: Set the pathname of the custom image directory
+ ansible.builtin.set_fact:
+ custom_image_dir: "{{ kdevops_storage_pool_path }}/guestfs/custom_images/{{ base_image_os_version }}"
+
+- name: Ensure the custom image directory exists
+ ansible.builtin.file:
+ path: "{{ custom_image_dir }}"
+ state: directory
+ mode: "u=rwx,g=rx,o=rx"
+
+- name: Set the pathname of the custom image
+ ansible.builtin.set_fact:
+ custom_image: "{{ custom_image_dir }}/{{ base_image_os_version }}.raw"
+
+- name: Set the pathname of the custom image sentinel
+ ansible.builtin.set_fact:
+ custom_image_ok: "{{ custom_image_dir }}.ok"
+
+- name: Set the pathname of the custom source configuration file
+ ansible.builtin.set_fact:
+ custom_source: "/etc/virt-builder/repos.d/kdevops-custom-images-{{ base_image_os_version }}.conf"
+
+- name: Set the pathname of the custom index file
+ ansible.builtin.set_fact:
+ custom_index: "{{ custom_image_dir | realpath }}/index"
+
+- name: Check if the custom image file already exists
+ ansible.builtin.stat:
+ path: "{{ custom_image }}"
+ get_attributes: false
+ get_checksum: false
+ get_mime: false
+ register: result
+
+- name: Fetch the custom image
+ ansible.builtin.get_url:
+ url: "{{ guestfs_custom_raw_image_url }}"
+ dest: "{{ custom_image_dir }}"
+ mode: "u=rw,g=r,o=r"
+ when:
+ - not result.stat.exists
+ - guestfs_has_custom_raw_image_url|bool
+
+- name: Check if the custom image sentinel file already exists
+ ansible.builtin.stat:
+ path: "{{ custom_image_ok }}"
+ get_attributes: false
+ get_checksum: false
+ get_mime: false
+ register: result
+
+- name: Check the custom image
+ when:
+ - not result.stat.exists
+ - guestfs_has_custom_raw_image_sha512sums|bool
+ block:
+ - name: Get the base name of the sha512sums file
+ ansible.builtin.set_fact:
+ sha512sums_file: "{{ guestfs_custom_raw_image_sha512sums_url | basename }}"
+
+ - name: Set the full pathname of sha512sums file
+ ansible.builtin.set_fact:
+ custom_image_sha512sum: "{{ custom_image_dir }}/{{ sha512sums_file }}"
+
+ - name: Check if the sha512sums file already exists
+ ansible.builtin.stat:
+ path: "{{ custom_image_sha512sum }}"
+ get_attributes: false
+ get_checksum: false
+ get_mime: false
+ register: result
+
+ - name: Fetch the sha512sums file
+ ansible.builtin.get_url:
+ url: "{{ guestfs_custom_raw_image_sha512sums_url }}"
+ dest: "{{ custom_image_dir }}"
+ mode: "u=rw,g=r,o=r"
+ when:
+ - not result.stat.exists
+
+ - name: Compute checksum of something
+ ansible.builtin.command:
+ cmd: "sha512sum --ignore-missing -c {{ sha512sums_file }}"
+ chdir: "{{ custom_image_dir }}"
+ changed_when: false
+
+ - name: Touch the custom image sentinel
+ ansible.builtin.file:
+ path: "{{ custom_image_ok }}"
+ state: touch
+ mode: "u=rw,g=r,o=r"
+
+- name: Check if the custom source exists
+ ansible.builtin.stat:
+ path: "{{ custom_source }}"
+ get_attributes: false
+ get_checksum: false
+ get_mime: false
+ register: result
+
+- name: Build the custom source
+ ansible.builtin.template:
+ src: "{{ role_path }}/templates/custom-source.j2"
+ dst: "{{ custom_source }}"
+ mode: "u=rw,g=r,o=r"
+ when:
+ - not result.stat.exists
+
+- name: Check if the custom index exists
+ ansible.builtin.stat:
+ path: "{{ custom_index }}"
+ get_attributes: false
+ get_checksum: false
+ get_mime: false
+ register: result
+
+- name: Build the custom index
+ ansible.builtin.template:
+ src: "{{ role_path }}/templates/custom-index.j2"
+ dst: "{{ custom_index }}"
+ mode: "u=rw,g=r,o=r"
+ when:
+ - not result.stat.exists
+
+- name: Show rolling distribution release warning
+ ansible.builtin.debug:
+ msg: |
+ ------------------------------------------------------------------
+ This is a rolling distribution release! To upgrade just do:
+
+ rm -rf {{ custom_image }}/*
+ rm -f {{ custom_source }}
+ rm -f {{ custom_index }}
+
+ Running guests always use their own copy. To rebuild your custom
+ base image from the custom image, also remove the base image:
+
+ rm -f ${BASE_IMAGE}
+
+ This can always be done safely without affecting running guests.
+ ------------------------------------------------------------------
+ when:
+ - guestfs_has_custom_raw_image_rolling|bool
+
+- name: Show the custom virt-builder database
+ ansible.builtin.debug:
+ msg: |
+ Custom virt-builder source: {{ custom_source }}
+ Custom virt-builder index: {{ custom_index }}
+ Custom virt-builder image: {{ custom_image }}
+
+- name: Generating the index for {{ base_image_os_version }}
+ ansible.builtin.command:
+ argv:
+ - "virt-builder-repository"
+ - "--no-compression"
+ - "{{ custom_image_dir }}"
+ changed_when: true
diff --git a/playbooks/roles/base_image/tasks/main.yml b/playbooks/roles/base_image/tasks/main.yml
new file mode 100644
index 000000000000..a708fd8dff29
--- /dev/null
+++ b/playbooks/roles/base_image/tasks/main.yml
@@ -0,0 +1,19 @@
+---
+- name: Stat {{ base_image_pathname }}
+ ansible.builtin.stat:
+ path: "{{ base_image_pathname }}"
+ get_checksum: false
+ get_mime: false
+ register: result
+
+- name: Create custom upstream OS image
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/tasks/custom_image.yml"
+ when:
+ - guestfs_has_custom_raw_image|bool
+
+- name: Create the base OS image
+ ansible.builtin.include_tasks:
+ file: "{{ role_path }}/tasks/base-image.yml"
+ when:
+ - not result.stat.exists
diff --git a/playbooks/roles/base_image/templates/custom-index.j2 b/playbooks/roles/base_image/templates/custom-index.j2
new file mode 100644
index 000000000000..32edd8f9e1e5
--- /dev/null
+++ b/playbooks/roles/base_image/templates/custom-index.j2
@@ -0,0 +1,2 @@
+[{{ base_image_os_version }}]
+file={{ base_image_os_version }}.raw
diff --git a/playbooks/roles/base_image/templates/custom-source.j2 b/playbooks/roles/base_image/templates/custom-source.j2
new file mode 100644
index 000000000000..cb4af83fb5f3
--- /dev/null
+++ b/playbooks/roles/base_image/templates/custom-source.j2
@@ -0,0 +1,3 @@
+[local]
+uri=file:///{{ custom_index }}
+proxy=off
diff --git a/playbooks/roles/base_image/templates/virt-builder.j2 b/playbooks/roles/base_image/templates/virt-builder.j2
new file mode 100644
index 000000000000..6abb7e6643af
--- /dev/null
+++ b/playbooks/roles/base_image/templates/virt-builder.j2
@@ -0,0 +1,77 @@
+{% if rhel_org_id is defined %}
+run-command subscription-manager register --org={{ rhel_org_id }} --activationkey={{ rhel_activation_key }}
+{% endif %}
+
+{% if kdevops_custom_yum_repofile is defined and kdevops_custom_yum_repofile != "" %}
+copy-in {{ kdevops_custom_yum_repofile }}:/etc/yum.repos.d
+{% endif %}
+
+{% if guestfs_copy_sources_from_host_to_guest %}
+mkdir {{ target_dir }}
+copy-in {{ guestfs_distro_source_and_dest_file }}:{{ target_dir }}
+{% endif %}
+
+install sudo,qemu-guest-agent,python3,bash
+run-command useradd {{ kdevops_uid }} -s /bin/bash -m kdevops
+append-line /etc/sudoers.d/kdevops:kdevops ALL=(ALL) NOPASSWD: ALL
+edit /etc/default/grub:s/^GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0"/
+run-command {{ update_grub_cmd }}
+root-password password:kdevops
+
+{% if rhel_org_id is defined %}
+sm-unregister
+{% endif %}
+
+{% if distro_debian_based is defined and distro_debian_based %}
+{# Ugh, debian has to be told to bring up the network and regenerate ssh keys #}
+{# Hope we get that interface name right! #}
+install isc-dhcp-client,ifupdown
+mkdir /etc/network/interfaces.d/
+append-line /etc/network/interfaces.d/enp1s0:auto enp1s0
+append-line /etc/network/interfaces.d/enp1s0:allow-hotplug enp1s0
+append-line /etc/network/interfaces.d/enp1s0:iface enp1s0 inet dhcp
+firstboot-command systemctl disable systemd-networkd-wait-online.service
+firstboot-command systemctl stop ssh
+firstboot-command DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true dpkg-reconfigure -p low --force openssh-server
+firstboot-command systemctl start ssh
+firstboot-command apt update && apt upgrade --yes
+uninstall unattended-upgrades
+
+{% if distro_debian_trixie is defined and distro_debian_trixie %}
+{# CONFIG_GUESTFS_COPY_SOURCES_FROM_HOST_TO_GUEST will not work #}
+{# if /etc/nsswitch.conf has a line like this: #}
+{# #}
+{# hosts: files myhostname resolve [!UNAVAIL=return] dns #}
+{# #}
+{# We need DNS to be used so virb0 will be used for a DNS request #}
+{# For the life of me I can't get the following line to work with #}
+{# the virt-builder command and so we do a full edit of the file for now #}
+{# edit /etc/nsswitch.conf:'s/\[!UNAVAIL=return\]//' #}
+write /etc/nsswitch.conf: # kdevops generated /etc/nsswitch.conf
+append-line /etc/nsswitch.conf:passwd: files
+append-line /etc/nsswitch.conf:group: files
+append-line /etc/nsswitch.conf:shadow: files
+append-line /etc/nsswitch.conf:gshadow: files
+append-line /etc/nsswitch.conf:hosts: files myhostname resolve dns
+append-line /etc/nsswitch.conf:networks: files
+append-line /etc/nsswitch.conf:protocols: db files
+append-line /etc/nsswitch.conf:services: db files
+append-line /etc/nsswitch.conf:ethers: db files
+append-line /etc/nsswitch.conf:rpc: db files
+append-line /etc/nsswitch.conf:netgroup: nis
+uninstall cloud-init
+write /etc/default/locale:LANG=en_US.UTF-8
+append-line /etc/default/locale:LANGUAGE=en_US:en
+write /etc/locale.gen:en_US.UTF-8 UTF-8
+firstboot-command locale-gen en_US.UTF-8
+firstboot-command update-locale LANG=en_US.UTF-8
+firstboot-command DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true dpkg-reconfigure -p low --force locales
+firstboot-command systemctl stop ssh
+firstboot-command systemctl start ssh
+
+{% if guestfs_copy_sources_from_host_to_guest %}
+delete /etc/apt/sources.list.d/debian.sources
+{% endif %}
+
+{% endif %}
+{% endif %}
--
2.49.0
next prev parent reply other threads:[~2025-05-30 17:52 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-30 17:52 [PATCH v2 00/12] Convert bringup_guestfs to a single Ansible role Chuck Lever
2025-05-30 17:52 ` [PATCH v2 01/12] guestfs: Replace scripts/destroy_guestfs.sh with an Ansible playbook Chuck Lever
2025-05-30 17:52 ` [PATCH v2 02/12] Move the guestfs install-deps to the guestfs playbook Chuck Lever
2025-05-30 17:52 ` [PATCH v2 03/12] guestfs: Do not use the config-check tag Chuck Lever
2025-05-30 17:52 ` [PATCH v2 04/12] guestfs: Add a "bringup" tag to the guestfs role Chuck Lever
2025-05-30 17:52 ` [PATCH v2 05/12] guestfs: Copy "network" tag steps to " Chuck Lever
2025-05-30 17:52 ` [PATCH v2 06/12] guestfs: Move the QEMU_GROUP check Chuck Lever
2025-05-30 17:52 ` Chuck Lever [this message]
2025-05-30 17:52 ` [PATCH v2 08/12] guestfs: Convert scripts/bringup_guestfs.sh to Ansible Chuck Lever
2025-05-30 17:52 ` [PATCH v2 09/12] guestfs: Move console-related steps to guestfs role Chuck Lever
2025-05-30 17:52 ` [PATCH v2 10/12] bringup_guestfs: Remove the role Chuck Lever
2025-05-30 17:52 ` [PATCH v2 11/12] scripts: Remove the bringup_guestfs.sh script Chuck Lever
2025-05-30 17:52 ` [PATCH v2 12/12] scripts: Remove the destroy_guestfs.sh script Chuck Lever
2025-06-03 19:29 ` [PATCH v2 00/12] Convert bringup_guestfs to a single Ansible role Luis Chamberlain
2025-06-04 14:29 ` Chuck Lever
2025-06-04 17:02 ` Luis Chamberlain
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250530175229.489925-8-cel@kernel.org \
--to=cel@kernel.org \
--cc=chuck.lever@oracle.com \
--cc=kdevops@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox