From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D83B127585F for ; Fri, 30 May 2025 17:52:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748627556; cv=none; b=SOEiZBwPkcefLntCqPJHM6hI7euZn7yWo+5wRdy6ngFZos3gfUcZFit6kNDCUqYOpQsb6aV6GLyfaC2tF+CC6mdMQ3tbOC7/+tn7tHPZEZC2dmoetGCbwkaETVsdP9S/UAnHMrxT+7DSejRYlromHOkyMLZODEvOPK8QO/HvZ9o= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1748627556; c=relaxed/simple; bh=wSKPpdeez2lPlb3ma6ycOAgFjRGZQKWfTO7GnjDIwz4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BSpgYBQ11FRHdSzvKcIzQiAAeGaOjDjE80xvXMfHZcBysZE4zhDwj/jbvBRCebJU0gjaBlWG/pHjP9vvZKyFA7cYMSsdV3aayGNV7VrIUEHgrXI2tqL/2FTkOOpPnB3WX1J99lpHQmULVEoIMtIKghVPMc92HdQF9ZGC+Vo23tI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=P6C0gCrH; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="P6C0gCrH" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1A173C4CEED; Fri, 30 May 2025 17:52:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1748627556; bh=wSKPpdeez2lPlb3ma6ycOAgFjRGZQKWfTO7GnjDIwz4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P6C0gCrHeD/0XbVClbBCfMRamWLGMX/dbXrzHoCIces7N7zBFWeCfbpPW9GI8rr2k E6TIAmcki/avH+DdV1CygEdB0nBQvmN1Pm4a0v36OyT+aOrVKvVE5kT5WnDaOQH9Dt 4RirBxKqFVisKHNFCevfX6eb5d1gA2Xx9hnX5ak/3Sj5SEWdkC/N2+XepRXcmXTFXP 1i+1f2cxValk1RngeMNTHEdswKc69gn1SZvPAVVJrkqfgr5kpvkKc4M+uNwx3U1b9c /IFVIUyG8Ez3wBDf7bfIk4Vri7YpflIoVYw03FKMTbhQLQFwdXu06I9+vFVwBQgtxh CMfeEE3yMuXnA== From: Chuck Lever To: Cc: Chuck Lever Subject: [PATCH v2 07/12] Add a base-image role Date: Fri, 30 May 2025 13:52:24 -0400 Message-ID: <20250530175229.489925-8-cel@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250530175229.489925-1-cel@kernel.org> References: <20250530175229.489925-1-cel@kernel.org> Precedence: bulk X-Mailing-List: kdevops@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Chuck Lever 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 --- 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