public inbox for kdevops@lists.linux.dev
 help / color / mirror / Atom feed
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


  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