All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.