From 2b208d84db080659badbb70044497aae040592e7 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 25 Mar 2014 10:46:08 +0000 Subject: [PATCH] Add virt-customize standalone tool. This includes some simple tests and a manual page. --- .gitignore | 3 + Makefile.am | 1 + builder/virt-builder.pod | 1 + customize/Makefile.am | 41 +++++- customize/main.ml | 222 +++++++++++++++++++++++++++++++ customize/test-virt-customize.sh | 32 +++++ customize/virt-customize.pod | 156 ++++++++++++++++++++++ fish/guestfish.pod | 1 + po-docs/ja/Makefile.am | 9 ++ po-docs/podfiles | 3 + po-docs/uk/Makefile.am | 9 ++ src/guestfs.pod | 3 +- sysprep/virt-sysprep.pod | 1 + 13 files changed, 474 insertions(+), 8 deletions(-) create mode 100644 customize/main.ml create mode 100755 customize/test-virt-customize.sh create mode 100644 customize/virt-customize.pod diff --git a/.gitignore b/.gitignore index a93eb95fb..0231a0468 100644 --- a/.gitignore +++ b/.gitignore @@ -95,7 +95,9 @@ Makefile.in /customize/customize_cmdline.mli /customize/customize-options.pod /customize/customize-synopsis.pod +/customize/stamp-virt-customize.pod /customize/virt-customize +/customize/virt-customize.1 /daemon/actions.h /daemon/errnostring.c /daemon/errnostring-gperf.c @@ -236,6 +238,7 @@ Makefile.in /html/virt-cat.1.html /html/virt-copy-in.1.html /html/virt-copy-out.1.html +/html/virt-customize.1.html /html/virt-df.1.html /html/virt-diff.1.html /html/virt-edit.1.html diff --git a/Makefile.am b/Makefile.am index 5b8a82e71..ce0f9adad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -239,6 +239,7 @@ HTMLFILES = \ html/virt-cat.1.html \ html/virt-copy-in.1.html \ html/virt-copy-out.1.html \ + html/virt-customize.1.html \ html/virt-df.1.html \ html/virt-diff.1.html \ html/virt-edit.1.html \ diff --git a/builder/virt-builder.pod b/builder/virt-builder.pod index 2429f6630..c6e66ff4f 100644 --- a/builder/virt-builder.pod +++ b/builder/virt-builder.pod @@ -1590,6 +1590,7 @@ L, L, L, L, +L, L, L, L, diff --git a/customize/Makefile.am b/customize/Makefile.am index 889fe1cea..2747adda8 100644 --- a/customize/Makefile.am +++ b/customize/Makefile.am @@ -18,9 +18,14 @@ include $(top_srcdir)/subdir-rules.mk EXTRA_DIST = \ - $(SOURCES) + $(SOURCES) \ + test-virt-customize.sh \ + virt-customize.pod -CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o +CLEANFILES = \ + *~ *.cmi *.cmo *.cmx *.cmxa *.o \ + stamp-virt-customize.pod \ + virt-customize virt-customize.1 generator_built = \ customize_cmdline.mli \ @@ -41,6 +46,7 @@ SOURCES = \ firstboot.mli \ hostname.ml \ hostname.mli \ + main.ml \ password.ml \ password.mli \ perl_edit.ml \ @@ -55,8 +61,12 @@ SOURCES = \ if HAVE_OCAML deps = \ + $(top_builddir)/fish/guestfish-uri.o \ $(top_builddir)/mllib/common_gettext.cmx \ $(top_builddir)/mllib/common_utils.cmx \ + $(top_builddir)/mllib/config.cmx \ + $(top_builddir)/mllib/uri-c.o \ + $(top_builddir)/mllib/uRI.cmx \ crypt-c.o if HAVE_OCAMLOPT @@ -76,7 +86,8 @@ ocaml_modules = \ random_seed \ timezone \ customize_cmdline \ - customize_run + customize_run \ + main if HAVE_OCAMLOPT OBJECTS += $(patsubst %,%.cmx,$(ocaml_modules)) @@ -84,9 +95,7 @@ else OBJECTS += $(patsubst %,%.cmo,$(ocaml_modules)) endif -# XXX virt-customize isn't a complete tool yet, so currently this is -# just a dummy target binary. -noinst_SCRIPTS = virt-customize +bin_SCRIPTS = virt-customize # -I $(top_builddir)/src/.libs is a hack which forces corresponding -L # option to be passed to gcc, so we don't try linking against an @@ -144,11 +153,29 @@ DEFAULT_INCLUDES = \ .c.o: $(CC) $(CFLAGS) $(PROF_CFLAGS) $(DEFAULT_INCLUDES) -c $< -o $@ +# Manual pages and HTML files for the website. +man_MANS = virt-customize.1 +noinst_DATA = $(top_builddir)/html/virt-customize.1.html + +virt-customize.1 $(top_builddir)/html/virt-customize.1.html: stamp-virt-customize.pod + +stamp-virt-customize.pod: virt-customize.pod $(top_srcdir)/customize/customize-synopsis.pod $(top_srcdir)/customize/customize-options.pod + $(PODWRAPPER) \ + --man virt-customize.1 \ + --html $(top_builddir)/html/virt-customize.1.html \ + --insert $(top_srcdir)/customize/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \ + --insert $(top_srcdir)/customize/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ + --license GPLv2+ \ + $< + touch $@ + # Tests. TESTS_ENVIRONMENT = $(top_builddir)/run --test -TESTS = +if ENABLE_APPLIANCE +TESTS = test-virt-customize.sh +endif check-valgrind: $(MAKE) VG="$(top_builddir)/run @VG@" check diff --git a/customize/main.ml b/customize/main.ml new file mode 100644 index 000000000..74ecb8ead --- /dev/null +++ b/customize/main.ml @@ -0,0 +1,222 @@ +(* virt-customize + * Copyright (C) 2014 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 Common_gettext.Gettext +open Common_utils + +open Printf + +module G = Guestfs + +let () = Random.self_init () + +let prog = Filename.basename Sys.executable_name + +let main () = + let debug_gc = ref false in + let domain = ref None in + let dryrun = ref false in + let files = ref [] in + let format = ref "auto" in + let quiet = ref false in + let libvirturi = ref "" in + let trace = ref false in + let verbose = ref false in + + let display_version () = + printf "virt-customize %s\n" Config.package_version; + exit 0 + and add_file arg = + let uri = + try URI.parse_uri arg + with Invalid_argument "URI.parse_uri" -> + eprintf "Error parsing URI '%s'. Look for error messages printed above.\n" arg; + exit 1 in + let format = match !format with "auto" -> None | fmt -> Some fmt in + files := (uri, format) :: !files + and set_domain dom = + if !domain <> None then ( + eprintf (f_"%s: --domain option can only be given once\n") prog; + exit 1 + ); + domain := Some dom + in + + let argspec = [ + "-a", Arg.String add_file, s_"file" ^ " " ^ s_"Add disk image file"; + "--add", Arg.String add_file, s_"file" ^ " " ^ s_"Add disk image file"; + "-c", Arg.Set_string libvirturi, s_"uri" ^ " " ^ s_"Set libvirt URI"; + "--connect", Arg.Set_string libvirturi, s_"uri" ^ " " ^ s_"Set libvirt URI"; + "--debug-gc", Arg.Set debug_gc, " " ^ s_"Debug GC and memory allocations (internal)"; + "-d", Arg.String set_domain, s_"domain" ^ " " ^ s_"Set libvirt guest name"; + "--domain", Arg.String set_domain, s_"domain" ^ " " ^ s_"Set libvirt guest name"; + "-n", Arg.Set dryrun, " " ^ s_"Perform a dry run"; + "--dryrun", Arg.Set dryrun, " " ^ s_"Perform a dry run"; + "--dry-run", Arg.Set dryrun, " " ^ s_"Perform a dry run"; + "--format", Arg.Set_string format, s_"format" ^ " " ^ s_"Set format (default: auto)"; + "--long-options", Arg.Unit display_long_options, " " ^ s_"List long options"; + "-q", Arg.Set quiet, " " ^ s_"Don't print log messages"; + "--quiet", Arg.Set quiet, " " ^ s_"Don't print log messages"; + "-v", Arg.Set verbose, " " ^ s_"Enable debugging messages"; + "--verbose", Arg.Set verbose, " " ^ s_"Enable debugging messages"; + "-V", Arg.Unit display_version, " " ^ s_"Display version and exit"; + "--version", Arg.Unit display_version, " " ^ s_"Display version and exit"; + "-x", Arg.Set trace, " " ^ s_"Enable tracing of libguestfs calls"; + ] in + let customize_argspec, get_customize_ops = + Customize_cmdline.argspec ~prog () in + let customize_argspec = + List.map (fun (spec, _, _) -> spec) customize_argspec in + let argspec = argspec @ customize_argspec in + let argspec = + let cmp (arg1, _, _) (arg2, _, _) = + let arg1 = skip_dashes arg1 and arg2 = skip_dashes arg2 in + compare (String.lowercase arg1) (String.lowercase arg2) + in + List.sort cmp argspec in + let argspec = Arg.align argspec in + long_options := argspec; + + let anon_fun _ = raise (Arg.Bad (s_"extra parameter on the command line")) in + let usage_msg = + sprintf (f_"\ +%s: customize a virtual machine + + virt-customize [--options] -d domname + + virt-customize [--options] -a disk.img [-a disk.img ...] + +A short summary of the options is given below. For detailed help please +read the man page virt-customize(1). +") + prog in + Arg.parse argspec anon_fun usage_msg; + + (* Check -a and -d options. *) + let files = !files in + let domain = !domain in + let libvirturi = match !libvirturi with "" -> None | s -> Some s in + let add = + match files, domain with + | [], None -> + eprintf (f_"%s: you must give either -a or -d options\n") prog; + eprintf (f_"Read virt-customize(1) man page for further information.\n"); + exit 1 + | [], Some dom -> + fun (g : Guestfs.guestfs) readonly -> + let allowuuid = true in + let readonlydisk = "ignore" (* ignore CDs, data drives *) in + let discard = if readonly then None else Some "besteffort" in + ignore (g#add_domain + ~readonly ?discard + ?libvirturi ~allowuuid ~readonlydisk + dom) + | _, Some _ -> + eprintf (f_"%s: you cannot give -a and -d options together\n") prog; + eprintf (f_"Read virt-customize(1) man page for further information.\n"); + exit 1 + | files, None -> + fun g readonly -> + List.iter ( + fun (uri, format) -> + let { URI.path = path; protocol = protocol; + server = server; username = username } = uri in + let discard = if readonly then None else Some "besteffort" in + g#add_drive + ~readonly ?discard + ?format ~protocol ?server ?username + path + ) files + in + + (* Dereference the rest of the args. *) + let debug_gc = !debug_gc in + let dryrun = !dryrun in + let quiet = !quiet in + let trace = !trace in + let verbose = !verbose in + + let ops = get_customize_ops () in + + let msg fs = make_message_function ~quiet fs in + + msg (f_"Examining the guest ..."); + + (* Connect to libguestfs. *) + let g = new G.guestfs () in + if trace then g#set_trace true; + if verbose then g#set_verbose true; + add g dryrun; + g#launch (); + + (* Inspection. *) + (match Array.to_list (g#inspect_os ()) with + | [] -> + eprintf (f_"%s: no operating systems were found in the guest image\n") prog; + exit 1 + | roots -> + List.iter ( + fun root -> + (* Mount up the disks, like guestfish -i. + * See [ocaml/examples/inspect_vm.ml]. + *) + let mps = g#inspect_get_mountpoints root in + let cmp (a,_) (b,_) = compare (String.length a) (String.length b) in + let mps = List.sort cmp mps in + List.iter ( + fun (mp, dev) -> + try g#mount dev mp; + with Guestfs.Error msg -> eprintf (f_"%s (ignored)\n") msg + ) mps; + + (* Do the customization. *) + Customize_run.run ~prog ~debug:verbose ~quiet g root ops; + + g#umount_all (); + ) roots; + ); + + g#shutdown (); + g#close (); + + if debug_gc then + Gc.compact () + +(* Finished. *) +let () = + (try main () + with + | Failure msg -> (* from failwith/failwithf *) + eprintf (f_"%s: %s\n") prog msg; + exit 1 + | Invalid_argument msg -> (* probably should never happen *) + eprintf (f_"%s: internal error: invalid argument: %s\n") prog msg; + exit 1 + | Assert_failure (file, line, char) -> (* should never happen *) + eprintf (f_"%s: internal error: assertion failed at %s, line %d, char %d\n") + prog file line char; + exit 1 + | Not_found -> (* should never happen *) + eprintf (f_"%s: internal error: Not_found exception was thrown\n") prog; + exit 1 + | exn -> + eprintf (f_"%s: exception: %s\n") prog (Printexc.to_string exn); + exit 1 + ); + + exit 0 diff --git a/customize/test-virt-customize.sh b/customize/test-virt-customize.sh new file mode 100755 index 000000000..877e8d3b6 --- /dev/null +++ b/customize/test-virt-customize.sh @@ -0,0 +1,32 @@ +#!/bin/bash - +# libguestfs virt-customize test script +# Copyright (C) 2014 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. + +export LANG=C +set -e + +# virt-customize with the -n option doesn't modify the guest. It ought +# to be able to customize any of our Linux-like test guests. + +for f in ../tests/guests/{debian,fedora,ubuntu}.img; do + # Ignore zero-sized windows.img if ntfs-3g is not installed. + if [ -s "$f" ]; then + $VG ./virt-customize -n -a $f \ + --write /etc/motd:HELLO \ + --delete /etc/motd + fi +done diff --git a/customize/virt-customize.pod b/customize/virt-customize.pod new file mode 100644 index 000000000..9c7f0fdab --- /dev/null +++ b/customize/virt-customize.pod @@ -0,0 +1,156 @@ +=head1 NAME + +virt-customize - Customize a virtual machine + +=head1 SYNOPSIS + + virt-customize [--options] -d domname +__CUSTOMIZE_SYNOPSIS__ + + virt-customize [--options] -a disk.img [-a disk.img ...] +__CUSTOMIZE_SYNOPSIS__ + +=head1 DESCRIPTION + +Virt-customize can customize a virtual machine (disk image) by +installing packages, editing configuration files, and so on. + +Virt-customize modifies the guest or disk image I. The +guest must be shut down. If you want to preserve the existing +contents of the guest, I. + +You do I need to run virt-customize as root. In fact we'd +generally recommend that you don't. + +Related tools include: L and L. + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +Display brief help. + +=item B<-a> file + +=item B<--add> file + +Add I which should be a disk image from a virtual machine. + +The format of the disk image is auto-detected. To override this and +force a particular format use the I<--format> option. + +=item B<-a> URI + +=item B<--add> URI + +Add a remote disk. The URI format is compatible with guestfish. +See L. + +=item B<-c> URI + +=item B<--connect> URI + +If using libvirt, connect to the given I. If omitted, then we +connect to the default libvirt hypervisor. + +If you specify guest block devices directly (I<-a>), then libvirt is +not used at all. + +=item B<-d> guest + +=item B<--domain> guest + +Add all the disks from the named libvirt guest. Domain UUIDs can be +used instead of names. + +=item B<-n> + +=item B<--dry-run> + +Perform a read-only "dry run" on the guest. This runs the sysprep +operation, but throws away any changes to the disk at the end. + +=item B<--format> raw|qcow2|.. + +=item B<--format> auto + +The default for the I<-a> option is to auto-detect the format of the +disk image. Using this forces the disk format for I<-a> options which +follow on the command line. Using I<--format auto> switches back to +auto-detection for subsequent I<-a> options. + +For example: + + virt-customize --format raw -a disk.img + +forces raw format (no auto-detection) for C. + + virt-customize --format raw -a disk.img --format auto -a another.img + +forces raw format (no auto-detection) for C and reverts to +auto-detection for C. + +If you have untrusted raw-format guest disk images, you should use +this option to specify the disk format. This avoids a possible +security problem with malicious guests (CVE-2010-3851). + +=item B<-q> + +=item B<--quiet> + +Don't print log messages. + +To enable detailed logging of individual file operations, use I<-x>. + +=item B<-v> + +=item B<--verbose> + +Enable verbose messages for debugging. + +=item B<-V> + +=item B<--version> + +Display version number and exit. + +=item B<-x> + +Enable tracing of libguestfs API calls. + +=back + +=head2 Customization options + +__CUSTOMIZE_OPTIONS__ + +=head1 EXIT STATUS + +This program returns 0 on success, or 1 if there was an error. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L. + +=head1 AUTHORS + +Richard W.M. Jones L + +=head1 COPYRIGHT + +Copyright (C) 2011-2014 Red Hat Inc. diff --git a/fish/guestfish.pod b/fish/guestfish.pod index a53ef9276..25279fbef 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -1607,6 +1607,7 @@ L, L, L, L, +L, L, L, L, diff --git a/po-docs/ja/Makefile.am b/po-docs/ja/Makefile.am index f17be96f6..0b9df84ed 100644 --- a/po-docs/ja/Makefile.am +++ b/po-docs/ja/Makefile.am @@ -116,6 +116,15 @@ virt-builder.1: virt-builder.pod customize-synopsis.pod customize-options.pod --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ $< +virt-customize.1: virt-customize.pod customize-synopsis.pod customize-options.pod + $(PODWRAPPER) \ + --no-strict-checks \ + --man $@ \ + --license GPLv2+ \ + --insert $(srcdir)/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \ + --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ + $< + virt-sysprep.1: virt-sysprep.pod sysprep-extra-options.pod sysprep-operations.pod $(PODWRAPPER) \ --no-strict-checks \ diff --git a/po-docs/podfiles b/po-docs/podfiles index d86355445..20946015b 100644 --- a/po-docs/podfiles +++ b/po-docs/podfiles @@ -5,6 +5,9 @@ ../cat/virt-cat.pod ../cat/virt-filesystems.pod ../cat/virt-ls.pod +../customize/customize-options.pod +../customize/customize-synopsis.pod +../customize/virt-customize.pod ../daemon/guestfsd.pod ../df/virt-df.pod ../diff/virt-diff.pod diff --git a/po-docs/uk/Makefile.am b/po-docs/uk/Makefile.am index f17be96f6..0b9df84ed 100644 --- a/po-docs/uk/Makefile.am +++ b/po-docs/uk/Makefile.am @@ -116,6 +116,15 @@ virt-builder.1: virt-builder.pod customize-synopsis.pod customize-options.pod --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ $< +virt-customize.1: virt-customize.pod customize-synopsis.pod customize-options.pod + $(PODWRAPPER) \ + --no-strict-checks \ + --man $@ \ + --license GPLv2+ \ + --insert $(srcdir)/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \ + --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ + $< + virt-sysprep.1: virt-sysprep.pod sysprep-extra-options.pod sysprep-operations.pod $(PODWRAPPER) \ --no-strict-checks \ diff --git a/src/guestfs.pod b/src/guestfs.pod index e6e91f4a7..a81cc6d68 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -4274,7 +4274,7 @@ Outside contributions, experimental parts. =item C -virt-customize mini-library. +L command and documentation. =item C @@ -4735,6 +4735,7 @@ L, L, L, L, +L, L, L, L, diff --git a/sysprep/virt-sysprep.pod b/sysprep/virt-sysprep.pod index aa570a50e..b59c79634 100644 --- a/sysprep/virt-sysprep.pod +++ b/sysprep/virt-sysprep.pod @@ -528,6 +528,7 @@ L, L, L, L, +L, L, L, L,