virt-v2v/0020-convert_linux-install-...

410 lines
17 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 21309da26e0647c00c16cfb374fa418991b432aa Mon Sep 17 00:00:00 2001
From: Laszlo Ersek <lersek@redhat.com>
Date: Mon, 13 Jun 2022 19:01:35 +0200
Subject: [PATCH] convert_linux: install the QEMU guest agent with a firstboot
script
Register a firstboot script, for installing the guest agent with the
guest's own package manager -- that is, "Guest_packages.install_command".
For installing the package, network connectivity is required. Check it
first with "nmcli" (also checking whether NetworkManager is running), then
with "systemd-networkd-wait-online" (dependent on systemd-networkd). Note
that NetworkManager and systemd-networkd are never supposed to be enabled
at the same time.
The source domain's SELinux policy may not allow our firstboot service to
execute the package's installation scripts (if any). For that reason,
temporarily disable SELinux around package installation.
After installation, register another script for launching the agent.
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2028764
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20220613170135.12557-5-lersek@redhat.com>
Reviewed-by: Richard W.M. Jones <rjones@redhat.com>
(cherry picked from commit e64356896377af1ac75a03d6a4c6a4208910bbf4)
---
common | 2 +-
convert/convert_linux.ml | 78 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 77 insertions(+), 3 deletions(-)
Submodule common 48527b87..9e990f3e:
diff --git a/common/mlcustomize/Makefile.am b/common/mlcustomize/Makefile.am
index cd7d8971..4e260647 100644
--- a/common/mlcustomize/Makefile.am
+++ b/common/mlcustomize/Makefile.am
@@ -38,10 +38,12 @@ generator_built = \
SOURCES_MLI = \
firstboot.mli \
+ guest_packages.mli \
SELinux_relabel.mli
SOURCES_ML = \
firstboot.ml \
+ guest_packages.ml \
SELinux_relabel.ml
if HAVE_OCAML
diff --git a/common/mlcustomize/customize-options.pod b/common/mlcustomize/customize-options.pod
index a83c80a5..8aafacde 100644
--- a/common/mlcustomize/customize-options.pod
+++ b/common/mlcustomize/customize-options.pod
@@ -310,6 +310,10 @@ It cannot delete directories, only regular files.
=back
+=item B<--selinux-relabel>
+
+This is a compatibility option that does nothing.
+
=item B<--sm-attach> SELECTOR
Attach to a pool using C<subscription-manager>.
diff --git a/common/mlcustomize/customize-synopsis.pod b/common/mlcustomize/customize-synopsis.pod
index 25208538..9e2c4b2b 100644
--- a/common/mlcustomize/customize-synopsis.pod
+++ b/common/mlcustomize/customize-synopsis.pod
@@ -13,4 +13,4 @@
[--uninstall PKG,PKG..] [--update] [--upload FILE:DEST]
[--write FILE:CONTENT] [--no-logfile]
[--password-crypto md5|sha256|sha512] [--no-selinux-relabel]
- [--sm-credentials SELECTOR]
+ [--selinux-relabel] [--sm-credentials SELECTOR]
diff --git a/common/mlcustomize/customize_cmdline.ml b/common/mlcustomize/customize_cmdline.ml
index 5d404e84..a17bed40 100644
--- a/common/mlcustomize/customize_cmdline.ml
+++ b/common/mlcustomize/customize_cmdline.ml
@@ -111,6 +111,8 @@ and flags = {
(* --password-crypto md5|sha256|sha512 *)
no_selinux_relabel : bool;
(* --no-selinux-relabel *)
+ selinux_relabel_ignored : bool;
+ (* --selinux-relabel *)
sm_credentials : Subscription_manager.sm_credentials option;
(* --sm-credentials SELECTOR *)
}
@@ -122,6 +124,7 @@ let rec argspec () =
let scrub_logfile = ref false in
let password_crypto = ref None in
let no_selinux_relabel = ref false in
+ let selinux_relabel_ignored = ref false in
let sm_credentials = ref None in
let rec get_ops () = {
@@ -132,6 +135,7 @@ let rec argspec () =
scrub_logfile = !scrub_logfile;
password_crypto = !password_crypto;
no_selinux_relabel = !no_selinux_relabel;
+ selinux_relabel_ignored = !selinux_relabel_ignored;
sm_credentials = !sm_credentials;
}
in
@@ -464,6 +468,12 @@ let rec argspec () =
s_"Do not relabel files with correct SELinux labels"
),
None, "Do not attempt to correct the SELinux labels of files in the guest.\n\nIn such guests that support SELinux, customization automatically\nrelabels files so that they have the correct SELinux label. (The\nrelabeling is performed immediately, but if the operation fails,\ncustomization will instead touch F</.autorelabel> on the image to\nschedule a relabel operation for the next time the image boots.) This\noption disables the automatic relabeling.\n\nThe option is a no-op for guests that do not support SELinux.";
+ (
+ [ L"selinux-relabel" ],
+ Getopt.Set selinux_relabel_ignored,
+ s_"Compatibility option doing nothing"
+ ),
+ None, "This is a compatibility option that does nothing.";
(
[ L"sm-credentials" ],
Getopt.String (
diff --git a/common/mlcustomize/customize_cmdline.mli b/common/mlcustomize/customize_cmdline.mli
index 7ee882a6..7d14e782 100644
--- a/common/mlcustomize/customize_cmdline.mli
+++ b/common/mlcustomize/customize_cmdline.mli
@@ -103,6 +103,8 @@ and flags = {
(* --password-crypto md5|sha256|sha512 *)
no_selinux_relabel : bool;
(* --no-selinux-relabel *)
+ selinux_relabel_ignored : bool;
+ (* --selinux-relabel *)
sm_credentials : Subscription_manager.sm_credentials option;
(* --sm-credentials SELECTOR *)
}
diff --git a/common/mlcustomize/guest_packages.ml b/common/mlcustomize/guest_packages.ml
new file mode 100644
index 00000000..4c3c34ed
--- /dev/null
+++ b/common/mlcustomize/guest_packages.ml
@@ -0,0 +1,132 @@
+(* virt-customize
+ * Copyright (C) 2012-2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Std_utils
+
+exception Unknown_package_manager of string
+exception Unimplemented_package_manager of string
+
+(* Windows has package_management == "unknown". *)
+let error_unknown_package_manager flag =
+ let msg = sprintf (f_"cannot use %s because no package manager has been \
+ detected for this guest OS.\n\nIf this guest OS is a \
+ common one with ordinary package management then this \
+ may have been caused by a failure of libguestfs \
+ inspection.\n\nFor OSes such as Windows that lack \
+ package management, this is not possible. Try using \
+ one of the --firstboot* flags instead (described in \
+ the virt-customize(1) manual).") flag in
+ raise (Unknown_package_manager msg)
+
+let error_unimplemented_package_manager flag pm =
+ let msg = sprintf (f_"sorry, %s with the %s package manager has not \
+ been implemented yet.\n\nYou can work around this by \
+ using one of the --run* or --firstboot* options \
+ instead (described in the virt-customize(1) manual).")
+ flag pm in
+ raise (Unimplemented_package_manager msg)
+
+(* http://distrowatch.com/dwres.php?resource=package-management *)
+let install_command packages package_management =
+ let quoted_args = String.concat " " (List.map quote packages) in
+ match package_management with
+ | "apk" ->
+ sprintf "
+ apk update
+ apk add %s
+ " quoted_args
+ | "apt" ->
+ (* http://unix.stackexchange.com/questions/22820 *)
+ sprintf "
+ export DEBIAN_FRONTEND=noninteractive
+ apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
+ apt-get $apt_opts update
+ apt-get $apt_opts install %s
+ " quoted_args
+ | "dnf" ->
+ sprintf "dnf%s -y install %s"
+ (if verbose () then " --verbose" else "")
+ quoted_args
+ | "pisi" -> sprintf "pisi it %s" quoted_args
+ | "pacman" -> sprintf "pacman -S --noconfirm %s" quoted_args
+ | "urpmi" -> sprintf "urpmi %s" quoted_args
+ | "xbps" -> sprintf "xbps-install -Sy %s" quoted_args
+ | "yum" -> sprintf "yum -y install %s" quoted_args
+ | "zypper" -> sprintf "zypper -n in -l %s" quoted_args
+
+ | "unknown" ->
+ error_unknown_package_manager (s_"--install")
+ | pm ->
+ error_unimplemented_package_manager (s_"--install") pm
+
+let update_command package_management =
+ match package_management with
+ | "apk" ->
+ "
+ apk update
+ apk upgrade
+ "
+ | "apt" ->
+ (* http://unix.stackexchange.com/questions/22820 *)
+ "
+ export DEBIAN_FRONTEND=noninteractive
+ apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
+ apt-get $apt_opts update
+ apt-get $apt_opts upgrade
+ "
+ | "dnf" ->
+ sprintf "dnf%s -y --best upgrade"
+ (if verbose () then " --verbose" else "")
+ | "pisi" -> "pisi upgrade"
+ | "pacman" -> "pacman -Su"
+ | "urpmi" -> "urpmi --auto-select"
+ | "xbps" -> "xbps-install -Suy"
+ | "yum" -> "yum -y update"
+ | "zypper" -> "zypper -n update -l"
+
+ | "unknown" ->
+ error_unknown_package_manager (s_"--update")
+ | pm ->
+ error_unimplemented_package_manager (s_"--update") pm
+
+let uninstall_command packages package_management =
+ let quoted_args = String.concat " " (List.map quote packages) in
+ match package_management with
+ | "apk" -> sprintf "apk del %s" quoted_args
+ | "apt" ->
+ (* http://unix.stackexchange.com/questions/22820 *)
+ sprintf "
+ export DEBIAN_FRONTEND=noninteractive
+ apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
+ apt-get $apt_opts remove %s
+ " quoted_args
+ | "dnf" -> sprintf "dnf -y remove %s" quoted_args
+ | "pisi" -> sprintf "pisi rm %s" quoted_args
+ | "pacman" -> sprintf "pacman -R %s" quoted_args
+ | "urpmi" -> sprintf "urpme %s" quoted_args
+ | "xbps" -> sprintf "xbps-remove -Sy %s" quoted_args
+ | "yum" -> sprintf "yum -y remove %s" quoted_args
+ | "zypper" -> sprintf "zypper -n rm %s" quoted_args
+
+ | "unknown" ->
+ error_unknown_package_manager (s_"--uninstall")
+ | pm ->
+ error_unimplemented_package_manager (s_"--uninstall") pm
diff --git a/common/mlcustomize/guest_packages.mli b/common/mlcustomize/guest_packages.mli
new file mode 100644
index 00000000..7504a6ab
--- /dev/null
+++ b/common/mlcustomize/guest_packages.mli
@@ -0,0 +1,44 @@
+(* virt-customize
+ * Copyright (C) 2012-2021 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+exception Unknown_package_manager of string
+exception Unimplemented_package_manager of string
+(** For all three functions below, [package_management] determines the package
+ management system in use by the guest; commonly it should be filled in from
+ [Guestfs.inspect_get_package_management], or the equivalent guestfs object
+ method.
+
+ If [package_management] is unknown or unimplemented, the functions raise
+ [Unknown_package_manager "error message"] or [Unimplemented_package_manager
+ "error message"], correspondingly. *)
+
+val install_command : string list -> string -> string
+(** [install_command packages package_management] produces a properly quoted
+ shell command string suitable for execution in the guest (directly or via a
+ Firstboot script) for installing the OS packages listed in [packages]. *)
+
+val update_command : string -> string
+(** [update_command package_management] produces a properly quoted shell command
+ string suitable for execution in the guest (directly or via a Firstboot
+ script) for updating the OS packages that are currently installed in the
+ guest. *)
+
+val uninstall_command : string list -> string -> string
+(** [uninstall_command packages package_management] produces a properly quoted
+ shell command string suitable for execution in the guest (directly or via a
+ Firstboot script) for uninstalling the OS packages listed in [packages]. *)
diff --git a/convert/convert_linux.ml b/convert/convert_linux.ml
index 2ddbc07a..59d143bd 100644
--- a/convert/convert_linux.ml
+++ b/convert/convert_linux.ml
@@ -562,8 +562,82 @@ let convert (g : G.guestfs) source inspect keep_serial_console _ =
name = qga_pkg
) inspect.i_apps in
if not has_qemu_guest_agent then
- (* FIXME -- install qemu-guest-agent here *)
- ()
+ try
+ let inst_cmd = Guest_packages.install_command [qga_pkg]
+ inspect.i_package_management in
+
+ (* Use only the portable filename character set in this. *)
+ let selinux_enforcing = "/root/virt-v2v-fb-selinux-enforcing"
+ and timeout = 30 in
+ let fbs =
+ Firstboot.add_firstboot_script g inspect.i_root
+ in
+ info (f_"The QEMU Guest Agent will be installed for this guest at \
+ first boot.");
+
+ (* Wait for the network to come online in the guest (best effort).
+ *)
+ fbs "wait online"
+ (sprintf "#!/bin/sh\n\
+ if conn=$(nmcli networking connectivity); then\n\
+ \ \ tries=0\n\
+ \ \ while\n\
+ \ \ \ \ test $tries -lt %d &&\n\
+ \ \ \ \ test full != \"$conn\"\n\
+ \ \ do\n\
+ \ \ \ \ sleep 1\n\
+ \ \ \ \ tries=$((tries + 1))\n\
+ \ \ \ \ conn=$(nmcli networking connectivity)\n\
+ \ \ done\n\
+ elif systemctl -q is-active systemd-networkd; then\n\
+ \ \ /usr/lib/systemd/systemd-networkd-wait-online \\\n\
+ \ \ \ \ -q --timeout=%d\n\
+ fi\n" timeout timeout);
+
+ (* Disable SELinux temporarily around package installation. Refer to
+ * <https://bugzilla.redhat.com/show_bug.cgi?id=2028764#c7> and
+ * <https://bugzilla.redhat.com/show_bug.cgi?id=2028764#c8>.
+ *)
+ fbs "setenforce 0"
+ (sprintf "#!/bin/sh\n\
+ rm -f %s\n\
+ if command -v getenforce >/dev/null &&\n\
+ \ \ test Enforcing = \"$(getenforce)\"\n\
+ then\n\
+ \ \ touch %s\n\
+ \ \ setenforce 0\n\
+ fi\n" selinux_enforcing selinux_enforcing);
+ fbs "install qga" inst_cmd;
+ fbs "setenforce restore"
+ (sprintf "#!/bin/sh\n\
+ if test -f %s; then\n\
+ \ \ setenforce 1\n\
+ \ \ rm -f %s\n\
+ fi\n" selinux_enforcing selinux_enforcing);
+
+ (* Start the agent now and at subsequent boots. The following
+ * commands should work on both sysvinit distros / distro versions
+ * (regardless of "/etc/rc.d/" vs. "/etc/init.d/" being the scheme
+ * in use) and systemd distros (via redirection to systemctl).
+ *
+ * On distros where the chkconfig command is redirected to
+ * systemctl, the chkconfig command is likely superfluous. That's
+ * because on systemd distros, the QGA package comes with such
+ * runtime dependencies / triggers that the presence of the
+ * virtio-serial port named "org.qemu.guest_agent.0" automatically
+ * starts the agent during (second and later) boots. However, even
+ * on such distros, the chkconfig command should do no harm.
+ *)
+ fbs "start qga"
+ (sprintf "#!/bin/sh\n\
+ service %s start\n\
+ chkconfig %s on\n" qga_pkg qga_pkg)
+ with
+ | Guest_packages.Unknown_package_manager msg
+ | Guest_packages.Unimplemented_package_manager msg ->
+ warning (f_"The QEMU Guest Agent will not be installed. The \
+ install command for package %s could not be created: \
+ %s.") qga_pkg msg
and configure_kernel () =
(* Previously this function would try to install kernels, but we
--
2.31.1