New tool: virt-builder: For quickly building virtual machine images.

On baremetal you can build and customize a new guest in under 2
minutes.  For example:

$ virt-builder fedora-19 \
    --root-password password:test \
    --install minicom \
    --firstboot-command 'yum -y update' \
    --firstboot-command 'useradd -m -p "" rjones ; chage -d 0 rjones'
[     0.0] Downloading: file:///home/rjones/d/libguestfs/builder/website/fedora-19.xz
[     1.0] Uncompressing: file:///home/rjones/d/libguestfs/builder/website/fedora-19.xz
[    24.0] Running virt-resize to expand the disk to 4.2G
[    77.0] Opening the new disk
[    81.0] Installing packages: minicom
[    94.0] Installing firstboot command: [001] yum -y update
[    94.0] Installing firstboot command: [002] useradd -m -p "" rjones ; chage -d 0 rjones
[    94.0] Finishing off
This commit is contained in:
Richard W.M. Jones
2013-09-24 14:00:37 +01:00
parent 6203c50479
commit 9ba6717e94
39 changed files with 3421 additions and 6 deletions

7
.gitignore vendored
View File

@@ -46,6 +46,7 @@ Makefile.in
/appliance/stamp-supermin
/appliance/supermin.d
/autom4te.cache
/bash/virt-builder
/bash/virt-cat
/bash/virt-df
/bash/virt-edit
@@ -56,6 +57,10 @@ Makefile.in
/bash/virt-sysprep
/bash/virt-sparsify
/build-aux
/builder/.depend
/builder/stamp-virt-builder.pod
/builder/virt-builder
/builder/virt-builder.1
/cat/stamp-virt-*.pod
/cat/virt-cat
/cat/virt-cat.1
@@ -206,6 +211,7 @@ Makefile.in
/html/libguestfs-make-fixed-appliance.1.html
/html/libguestfs-test-tool.1.html
/html/virt-alignment-scan.1.html
/html/virt-builder.1.html
/html/virt-cat.1.html
/html/virt-copy-in.1.html
/html/virt-copy-out.1.html
@@ -264,6 +270,7 @@ Makefile.in
/mllib/common_gettext.ml
/mllib/common_utils_tests
/mllib/dummy
/mllib/libdir.ml
/ocaml/bindtests.bc
/ocaml/bindtests.opt
/ocaml/bindtests.ml

View File

@@ -118,7 +118,7 @@ SUBDIRS += csharp
# OCaml tools. Note 'mllib' contains random shared code used by
# all of the OCaml tools.
if HAVE_OCAML
SUBDIRS += mllib resize sparsify sysprep
SUBDIRS += mllib builder resize sparsify sysprep
endif
# Perl tools.
@@ -206,6 +206,7 @@ HTMLFILES = \
html/libguestfs-make-fixed-appliance.1.html \
html/libguestfs-test-tool.1.html \
html/virt-alignment-scan.1.html \
html/virt-builder.1.html \
html/virt-cat.1.html \
html/virt-copy-in.1.html \
html/virt-copy-out.1.html \
@@ -275,7 +276,7 @@ all-local:
grep -v -E '/((guestfs|rc)_protocol\.c)$$' | \
LC_ALL=C sort > po/POTFILES
cd $(srcdir); \
find mllib resize sparsify sysprep -name '*.ml' | \
find builder mllib resize sparsify sysprep -name '*.ml' | \
LC_ALL=C sort > po/POTFILES-ml
# Manual pages in top level directory.

10
README
View File

@@ -162,6 +162,16 @@ The full requirements are described below.
+--------------+-------------+---+-----------------------------------------+
| uml_mkcow | | O | For the UML backend. |
+--------------+-------------+---+-----------------------------------------+
| curl | | O | Used by virt-builder for downloads |
+--------------+-------------+---+-----------------------------------------+
| gpg | | O | Used by virt-builder for digital |
| | | | signatures |
+--------------+-------------+---+-----------------------------------------+
| xz | | O | Used by virt-builder for compression |
+--------------+-------------+---+-----------------------------------------+
| nbdkit | | O | Used by virt-builder to speed up |
| | | | template xz-decompression |
+--------------+-------------+---+-----------------------------------------+
| findlib | | O | For the OCaml bindings. |
+--------------+-------------+---+-----------------------------------------+
| ocaml-gettext| | O | For localizing OCaml virt-* tools. |

View File

@@ -21,6 +21,7 @@ scripts = \
guestfish \
guestmount \
virt-alignment-scan \
virt-builder \
virt-cat \
virt-df \
virt-edit \
@@ -55,6 +56,8 @@ virt-ls:
virt-sysprep:
ln -sf virt-alignment-scan $@
virt-builder:
ln -sf virt-resize $@
virt-sparsify:
ln -sf virt-resize $@

View File

@@ -1,4 +1,5 @@
# virt-resize, virt-sparsify bash completion script -*- shell-script -*-
# virt-resize, virt-builder, virt-sparsify bash completion script
# -*- shell-script -*-
# Copyright (C) 2010-2013 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
@@ -33,6 +34,12 @@ _guestfs_options_only ()
esac
}
_virt_builder ()
{
_guestfs_options_only "$(virt-builder --long-options)"
} &&
complete -o default -F _virt_builder virt-builder
_virt_resize ()
{
_guestfs_options_only "$(virt-resize --long-options)"

161
builder/Makefile.am Normal file
View File

@@ -0,0 +1,161 @@
# libguestfs virt-builder tool
# Copyright (C) 2013 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.
include $(top_srcdir)/subdir-rules.mk
EXTRA_DIST = \
$(SOURCES) \
virt-builder.pod \
test-virt-builder.sh \
website/.gitignore \
website/README \
website/index \
website/index.asc \
website/fedora-18.ks \
website/fedora-18.sh \
website/fedora-18.xz.sig \
website/fedora-19.ks \
website/fedora-19.sh \
website/fedora-19.xz.sig
CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-builder
# Alphabetical order.
SOURCES = \
builder.ml \
downloader.mli \
downloader.ml \
get_kernel.mli \
get_kernel.ml \
index_parser.mli \
index_parser.ml \
list_entries.mli \
list_entries.ml \
sigchecker.mli \
sigchecker.ml
if HAVE_OCAML
# Note this list must be in dependency order.
OBJECTS = \
$(top_builddir)/mllib/libdir.cmx \
$(top_builddir)/mllib/common_gettext.cmx \
$(top_builddir)/mllib/common_utils.cmx \
$(top_builddir)/mllib/random_seed.cmx \
$(top_builddir)/mllib/firstboot.cmx \
$(top_builddir)/mllib/crypt-c.o \
$(top_builddir)/mllib/crypt.cmx \
$(top_builddir)/mllib/password.cmx \
get_kernel.cmx \
downloader.cmx \
sigchecker.cmx \
index_parser.cmx \
list_entries.cmx \
builder.cmx
bin_SCRIPTS = virt-builder
# -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
# installed copy of libguestfs.
OCAMLPACKAGES = \
-package str,unix \
-I $(top_builddir)/src/.libs \
-I $(top_builddir)/ocaml \
-I $(top_builddir)/mllib
if HAVE_OCAML_PKG_GETTEXT
OCAMLPACKAGES += -package gettext-stub
endif
OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
OCAMLOPTFLAGS = $(OCAMLCFLAGS)
virt-builder: $(OBJECTS)
$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
mlguestfs.cmxa -linkpkg $^ \
-cclib '-lncurses -lcrypt' \
$(OCAML_GCOV_LDFLAGS) \
-o $@
.mli.cmi:
$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
.ml.cmo:
$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
.ml.cmx:
$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -c $< -o $@
# automake will decide we don't need C support in this file. Really
# we do, so we have to provide it ourselves.
DEFAULT_INCLUDES = \
-I. \
-I$(top_builddir) \
-I$(shell $(OCAMLC) -where) \
-I$(top_srcdir)/src \
-I$(top_srcdir)/fish
.c.o:
$(CC) $(CFLAGS) $(PROF_CFLAGS) $(DEFAULT_INCLUDES) -c $< -o $@
# Manual pages and HTML files for the website.
man_MANS = virt-builder.1
noinst_DATA = $(top_builddir)/html/virt-builder.1.html
virt-builder.1 $(top_builddir)/html/virt-builder.1.html: stamp-virt-builder.pod
stamp-virt-builder.pod: virt-builder.pod
$(PODWRAPPER) \
--man virt-builder.1 \
--html $(top_builddir)/html/virt-builder.1.html \
--license GPLv2+ \
$<
touch $@
CLEANFILES += stamp-virt-builder.pod
# Tests.
TESTS_ENVIRONMENT = $(top_builddir)/run --test
if ENABLE_APPLIANCE
TESTS = test-virt-builder.sh
endif ENABLE_APPLIANCE
check-valgrind:
$(MAKE) VG="$(top_builddir)/run @VG@" check
# Dependencies.
depend: .depend
.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
rm -f $@ $@-t
$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/mllib $^ | \
$(SED) 's/ *$$//' | \
$(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
$(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
sort > $@-t
mv $@-t $@
-include .depend
endif
DISTCLEANFILES = .depend
.PHONY: depend docs

721
builder/builder.ml Normal file
View File

@@ -0,0 +1,721 @@
(* virt-builder
* Copyright (C) 2013 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
module G = Guestfs
open Common_utils
open Password
open Unix
open Printf
let quote = Filename.quote
(* Command line argument parsing. *)
let prog = Filename.basename Sys.executable_name
let cachedir =
try Some (Sys.getenv "XDG_CACHE_HOME" // "virt-builder")
with Not_found ->
try Some (Sys.getenv "HOME" // ".cache" // "virt-builder")
with Not_found ->
None (* no cache directory *)
let mode, arg,
attach, cache, check_signature, curl, debug, fingerprint,
firstboot, run,
format, gpg, install, list_long, network, output,
password_crypto, quiet, root_password,
size, source, upload =
let display_version () =
let g = new G.guestfs () in
let version = g#version () in
printf (f_"virt-builder %Ld.%Ld.%Ld%s\n")
version.G.major version.G.minor version.G.release version.G.extra;
exit 0
in
let mode = ref `Install in
let list_mode () = mode := `List in
let get_kernel_mode () = mode := `Get_kernel in
let delete_cache_mode () = mode := `Delete_cache in
let attach = ref [] in
let attach_format = ref None in
let set_attach_format = function
| "auto" -> attach_format := None
| s -> attach_format := Some s
in
let attach_disk s = attach := (!attach_format, s) :: !attach in
let cache = ref cachedir in
let set_cache arg = cache := Some arg in
let no_cache () = cache := None in
let check_signature = ref true in
let curl = ref "curl" in
let debug = ref false in
let fingerprint =
try Some (Sys.getenv "VIRT_BUILDER_FINGERPRINT")
with Not_found -> None in
let fingerprint = ref fingerprint in
let set_fingerprint fp = fingerprint := Some fp in
let firstboot = ref [] in
let add_firstboot s =
if not (Sys.file_exists s) then (
eprintf (f_"%s: --firstboot: %s: file not found.\n") prog s;
exit 1
);
firstboot := `Script s :: !firstboot
in
let add_firstboot_cmd s = firstboot := `Command s :: !firstboot in
let add_firstboot_install pkgs =
let pkgs = string_nsplit "," pkgs in
firstboot := `Packages pkgs :: !firstboot
in
let format = ref "" in
let gpg = ref "gpg" in
let install = ref [] in
let add_install pkgs =
let pkgs = string_nsplit "," pkgs in
install := pkgs @ !install
in
let list_long = ref false in
let network = ref true in
let output = ref "" in
let password_crypto : password_crypto option ref = ref None in
let set_password_crypto arg =
password_crypto := Some (password_crypto_of_string ~prog arg)
in
let quiet = ref false in
let root_password = ref None in
let set_root_password arg =
let pw = get_password ~prog arg in
root_password := Some pw
in
let run = ref [] in
let add_run s =
if not (Sys.file_exists s) then (
eprintf (f_"%s: --run: %s: file not found.\n") prog s;
exit 1
);
run := `Script s :: !run
in
let add_run_cmd s = run := `Command s :: !run in
let size = ref None in
let set_size arg = size := Some (parse_size ~prog arg) in
let source =
try Sys.getenv "VIRT_BUILDER_SOURCE"
with Not_found -> "http://libguestfs.org/download/builder/index.asc" in
let source = ref source in
let upload = ref [] in
let add_upload arg =
let i =
try String.index arg ':'
with Not_found ->
eprintf (f_"%s: invalid --upload format, see the man page.\n") prog;
exit 1 in
let len = String.length arg in
let file = String.sub arg 0 i in
if not (Sys.file_exists file) then (
eprintf (f_"%s: --upload: %s: file not found.\n") prog file;
exit 1
);
let dest = String.sub arg (i+1) (len-(i+1)) in
upload := (file, dest) :: !upload
in
let ditto = " -\"-" in
let argspec = Arg.align [
"--attach", Arg.String attach_disk, "iso" ^ " " ^ s_"Attach data disk/ISO during install";
"--attach-format", Arg.String set_attach_format,
"format" ^ " " ^ s_"Set attach disk format";
"--cache", Arg.String set_cache, "dir" ^ " " ^ s_"Set template cache dir";
"--no-cache", Arg.Unit no_cache, " " ^ s_"Disable template cache";
"--check-signature", Arg.Set check_signature,
" " ^ s_"Check digital signatures";
"--check-signatures", Arg.Set check_signature, ditto;
"--no-check-signature", Arg.Clear check_signature,
" " ^ s_"Disable digital signatures";
"--no-check-signatures", Arg.Clear check_signature, ditto;
"--curl", Arg.Set_string curl, "curl" ^ " " ^ s_"Set curl binary/command";
"--delete-cache", Arg.Unit delete_cache_mode,
" " ^ s_"Delete the template cache";
"--fingerprint", Arg.String set_fingerprint,
"AAAA.." ^ " " ^ s_"Fingerprint of valid signing key";
"--firstboot", Arg.String add_firstboot, "script" ^ " " ^ s_"Run script at first guest boot";
"--firstboot-command", Arg.String add_firstboot_cmd, "cmd+args" ^ " " ^ s_"Run command at first guest boot";
"--firstboot-install", Arg.String add_firstboot_install,
"pkg,pkg" ^ " " ^ s_"Add package(s) to install at firstboot";
"--format", Arg.Set_string format, "raw|qcow2" ^ " " ^ s_"Output format (default: raw)";
"--get-kernel", Arg.Unit get_kernel_mode,
"image" ^ " " ^ s_"Get kernel from image";
"--gpg", Arg.Set_string gpg, "gpg" ^ " " ^ s_"Set GPG binary/command";
"--install", Arg.String add_install, "pkg,pkg" ^ " " ^ s_"Add package(s) to install";
"-l", Arg.Unit list_mode, " " ^ s_"List available templates";
"--list", Arg.Unit list_mode, ditto;
"--long", Arg.Set list_long, ditto;
"--long-options", Arg.Unit display_long_options, " " ^ s_"List long options";
"--network", Arg.Set network, " " ^ s_"Enable appliance network (default)";
"--no-network", Arg.Clear network, " " ^ s_"Disable appliance network";
"-o", Arg.Set_string output, "file" ^ " " ^ s_"Set output filename";
"--output", Arg.Set_string output, "file" ^ ditto;
"--password-crypto", Arg.String set_password_crypto,
"md5|sha256|sha512" ^ " " ^ s_"Set password crypto";
"--quiet", Arg.Set quiet, " " ^ s_"No progress messages";
"--root-password", Arg.String set_root_password,
"..." ^ " " ^ s_"Set root password";
"--run", Arg.String add_run, "script" ^ " " ^ s_"Run script in disk image";
"--run-command", Arg.String add_run_cmd, "cmd+args" ^ " " ^ s_"Run command in disk image";
"--size", Arg.String set_size, "size" ^ " " ^ s_"Set output disk size";
"--source", Arg.Set_string source, "URL" ^ " " ^ s_"Set source URL";
"--upload", Arg.String add_upload, "file:dest" ^ " " ^ s_"Upload file to dest";
"-v", Arg.Set debug, " " ^ s_"Enable debugging messages";
"--verbose", Arg.Set debug, ditto;
"-V", Arg.Unit display_version, " " ^ s_"Display version and exit";
"--version", Arg.Unit display_version, ditto;
] in
long_options := argspec;
let args = ref [] in
let anon_fun s = args := s :: !args in
let usage_msg =
sprintf (f_"\
%s: build virtual machine images quickly
A short summary of the options is given below. For detailed help please
read the man page virt-builder(1).
")
prog in
Arg.parse argspec anon_fun usage_msg;
(* Dereference options. *)
let args = List.rev !args in
let mode = !mode in
let attach = List.rev !attach in
let cache = !cache in
let check_signature = !check_signature in
let curl = !curl in
let debug = !debug in
let fingerprint = !fingerprint in
let firstboot = List.rev !firstboot in
let run = List.rev !run in
let format = match !format with "" -> None | s -> Some s in
let gpg = !gpg in
let install = !install in
let list_long = !list_long in
let network = !network in
let output = match !output with "" -> None | s -> Some s in
let password_crypto = !password_crypto in
let quiet = !quiet in
let root_password = !root_password in
let size = !size in
let source = !source in
let upload = List.rev !upload in
(* Check options. *)
let arg =
match mode with
| `Install ->
(match args with
| [arg] -> arg
| [] ->
eprintf (f_"%s: virt-builder os-version\nMissing 'os-version'. Use '--list' to list available template names.\n") prog;
exit 1
| _ ->
eprintf (f_"%s: virt-builder: too many parameters, expecting 'os-version'\n") prog;
exit 1
)
| `List ->
(match args with
| [] -> ""
| _ ->
eprintf (f_"%s: virt-builder --list does not need any extra arguments.\n") prog;
exit 1
)
| `Delete_cache ->
(match args with
| [] -> ""
| _ ->
eprintf (f_"%s: virt-builder --delete-cache does not need any extra arguments.\n") prog;
exit 1
)
| `Get_kernel ->
(match args with
| [arg] -> arg
| [] ->
eprintf (f_"%s: virt-builder --get-kernel image\nMissing 'image' (disk image file) argument.\n") prog;
exit 1
| _ ->
eprintf (f_"%s: virt-builder --get-kernel: too many parameters\n") prog;
exit 1
) in
mode, arg,
attach, cache, check_signature, curl, debug, fingerprint,
firstboot, run,
format, gpg, install, list_long, network, output,
password_crypto, quiet, root_password,
size, source, upload
(* Timestamped messages in ordinary, non-debug non-quiet mode. *)
let msg fs = make_message_function ~quiet fs
(* If debugging, echo the command line arguments. *)
let () =
if debug then (
eprintf "command line:";
List.iter (eprintf " %s") (Array.to_list Sys.argv);
prerr_newline ()
)
(* --get-kernel is really a different program ... *)
let () =
if mode = `Get_kernel then (
Get_kernel.get_kernel ~debug ?format ?output arg;
exit 0
)
let () =
if mode = `Delete_cache then (
match cachedir with
| Some cachedir ->
msg "Deleting: %s" cachedir;
let cmd = sprintf "rm -rf %s" (quote cachedir) in
ignore (Sys.command cmd);
exit 0
| None ->
eprintf (f_"%s: error: could not find cache directory\nIs $HOME set?\n")
prog;
exit 1
)
(* Check various programs/dependencies are installed. *)
let have_nbdkit =
(* Check that gpg is installed. Optional as long as the user
* disables all signature checks.
*)
let cmd = sprintf "%s --help >/dev/null 2>&1" gpg in
if Sys.command cmd <> 0 then (
if check_signature then (
eprintf (f_"%s: gpg is not installed (or does not work)\nYou should install gpg, or use --gpg option, or use --no-check-signature.\n") prog;
exit 1
)
else if debug then
eprintf (f_"%s: warning: gpg program is not available\n") prog
);
(* Check that curl works. *)
let cmd = sprintf "%s --help >/dev/null 2>&1" curl in
if Sys.command cmd <> 0 then (
eprintf (f_"%s: curl is not installed (or does not work)\n") prog;
exit 1
);
(* Check that virt-resize works. *)
let cmd = "virt-resize --help >/dev/null 2>&1" in
if Sys.command cmd <> 0 then (
eprintf (f_"%s: virt-resize is not installed (or does not work)\n") prog;
exit 1
);
(* Find out if nbdkit + nbdkit-xz-plugin is installed (optional). *)
let cmd =
sprintf "nbdkit %s/nbdkit/plugins/nbdkit-xz-plugin.so --help >/dev/null 2>&1"
Libdir.libdir in
let have_nbdkit = Sys.command cmd = 0 in
if not have_nbdkit && debug then
eprintf (f_"%s: warning: nbdkit or nbdkit-xz-plugin is not available\n")
prog;
have_nbdkit
(* Create the cache directory. *)
let cache =
match cache with
| None -> None
| (Some dir) as cache ->
(try mkdir dir 0o755 with _ -> ());
if Sys.is_directory dir then cache else None
(* Make the downloader and signature checker abstract data types. *)
let downloader =
Downloader.create ~debug ~curl ~cache
let sigchecker =
Sigchecker.create ~debug ~gpg ?fingerprint ~check_signature
(* Download the source (index) file. *)
let index =
Index_parser.get_index ~debug ~downloader ~sigchecker source
(* Now we can do the --list option. *)
let () =
if mode = `List then (
List_entries.list_entries ~list_long ~source index;
exit 0
)
(* If we get here, we want to create a guest (but which one?) *)
let entry =
assert (mode = `Install);
try List.assoc arg index
with Not_found ->
eprintf (f_"%s: cannot find os-version '%s'.\nUse --list to list available guest types.\n")
prog arg;
exit 1
(* Download the template, or it may be in the cache. *)
let template =
let template, delete_on_exit =
let { Index_parser.revision = revision; file_uri = file_uri } = entry in
let template = arg, revision in
msg (f_"Downloading: %s") file_uri;
Downloader.download downloader ~template file_uri in
if delete_on_exit then unlink_on_exit template;
template
(* Check the signature of the file. *)
let () =
let sigfile =
match entry with
| { Index_parser.signature_uri = None } -> None
| { Index_parser.signature_uri = Some signature_uri } ->
let sigfile, delete_on_exit =
Downloader.download downloader signature_uri in
if delete_on_exit then unlink_on_exit sigfile;
Some sigfile in
Sigchecker.verify_detached sigchecker template sigfile
(* Check the --size option. *)
let headroom = 256L *^ 1024L *^ 1024L
let size =
let { Index_parser.size = default_size } = entry in
match size with
| None -> default_size +^ headroom
| Some size ->
if size < default_size +^ headroom then (
eprintf (f_"%s: --size is too small for this disk image, minimum size is %s\n")
prog (human_size default_size);
exit 1
);
size
(* Create the output file. *)
let output, format =
match output, format with
| None, None -> sprintf "%s.img" arg, "raw"
| None, Some "raw" -> sprintf "%s.img" arg, "raw"
| None, Some format -> sprintf "%s.%s" arg format, format
| Some output, None -> output, "raw"
| Some output, Some format -> output, format
let delete_output_file =
let cmd =
sprintf "qemu-img create -f %s %s %Ld%s"
(quote format) (quote output) size
(if debug then "" else " >/dev/null 2>&1") in
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"%s: error: could not create output file '%s'\n") prog output;
exit 1
);
(* This ensures the output file will be deleted on failure,
* until we set !delete_output_file = false at the end of the build.
*)
let delete_output_file = ref true in
let delete_file () =
if !delete_output_file then
try unlink output with _ -> ()
in
at_exit delete_file;
delete_output_file
let source =
(* XXX Disable this for now because libvirt is broken:
* https://bugzilla.redhat.com/show_bug.cgi?id=1011063
*)
if have_nbdkit && false then (
(* If we have nbdkit, then we can use NBD to uncompress the xz
* file on the fly.
*)
let socket = Filename.temp_file "vbnbd" ".sock" in
let source = sprintf "nbd://?socket=%s" socket in
let argv = [| "nbdkit"; "-r"; "-f"; "-U"; socket;
Libdir.libdir // "nbdkit/plugins/nbdkit-xz-plugin.so";
"file=" ^ template |] in
let pid =
match fork () with
| 0 -> (* child *)
execvp "nbdkit" argv
| pid -> pid in
(* Clean up when the program exits. *)
let clean_up () =
(try kill pid Sys.sigterm with _ -> ());
(try unlink socket with _ -> ())
in
at_exit clean_up;
source
)
else (
(* Otherwise we have to uncompress it to a temporary file. *)
let { Index_parser.file_uri = file_uri } = entry in
let tmpfile = Filename.temp_file "vbsrc" ".img" in
let cmd = sprintf "xzcat %s > %s" (quote template) (quote tmpfile) in
if debug then eprintf "%s\n%!" cmd;
msg (f_"Uncompressing: %s") file_uri;
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"%s: error: failed to uncompress template\n") prog;
exit 1
);
unlink_on_exit tmpfile;
tmpfile
)
(* Resize the source to the output file. *)
let () =
msg (f_"Running virt-resize to expand the disk to %s") (human_size size);
let { Index_parser.expand = expand; lvexpand = lvexpand; format = format } =
entry in
let cmd =
sprintf "virt-resize%s%s%s%s %s %s"
(if debug then " --verbose" else " --quiet")
(match format with
| None -> ""
| Some format -> sprintf " --format %s" (quote format))
(match expand with
| None -> ""
| Some expand -> sprintf " --expand %s" (quote expand))
(match lvexpand with
| None -> ""
| Some lvexpand -> sprintf " --lv-expand %s" (quote lvexpand))
(quote source) (quote output) in
if debug then eprintf "%s\n%!" cmd;
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"%s: error: virt-resize failed\n") prog;
exit 1
)
(* Now mount the output disk so we can make changes. *)
let g =
msg (f_"Opening the new disk");
let g = new G.guestfs () in
if debug then g#set_trace true;
g#set_network network;
g#add_drive_opts ~format output;
(* Attach ISOs, if we have any. *)
List.iter (
fun (format, file) ->
g#add_drive_opts ?format ~readonly:true file;
) attach;
g#launch ();
g
(* Inspect the disk and mount it up. *)
let root =
match Array.to_list (g#inspect_os ()) with
| [root] ->
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: %s (ignored)\n") prog msg
) mps;
root
| _ ->
eprintf (f_"%s: no guest operating systems or multiboot OS found in this disk image\nThis is a failure of the source repository. Use -v for more information.\n") prog;
exit 1
(* Set the random seed. *)
let () = ignore (Random_seed.set_random_seed g root)
(* Useful wrapper for scripts. *)
let do_run cmd =
(* Add a prologue to the scripts:
* - Pass environment variables through from the host.
* - Send stdout to stderr so we capture all output in error messages.
*)
let env_vars =
filter_map (
fun name ->
try Some (sprintf "export %s=%s" name (quote (Sys.getenv name)))
with Not_found -> None
) [ "http_proxy"; "https_proxy"; "ftp_proxy" ] in
let env_vars = String.concat "\n" env_vars ^ "\n" in
let cmd = sprintf "\
exec 1>&2
%s
%s
" env_vars cmd in
if debug then eprintf "running: %s\n%!" cmd;
ignore (g#sh cmd)
let guest_install_command packages =
let quoted_args = String.concat " " (List.map quote packages) in
match g#inspect_get_package_management root with
| "apt" ->
sprintf "apt-get -y install %s" quoted_args
| "pisi" ->
sprintf "pisi it %s" quoted_args
| "pacman" ->
sprintf "pacman -S %s" quoted_args
| "urpmi" ->
sprintf "urpmi %s" quoted_args
| "yum" ->
sprintf "yum -y install %s" quoted_args
| "zypper" ->
(* XXX Should we use -n option? *)
sprintf "zypper in %s" quoted_args
| "unknown" ->
eprintf (f_"%s: --install is not supported for this guest operating system\n")
prog;
exit 1
| pm ->
eprintf (f_"%s: sorry, don't know how to use --install with the '%s' package manager\n")
prog pm;
exit 1
(* Install packages. *)
let () =
if install <> [] then (
msg (f_"Installing packages: %s") (String.concat " " install);
let cmd = guest_install_command install in
do_run cmd;
)
(* Root password.
* Note 'None' means that we randomize the root password.
*)
let () =
let make_random_password () =
(* Get random characters from the set [A-Za-z0-9] *)
let chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" in
let nr_chars = String.length chars in
let chan = open_in "/dev/urandom" in
let buf = String.create 16 in
for i = 0 to 15 do
buf.[i] <- chars.[Char.code (input_char chan) mod nr_chars]
done;
close_in chan;
msg "Random root password: %s [did you mean to use --root-password?]" buf;
buf
in
let root_password =
match root_password with
| Some pw -> pw
| None -> make_random_password () in
match g#inspect_get_type root with
| "linux" ->
let h = Hashtbl.create 1 in
Hashtbl.replace h "root" root_password;
set_linux_passwords ~prog ?password_crypto g root h
| _ ->
()
(* Upload files. *)
let () =
List.iter (
fun (file, dest) ->
msg (f_"Uploading: %s") dest;
g#upload file dest
) upload
(* Firstboot scripts/commands/install. *)
let () =
let id = ref 0 in
List.iter (
fun op ->
incr id;
let id = sprintf "%03d" !id in
match op with
| `Script script ->
msg (f_"Installing firstboot script: [%s] %s") id script;
let cmd = read_whole_file script in
Firstboot.add_firstboot_script g root id cmd
| `Command cmd ->
msg (f_"Installing firstboot command: [%s] %s") id cmd;
Firstboot.add_firstboot_script g root id cmd
| `Packages pkgs ->
msg (f_"Installing firstboot packages: [%s] %s") id
(String.concat " " pkgs);
let cmd = guest_install_command pkgs in
Firstboot.add_firstboot_script g root id cmd
) firstboot
(* Run scripts. *)
let () =
List.iter (
function
| `Script script ->
msg (f_"Running: %s") script;
let cmd = read_whole_file script in
do_run cmd
| `Command cmd ->
msg (f_"Running: %s") cmd;
do_run cmd
) run
(* Unmount everything and we're done! *)
let () =
msg "Finishing off";
g#umount_all ();
g#shutdown ();
g#close ()
(* Now that we've finished the build, don't delete the output file on
* exit.
*)
let () =
delete_output_file := false

114
builder/downloader.ml Normal file
View File

@@ -0,0 +1,114 @@
(* virt-builder
* Copyright (C) 2013 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 Unix
open Printf
let quote = Filename.quote
let (//) = Filename.concat
type uri = string
type filename = string
type t = {
debug : bool;
curl : string;
cache : string option; (* cache directory for templates *)
}
let create ~debug ~curl ~cache = {
debug = debug;
curl = curl;
cache = cache;
}
let rec download t ?template uri =
match template with
| None -> (* no cache, simple download *)
(* Create a temporary name. *)
let tmpfile = Filename.temp_file "vbcache" ".txt" in
download_to t uri tmpfile;
(tmpfile, true)
| Some (name, revision) ->
match t.cache with
| None ->
(* Not using the cache at all? *)
download t uri
| Some cachedir ->
let filename = cachedir // sprintf "%s.%d" name revision in
(* Is the requested template name + revision in the cache already?
* If not, download it.
*)
if not (Sys.file_exists filename) then
download_to t uri filename;
(filename, false)
and download_to t uri filename =
(* Get the status code first to ensure the file exists. *)
let cmd = sprintf "%s%s -g -o /dev/null -I -w '%%{http_code}' %s"
t.curl (if t.debug then "" else " -s -S") (quote uri) in
let chan = open_process_in cmd in
let status_code = input_line chan in
let stat = close_process_in chan in
(match stat with
| WEXITED 0 -> ()
| WEXITED i ->
eprintf (f_"virt-builder: curl (download) command failed downloading '%s'\n") uri;
exit 1
| WSIGNALED i ->
eprintf (f_"virt-builder: external command '%s' killed by signal %d\n")
cmd i;
exit 1
| WSTOPPED i ->
eprintf (f_"virt-builder: external command '%s' stopped by signal %d\n")
cmd i;
exit 1
);
let bad_status_code = function
| "" -> true
| s when s.[0] = '4' -> true (* 4xx *)
| s when s.[0] = '5' -> true (* 5xx *)
| _ -> false
in
if bad_status_code status_code then (
eprintf (f_"virt-builder: failed to download %s: HTTP status code %s\n")
uri status_code;
exit 1
);
(* Now download the file. *)
let filename_new = filename ^ ".new" in
let cmd = sprintf "%s%s -g -o %s %s"
t.curl (if t.debug then "" else " -s -S")
(quote filename_new) (quote uri) in
if t.debug then eprintf "%s\n%!" cmd;
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"virt-builder: curl (download) command failed downloading '%s'\n") uri;
(try unlink filename_new with _ -> ());
exit 1
);
(* Rename the file if curl was successful. *)
rename filename_new filename

38
builder/downloader.mli Normal file
View File

@@ -0,0 +1,38 @@
(* virt-builder
* Copyright (C) 2013 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.
*)
(** This module is a wrapper around curl, plus local caching. *)
type uri = string
type filename = string
type t
(** The abstract data type. *)
val create : debug:bool -> curl:string -> cache:string option -> t
(** Create the abstract type. *)
val download : t -> ?template:(string*int) -> uri -> (filename * bool)
(** Download the URI, returning the downloaded filename and a
temporary file flag. The temporary file flag is [true] iff
the downloaded file is temporary and should be deleted by the
caller (otherwise it's in the cache and you shouldn't delete it).
For templates, you must supply [~template:(name, revision)]. This
causes the cache to be used (if possible). Name and revision are
used for cache control (see the man page for details). *)

121
builder/get_kernel.ml Normal file
View File

@@ -0,0 +1,121 @@
(* virt-builder
* Copyright (C) 2013 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
module G = Guestfs
open Printf
let rex_numbers = Str.regexp "^\\([0-9]+\\)\\(.*\\)$"
let rex_letters = Str.regexp_case_fold "^\\([a-z]+\\)\\(.*\\)$"
(* Originally:
* http://rwmj.wordpress.com/2013/09/13/get-kernel-and-initramfs-from-a-disk-image/
*)
let rec get_kernel ~debug ?format ?output disk =
let g = new G.guestfs () in
if debug then g#set_trace true;
g#add_drive_opts ?format ~readonly:true disk;
g#launch ();
let roots = g#inspect_os () in
if Array.length roots = 0 then (
eprintf (f_"virt-builder: get-kernel: no operating system found\n");
exit 1
);
if Array.length roots > 1 then (
eprintf (f_"virt-builder: get-kernel: daual/mult-boot images are not supported by this tool\n");
exit 1
);
let root = roots.(0) in
(* Mount up the disks. *)
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_ro dev mp
with Guestfs.Error msg -> eprintf "%s (ignored)\n" msg
) mps;
(* Get all kernels and initramfses. *)
let glob w = Array.to_list (g#glob_expand w) in
let kernels = glob "/boot/vmlinuz-*" in
let initrds = glob "/boot/initramfs-*" in
(* Old RHEL: *)
let initrds = if initrds <> [] then initrds else glob "/boot/initrd-*" in
(* Debian/Ubuntu: *)
let initrds = if initrds <> [] then initrds else glob "/boot/initrd.img-*" in
(* Sort by version to get the latest version as first element. *)
let kernels = List.rev (List.sort compare_version kernels) in
let initrds = List.rev (List.sort compare_version initrds) in
if kernels = [] then (
eprintf (f_"virt-builder: no kernel found\n");
exit 1
);
(* Download the latest. *)
let outputdir =
match output with
| None -> Filename.current_dir_name
| Some dir -> dir in
let kernel_in = List.hd kernels in
let kernel_out = outputdir // Filename.basename kernel_in in
printf "download: %s -> %s\n%!" kernel_in kernel_out;
g#download kernel_in kernel_out;
if initrds <> [] then (
let initrd_in = List.hd initrds in
let initrd_out = outputdir // Filename.basename initrd_in in
printf "download: %s -> %s\n%!" initrd_in initrd_out;
g#download initrd_in initrd_out
);
(* Shutdown. *)
g#shutdown ();
g#close ()
and compare_version v1 v2 =
compare (split_version v1) (split_version v2)
and split_version = function
| "" -> []
| str ->
let first, rest =
if Str.string_match rex_numbers str 0 then (
let n = Str.matched_group 1 str in
let rest = Str.matched_group 2 str in
let n =
try `Number (int_of_string n)
with Failure "int_of_string" -> `String n in
n, rest
)
else if Str.string_match rex_letters str 0 then
`String (Str.matched_group 1 str), Str.matched_group 2 str
else (
let len = String.length str in
`Char str.[0], String.sub str 1 (len-1)
) in
first :: split_version rest

19
builder/get_kernel.mli Normal file
View File

@@ -0,0 +1,19 @@
(* virt-builder
* Copyright (C) 2013 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.
*)
val get_kernel : debug:bool -> ?format:string -> ?output:string -> string -> unit

373
builder/index_parser.ml Normal file
View File

@@ -0,0 +1,373 @@
(* virt-builder
* Copyright (C) 2013 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
open Unix
type index = (string * entry) list (* string = "os-version" *)
and entry = {
printable_name : string option; (* the name= field *)
osinfo : string option;
file_uri : string;
signature_uri : string option;
revision : int;
format : string option;
size : int64;
compressed_size : int64 option;
expand : string option;
lvexpand : string option;
notes : string option;
hidden : bool;
}
let print_entry chan (name, { printable_name = printable_name;
file_uri = file_uri;
osinfo = osinfo;
signature_uri = signature_uri;
revision = revision;
format = format;
size = size;
compressed_size = compressed_size;
expand = expand;
lvexpand = lvexpand;
notes = notes;
hidden = hidden }) =
let fp fs = fprintf chan fs in
fp "[%s]\n" name;
(match printable_name with
| None -> ()
| Some name -> fp "name=%s\n" name
);
(match osinfo with
| None -> ()
| Some id -> fp "osinfo=%s\n" id
);
fp "file=%s\n" file_uri;
(match signature_uri with
| None -> ()
| Some uri -> fp "sig=%s\n" uri
);
fp "revision=%d\n" revision;
(match format with
| None -> ()
| Some format -> fp "format=%s\n" format
);
fp "size=%Ld\n" size;
(match compressed_size with
| None -> ()
| Some size -> fp "compressed_size=%Ld\n" size
);
(match expand with
| None -> ()
| Some expand -> fp "expand=%s\n" expand
);
(match lvexpand with
| None -> ()
| Some lvexpand -> fp "lvexpand=%s\n" lvexpand
);
(match notes with
| None -> ()
| Some notes -> fp "notes=%s\n" notes
);
if hidden then fp "hidden=true\n"
let fieldname_rex = Str.regexp "^\\([a-z_]+\\)=\\(.*\\)$"
let get_index ~debug ~downloader ~sigchecker source =
let rec corrupt_line line =
eprintf (f_"virt-builder: error parsing index near this line:\n\n%s\n")
line;
corrupt_file ()
and corrupt_file () =
eprintf (f_"\nThe index file downloaded from '%s' is corrupt.\nYou need to ask the supplier of this file to fix it and upload a fixed version.\n")
source;
exit 1
in
let rec get_index () =
(* Get the index page. *)
let tmpfile, delete_tmpfile = Downloader.download downloader source in
(* Check index file signature (also verifies it was fully
* downloaded and not corrupted in transit).
*)
Sigchecker.verify sigchecker tmpfile;
(* Check the index page is not too huge. *)
let st = stat tmpfile in
if st.st_size > 1_000_000 then (
eprintf (f_"virt-builder: index page '%s' is too large (size %d bytes)\n")
source st.st_size;
exit 1
);
(* Load the file into memory. *)
let index = read_whole_file tmpfile in
if delete_tmpfile then
(try Unix.unlink tmpfile with _ -> ());
(* Split file into lines. *)
let index = string_nsplit "\n" index in
(* If there is a signature (checked above) then remove it. *)
let index =
match index with
| "-----BEGIN PGP SIGNED MESSAGE-----" :: lines ->
(* Ignore all lines until we get to first blank. *)
let lines = dropwhile ((<>) "") lines in
(* Ignore the blank line too. *)
let lines = List.tl lines in
(* Take lines until we get to the end signature. *)
let lines = takewhile ((<>) "-----BEGIN PGP SIGNATURE-----") lines in
lines
| _ -> index in
(* Split into sections around each /^[/ *)
let rec loop = function
| [] -> []
| x :: xs when String.length x >= 1 && x.[0] = '[' ->
let lines = takewhile ((<>) "") xs in
let rest = dropwhile ((<>) "") xs in
if rest = [] then
[x, lines]
else (
let rest = List.tl rest in
let rest = loop rest in
(x, lines) :: rest
)
| x :: _ -> corrupt_line x
in
let sections = loop index in
(* Parse the fields in each section. *)
let isspace = function ' ' | '\t' -> true | _ -> false in
let starts_space str = String.length str >= 1 && isspace str.[0] in
let rec loop = function
| [] -> []
| x :: xs when not (starts_space x) && String.contains x '=' ->
let xs' = takewhile starts_space xs in
let ys = dropwhile starts_space xs in
(x :: xs') :: loop ys
| x :: _ -> corrupt_line x
in
let sections = List.map (fun (n, lines) -> n, loop lines) sections in
if debug then (
eprintf "index file (%s) after splitting:\n" source;
List.iter (
fun (n, fields) ->
eprintf " os-version: %s\n" n;
let i = ref 0 in
List.iter (
fun field ->
eprintf " %d: " !i;
List.iter prerr_endline field;
incr i
) fields
) sections
);
(* Now we've parsed the file into the correct sections, we
* interpret the meaning of the fields.
*)
let sections = List.map (
fun (n, fields) ->
let len = String.length n in
if len < 3 || n.[0] <> '[' || n.[len-1] <> ']' then
corrupt_line n;
let n = String.sub n 1 (len-2) in
let fields = List.map (
function
| [] -> assert false (* can never happen, I think? *)
| x :: xs when Str.string_match fieldname_rex x 0 ->
let field = Str.matched_group 1 x in
let rest_of_line = Str.matched_group 2 x in
let allow_multiline =
match field with
| "name" -> false
| "osinfo" -> false
| "file" -> false
| "sig" -> false
| "revision" -> false
| "format" -> false
| "size" -> false
| "compressed_size" -> false
| "expand" -> false
| "lvexpand" -> false
| "notes" -> true
| "hidden" -> false
| _ ->
eprintf "warning: unknown field '%s' in index (ignored)\n%!"
field;
true in
let value =
if not allow_multiline then (
if xs <> [] then (
eprintf (f_"virt-builder: field '%s' cannot span multiple lines\n")
field;
corrupt_line (List.hd xs)
);
rest_of_line
) else (
String.concat "\n" (rest_of_line :: xs)
) in
field, value
| x :: _ ->
corrupt_line x
) fields in
(n, fields)
) sections in
(* Check for repeated os-version names. *)
let nseen = Hashtbl.create 13 in
List.iter (
fun (n, _) ->
if Hashtbl.mem nseen n then (
eprintf (f_"virt-builder: index is corrupt: os-version '%s' appears two or more times\n") n;
corrupt_file ()
);
Hashtbl.add nseen n true
) sections;
(* Check for repeated fields. *)
List.iter (
fun (n, fields) ->
let fseen = Hashtbl.create 13 in
List.iter (
fun (field, _) ->
if Hashtbl.mem fseen field then (
eprintf (f_"virt-builder: index is corrupt: %s: field '%s' appears two or more times\n") n field;
corrupt_file ()
);
Hashtbl.add fseen field true
) fields
) sections;
(* Turn the sections into the final index. *)
let entries =
List.map (
fun (n, fields) ->
let printable_name =
try Some (List.assoc "name" fields) with Not_found -> None in
let osinfo =
try Some (List.assoc "osinfo" fields) with Not_found -> None in
let file_uri =
try make_absolute_uri (List.assoc "file" fields)
with Not_found ->
eprintf (f_"virt-builder: no 'file' (URI) entry for '%s'\n") n;
corrupt_file () in
let signature_uri =
try Some (make_absolute_uri (List.assoc "sig" fields))
with Not_found -> None in
let revision =
try int_of_string (List.assoc "revision" fields)
with
| Not_found -> 1
| Failure "int_of_string" ->
eprintf (f_"virt-builder: cannot parse 'revision' field for '%s'\n")
n;
corrupt_file () in
let format =
try Some (List.assoc "format" fields) with Not_found -> None in
let size =
try Int64.of_string (List.assoc "size" fields)
with
| Not_found ->
eprintf (f_"virt-builder: no 'size' field for '%s'\n") n;
corrupt_file ()
| Failure "int_of_string" ->
eprintf (f_"virt-builder: cannot parse 'size' field for '%s'\n")
n;
corrupt_file () in
let compressed_size =
try Some (Int64.of_string (List.assoc "compressed_size" fields))
with
| Not_found ->
None
| Failure "int_of_string" ->
eprintf (f_"virt-builder: cannot parse 'compressed_size' field for '%s'\n")
n;
corrupt_file () in
let expand =
try Some (List.assoc "expand" fields) with Not_found -> None in
let lvexpand =
try Some (List.assoc "lvexpand" fields) with Not_found -> None in
let notes =
try Some (List.assoc "notes" fields) with Not_found -> None in
let hidden =
try bool_of_string (List.assoc "hidden" fields)
with
| Not_found -> false
| Failure "bool_of_string" ->
eprintf (f_"virt-builder: cannot parse 'hidden' field for '%s'\n")
n;
corrupt_file () in
let entry = { printable_name = printable_name;
osinfo = osinfo;
file_uri = file_uri;
signature_uri = signature_uri;
revision = revision;
format = format;
size = size;
compressed_size = compressed_size;
expand = expand;
lvexpand = lvexpand;
notes = notes;
hidden = hidden } in
n, entry
) sections in
if debug then (
eprintf "index file (%s) after parsing:\n" source;
List.iter (print_entry Pervasives.stderr) entries
);
entries
(* Verify same-origin policy for the file= and sig= fields. *)
and make_absolute_uri path =
if String.length path = 0 then (
eprintf (f_"virt-builder: zero length path in the index file\n");
corrupt_file ()
)
else if string_find path "://" >= 0 then (
eprintf (f_"virt-builder: cannot use a URI ('%s') in the index file\n")
path;
corrupt_file ()
)
else if path.[0] = '/' then (
eprintf (f_"virt-builder: you must use relative paths (not '%s') in the index file\n") path;
corrupt_file ()
)
else (
(* Construct the URI. *)
try
let i = String.rindex source '/' in
String.sub source 0 (i+1) ^ path
with
Not_found -> source // path
)
in
get_index ()

35
builder/index_parser.mli Normal file
View File

@@ -0,0 +1,35 @@
(* virt-builder
* Copyright (C) 2013 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.
*)
type index = (string * entry) list (* string = "os-version" *)
and entry = {
printable_name : string option; (* the name= field *)
osinfo : string option;
file_uri : string;
signature_uri : string option;
revision : int;
format : string option;
size : int64;
compressed_size : int64 option;
expand : string option;
lvexpand : string option;
notes : string option;
hidden : bool;
}
val get_index : debug:bool -> downloader:Downloader.t -> sigchecker:Sigchecker.t -> string -> index

66
builder/list_entries.ml Normal file
View File

@@ -0,0 +1,66 @@
(* virt-builder
* Copyright (C) 2013 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
let list_entries ?(list_long = false) ~source index =
if list_long then (
printf (f_"Source URI: %s\n") source;
printf "\n"
);
List.iter (
fun (name, { Index_parser.printable_name = printable_name;
size = size;
compressed_size = compressed_size;
notes = notes;
hidden = hidden }) ->
if not hidden then (
if not list_long then ( (* Short *)
printf "%-24s" name;
(match printable_name with
| None -> ()
| Some s -> printf " %s" s
);
printf "\n"
)
else ( (* Long *)
printf "%-24s %s\n" "os-version:" name;
(match printable_name with
| None -> ()
| Some name -> printf "%-24s %s\n" (s_"Full name:") name;
);
printf "%-24s %s\n" (s_"Minimum/default size:") (human_size size);
(match compressed_size with
| None -> ()
| Some size ->
printf "%-24s %s\n" (s_"Download size:") (human_size size);
);
(match notes with
| None -> ()
| Some notes ->
printf "\n";
printf "Notes:\n %s\n" notes
);
printf "\n"
)
)
) index

19
builder/list_entries.mli Normal file
View File

@@ -0,0 +1,19 @@
(* virt-builder
* Copyright (C) 2013 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.
*)
val list_entries : ?list_long:bool -> source:string -> Index_parser.index -> unit

209
builder/sigchecker.ml Normal file
View File

@@ -0,0 +1,209 @@
(* virt-builder
* Copyright (C) 2013 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
open Unix
let quote = Filename.quote
(* These are the public key and fingerprint belonging to
* Richard W.M. Jones who signs the templates on
* http://libguestfs.org/download/builder.
*)
let default_fingerprint = "F777 4FB1 AD07 4A7E 8C87 67EA 9173 8F73 E1B7 68A0"
let default_pubkey = "\
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.14 (GNU/Linux)
mQINBE6UMMEBEADM811hfTulaF4JpkVpAI10FImyb4ArvOiu8NdcUwTFo+cyWno3
U85B86H1Bsk/LgLTYtthSrTgsCtdxy+i5OaMjxZDIwKQ2+IYI3FCn9T3Mn28Idyh
kLHzrO9ph0Dv0BNfrlDZhQEC53aAFe/QxN7+A49BNBV7D1VAOOCsHjxMEDzcZkCa
oCrtXw1aNm2vkkj5ukbfukHAyLcQL7kow0qKPSVa1G4lfQP0WiG259Ydy+sUmbVb
TGdb6MEC84PQRDuw6/ZeoV04tn7ZNtQEMOS0uiciHOGfr2hBxQf9VIPNrHg42yaL
dOv51D99GuaxZ9E0HSoH/RwB1oXgd6rFdqVNYaBIQnnkwJANUEeGBArtIOZNCADT
Bt8vkSDm+lLEAFS+V8CACyW/LMIrGCvLdHeqtoAv0GDVyR2GPxldYfdtEmCUMWcb
Jlf71V9iAse2gUdoiHp5FfpGMkA5j7idKuxIws11XxRZJXXbBqiBqmVEAQ/v0m6p
kdo0MYTHydmecLuUK2bAGhpysfX97EfTSrxfrYphYWjTfKRD9GrADeZNfuz1DbKs
7LSqVaQJSjQrfgAwcnZLRaU0V4P5zxiz50gz1Aj3AZRL+Y3meZenzZTXcLFdnusg
wUfhhCuL3tluMtEh6tznumyxb43WO1yLwj6J6LtveiuJN1Z+KSQ6OieZcwARAQAB
tCVSaWNoYXJkIFcuTS4gSm9uZXMgPHJpY2hAYW5uZXhpYS5vcmc+iQI4BBMBAgAi
BQJOlDDBAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCRc49z4bdooHQY
D/wJLklSZNyXIW+rG5sUbg7j9cTIF5p/lB9kI2yx6KodJp/2knKyvnmzz0gBw/OE
HL4E4UW26oWKo+36I8wkBnuGa6UtANeITcJqFE19VpHEXHsxre64jNQnO8/w748W
1ROW+Ry43xmrlRWKuCm4oPYUzlp0fq9ATAne8eblfG+NOs8DYuA8xZNQzFaI2kDC
QLD4YoXLoNsP27Koga36b0KwxPFD9tyVZiu9XDH/3hMN7Nb15B66PFr+HcMmQ67G
nUIN5ulcIwj38i40cyaTs1VRheOzTHXE/a6Q2AhMKiKqOoEjQ73/mV7cAVoPtM3o
83Q/8aVKBH0bVRwAeV1tju6b14fqKoG0zNBEcXdlSkht6ScxJYIc/LPUxAMDwgSE
OWshjmeRzKXypBbHn/DP8QVyM2gk5wY+mMSH7MpR0p/hgj+rFO8H9L7pC4dCog3E
qzrYhRN+TaP6MPH3WkOwPH4d4IfQRFnHp+VPYPijKEiLrUl/o8k3DyAanAPBpJ/x
na4wXAjlFBctOq6g+SrCUiHpwk7b2YNwGgr5Vl3GmZELzK/G8gg3uJYKQ9Bpv16t
WWOz+IFiOFa0UULeo0QPmFAIMZiDojNsY1SwBKB3ZL1YWZezgMdQAbpze/IXoSt7
zxWJoKH2jK7q9mvFiaY12l2YnKuCcegWVAViLxRpBnrbz7QmUmljaGFyZCBXLk0u
IEpvbmVzIDxyam9uZXNAcmVkaGF0LmNvbT6JAjgEEwECACIFAk6UOQsCGwMGCwkI
BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEJFzj3Pht2igIUYQAKomI0edLakahsUQ
MxOZuhBbXJ4/VWF8bXYChDNPKvJp5nB7fBXujJ+39cIUM5fe2ViO6qSDpFC29imx
F5pPbAqspZBPBkLLiZLji8R42hGarntdtTW0UWSBpq+nC5+G1psrnATI3uXGNxKQ
R99c5HoMY7dBC2Y8TCGE64NINZ/XVh472s6IGLPn8MTn26YdRKC9BrVkCFMP2OBr
6D4IprnyTAWAzb68ew20QmyWO+NBi9MplaDNQVl8PIOgfpyWlkgX1z9m67pcSDkw
46hksp0yuOD1VwR4iVZ2/CmIsGRUlx41vWD6BIp9KxKyDIU1CYTRhq72dahHsl/8
BjCndV5PO0GphqfCzmCv4DXjUwmrMTbH/GFnt5rfwcMcXUgcK0vV9vQ2SOU56Zd1
fb27ZCFJKZc0Fu8krwFldCp/NYILf6ogUL/C1hfuCGSSuyDVY16Gg3dla1x+6zpF
asnWQlaw8xT5LlMWvTZs5WsoSVHu7dVZWlgxINP++hlZrTz/S8l38yyQ15YFFl3W
9M7dzkegOeDTPfx6B89WgfvfJjA/D0/FYxxWPXEtrn9DlJ4daEJqNsrvfLErz9R8
4IQmfmhR93j+rdotner+6keC/wVByEfbW1wmXtmFKXQ6srdpj8VKRFrvkyXVgepM
DypLgRH2v7lL2kdWhUu2y4EAgrwzuQINBE6UMMEBEADxQxMgUuDrw5GT4tqARTPI
SSdNcUsRxRhVA8srYOyECliE+B3TwcRDFBs+MyPFJVEuX8fi4eGj/AK5t1GHerfk
orUGlz72q4c7LLhkfZrsuJbk2dgkjvldKJnIazQJa6epGLqdsE5RlmSgwedIbtMd
naGJBQH8aKP/Wi1+wUxsm5N3p7+R2WRx48VfpEhYB+Zf/FkFm1Ycjwh57KQ0+OHw
ykf8VfMisxuH30tDxOCV+VptWKfOF2rDNdaNPWhij2YIjhJXRpkuRR+1PpI4jLaD
JxcVZmG/0zucacupUN2g5OUH59ySU/totD6YMnmp3FONoyF1uIEJo6Vs30npHGkO
XgBo3Pxt7oLJeykLPtdSLgm3cwXIYMWarVsAkKNXitQIVGpVRLeaK373VwmXFqoi
M2SMHeawTUdOORFjpQzkknlJWM1TmUVtHHKt8Pl9+/5+wXKyt2IDdcUkMrB6K5qF
fb7EwVhoI8ehJQK+eeDCjFwCAiwB3iV8JlyW+tEU7JuyXOQlwY1VWm/WqMD8gaRi
rT+RFDFliZ3tQbW2pqUoZBROV5HN4tieDfwxGKCvk6Tsdb30zA9DPQp93+238bYf
312sg9R+CD0AqxoxFG5FJu4HShcPRrPnYtRZqKRe40GDWvBEArXZprwL1qrP+Kl/
mRrEQpxAGIoFG8HbVvD3EQARAQABiQIfBBgBAgAJBQJOlDDBAhsMAAoJEJFzj3Ph
t2igSLQP/2uIrAY2CDr0kWBJiD3TztiHy8IdxwUpyTBTebwmAbi44/EvtJfIisrG
YjKIEv/w0E61gO7O1JBG4+IG93W+v9fTT/e39JMyxsYqoZZHUhP11Okx5grDS5b0
O8VXOmXVRMdVNfstRBr10HD9uNDq7ruKD18TxYTwN0GPD4gj1dbHQDR77Tr5cyBs
6Ou5PBOH4r3qcqf/cJUSMeUUu75xLwixux6E7tD2S+t6F07wlWxntUcPtzyAHj20
J89orUC+dT6r6MypBoI0jdJCp9JPGtR7i+fE5Gm4E5+AUSubLPtZGRY9Um2eMoS2
DnQpGOKx1VvsixR/Kw44j2tRAvmYMS4iDKcuZU+nZ+xokAgObILj/b9n/Qe2/fXy
CFdcgSvbm+dV1fZxsdMF/P9OU8aqdT9A9Fv5y+cDMEg4DVnhwMJTxGh/TCkw/H+A
frHEtRc98lSQN5odpITNG17mG6JOdHM+wA57qHH0uy4+5RsbyAJahcdBcmObK/RF
i4WZlThpbHftX5O/LH98aYQ2fJayIxv1EAjzOBOQ0MfBHI0KCJR1pysEisX28sJA
Ic73gnJJ3BLZbqfBRgxjNMNroxC+5Tw6uPGFHa3YnuIAxxw0HcDVZ9vnTWBWFPGw
ZvXkQ3FVJwZoLmHw47vvlVpLD/4gi1SuHWieRvZ+UdDq00E348pm
=neBW
-----END PGP PUBLIC KEY BLOCK-----
"
let key_imported = ref false
type t = {
debug : bool;
gpg : string;
fingerprint : string;
check_signature : bool;
}
let create ~debug ~gpg ?(fingerprint = default_fingerprint) ~check_signature =
{
debug = debug;
gpg = gpg;
fingerprint = fingerprint;
check_signature = check_signature;
}
(* Compare two strings of hex digits ignoring whitespace and case. *)
let rec equal_fingerprints fp1 fp2 =
let len1 = String.length fp1 and len2 = String.length fp2 in
let rec loop i j =
if i = len1 && j = len2 then true (* match! *)
else if i = len1 || j = len2 then false (* no match - different lengths *)
else (
let x1 = getxdigit fp1.[i] and x2 = getxdigit fp2.[j] in
match x1, x2 with
| Some x1, Some x2 when x1 = x2 -> loop (i+1) (j+1)
| Some x1, Some x2 -> false (* no match - different content *)
| Some _, None -> loop i (j+1)
| None, Some _ -> loop (i+1) j
| None, None -> loop (i+1) (j+1)
)
in
loop 0 0
and getxdigit = function
| '0'..'9' as c -> Some (Char.code c - Char.code '0')
| 'a'..'f' as c -> Some (Char.code c - Char.code 'a')
| 'A'..'F' as c -> Some (Char.code c - Char.code 'A')
| _ -> None
let rec verify t filename =
if t.check_signature then (
let args = quote filename in
do_verify t args
)
and verify_detached t filename sigfile =
if t.check_signature then (
match sigfile with
| None ->
eprintf (f_"virt-builder: error: there is no detached signature file\nThis probably means the index file is missing a sig=... line.\nYou can use --no-check-signature to ignore this error, but that means\nyou are susceptible to man-in-the-middle attacks.\n");
exit 1
| Some sigfile ->
let args = sprintf "%s %s" (quote sigfile) (quote filename) in
do_verify t args
)
and do_verify t args =
import_key t;
let status_file = Filename.temp_file "vbstat" ".txt" in
let cmd =
sprintf "%s --verify%s --status-file %s %s"
t.gpg (if t.debug then "" else " -q --logger-file /dev/null")
(quote status_file) args in
if t.debug then eprintf "%s\n%!" cmd;
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"virt-builder: error: GPG failure: could not verify digital signature of file\nTry:\n - Use the '-v' option and look for earlier error messages.\n - Delete the cache: virt-builder --delete-cache\n - Check no one has tampered with the website or your network!\n");
exit 1
);
(* Check the fingerprint is who it should be. *)
let status = read_whole_file status_file in
unlink status_file;
let status = string_nsplit "\n" status in
let fingerprint = ref "" in
List.iter (
fun line ->
let line = string_nsplit " " line in
match line with
| "[GNUPG:]" :: "VALIDSIG" :: fp :: _ -> fingerprint := fp
| _ -> ()
) status;
if not (equal_fingerprints !fingerprint t.fingerprint) then (
eprintf (f_"virt-builder: error: fingerprint of signature does not match the expected fingerprint!\n found fingerprint: %s\n expected fingerprint: %s\n")
!fingerprint t.fingerprint;
exit 1
)
(* Import the default public key, if it's the default fingerprint. *)
and import_key t =
if not !key_imported && equal_fingerprints t.fingerprint default_fingerprint
then (
let filename, chan = Filename.open_temp_file "vbpubkey" ".asc" in
output_string chan default_pubkey;
close_out chan;
let cmd = sprintf "%s --import %s%s"
t.gpg (quote filename)
(if t.debug then "" else " >/dev/null 2>&1") in
let r = Sys.command cmd in
if r <> 0 then (
eprintf (f_"virt-builder: error: could not import public key\nUse the '-v' option and look for earlier error messages.\n");
exit 1
);
unlink filename;
key_imported := true
)

28
builder/sigchecker.mli Normal file
View File

@@ -0,0 +1,28 @@
(* virt-builder
* Copyright (C) 2013 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.
*)
type t
val create : debug:bool -> gpg:string -> ?fingerprint:string -> check_signature:bool -> t
val verify : t -> string -> unit
(** Verify the file is signed (if check_signature is true). *)
val verify_detached : t -> string -> string option -> unit
(** Verify the file is signed against the detached signature
(if check_signature is true). *)

20
builder/test-virt-builder.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash -
# libguestfs virt-builder test script
# Copyright (C) 2013 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

986
builder/virt-builder.pod Normal file
View File

@@ -0,0 +1,986 @@
=encoding utf8
=head1 NAME
virt-builder - Build virtual machine images quickly
=head1 SYNOPSIS
virt-builder [-o|--output DISKIMAGE] [--size SIZE] [--format raw|qcow2]
[--attach ISOFILE]
[--install PKG,[PKG...]]
[--root-password ...]
[--upload FILE:DEST]
[--run SCRIPT] [--run-command 'CMD ARGS ...']
[--firstboot SCRIPT] [--firstboot-command 'CMD ARGS ...']
[--firstboot-install PKG,[PKG...]]
os-version
virt-builder -l|--list [--long]
virt-builder --delete-cache
virt-builder --get-kernel DISKIMAGE
[--format raw|qcow2] [--output OUTPUTDIR]
=head1 DESCRIPTION
Virt-builder is a tool for quickly building new virtual machines. You
can build a variety of VMs for local or cloud use, usually within a
few minutes or less. Virt-builder also has many ways to customize
these VMs. Everything is run from the command line and nothing
requires root privileges, so automation and scripting is simple.
Note that virt-builder does not install guests from scratch. It takes
cleanly prepared, digitally signed OSes and customizes them. This
approach is used because it is much faster, but if you need to do
fresh installs you may want to look at L<virt-install(1)> and
L<oz-install(1)>.
The easiest way to get started is by looking at the examples in the
next section.
=head1 EXAMPLES
=head2 List the virtual machines available
virt-builder --list
will list out the operating systems available to install. A selection
of freely redistributable OSes is available as standard. You can add
your own too (see below).
=head2 Build a virtual machine
virt-builder fedora-20
will build a Fedora 20 image. This will have all default
configuration (minimal size, no user accounts, random root password,
only the bare minimum installed software, etc.).
Note you I<do not need to run this command as root>.
The first time this runs it has to download the template over the
network, but this gets cached (see L</CACHING>).
The name of the output file is derived from the template name, so
above it will be C<fedora-20.img>. You can change the output filename
using the I<-o> option:
virt-builder fedora-20 -o mydisk.img
You can also use the I<-o> option to write to existing devices or
logical volumes.
virt-builder fedora-20 --format qcow2
As above, but write the output in qcow2 format to C<fedora-20.qcow2>.
virt-builder fedora-20 --size 20G
As above, but the output size will be 20 GB. The guest OS is resized
as it is copied to the output (automatically, using
L<virt-resize(1)>).
=head2 Setting the root password
virt-builder fedora-20 --root-password file:/tmp/rootpw
Create a Fedora 20 image. The root password is taken from the file
C</tmp/rootpw>.
Note if you I<don't> set I<--root-password> then the guest is given
a I<random> root password.
You can also create user accounts. See L</USERS AND PASSWORDS> below.
=head2 Installing software
To install packages from the ordinary (guest) software repository
(eg. yum or apt):
virt-builder fedora-20 --install "inkscape,+Xfce Desktop"
C<+> is used to install groups of packages (see L<yum(8)>).
=head2 Customizing the installation
There are four options that let you run shell scripts to customize the
installation. They are: I<--run>/I<--run-command>, which run a shell
script or command while the disk image is being generated and lets you
add or edit files that go into the disk image. And
I<--firstboot>/I<--firstboot-command>, which let you add
scripts/commands that are run the first time the guest boots.
For example:
cat <<'EOF' > /tmp/yum-update.sh
yum -y update
EOF
virt-builder fedora-20 --firstboot /tmp/yum-update.sh
or simply:
virt-builder fedora-20 --firstboot-command 'yum -y update'
which makes the C<yum update> command run once the first time the
guest boots.
Or:
cat <<'EOF' > /tmp/no-gpg-sigs.sh
sed -i 's/gpgcheck=1/gpgcheck=0/' /etc/yum.conf
EOF
virt-builder fedora-20 --run /tmp/no-gpg-sigs.sh
which edits C</etc/yum.conf> inside the disk image (during disk image
creation, long before boot).
You can combine these options, and have multiple of either or both
sets of scripts.
=head1 OPTIONS
=over 4
=item B<--help>
Display help.
=item B<--attach> ISOFILE
During the customization phase, the given disk is attached to the
libguestfs appliance. This is used to provide extra software
repositories or other data for customization.
You probably want to ensure the volume(s) or filesystems in the
attached disks are labelled (or an ISO volume name) so that you can
mount them by label in your run-scripts:
mkdir /tmp/mount
mount LABEL=EXTRA /tmp/mount
You can have multiple I<--attach> options, and the format can be any
disk format (not just an ISO).
See also: I<--run>,
L</Installing packages at build time from a side repository>,
L<virt-make-fs(1)>.
=item B<--attach-format> FORMAT
Specify the disk format for the next I<--attach> option. The
C<FORMAT> is usually C<raw> or C<qcow2>. Use C<raw> for ISOs.
=item B<--cache> DIR
=item B<--no-cache>
I<--cache> DIR sets the directory to use/check for cached template
files. If not set, defaults to either
C<$XDG_CACHE_HOME/virt-builder/> or C<$HOME/.cache/virt-builder/>.
I<--no-cache> disables template caching.
=item B<--check-signature>
=item B<--no-check-signature>
Check/don't check the digital signature of the OS template. The
default is to check the signature and exit if it is not correct.
Using I<--no-check-signature> bypasses this check.
See also I<--fingerprint>.
=item B<--curl> CURL
Specify an alternate L<curl(1)> binary. You can also use this to add
curl parameters, for example to disable https certificate checks:
virt-builder --curl "curl --insecure" [...]
=item B<--delete-cache>
Delete the template cache. See L</CACHING>.
=item B<--fingerprint> 'AAAA BBBB ...'
Check that the digital signature is signed by the key with the given
fingerprint. (The fingerprint is a long string, usually written as 10
groups of 4 hexadecimal digits).
If signature checking is enabled and the I<--fingerprint> option is
not given, then this checks the download was signed by
S<F777 4FB1 AD07 4A7E 8C87 67EA 9173 8F73 E1B7 68A0> (which is
S<Richard W.M. Jones's> key).
You can also set the C<VIRT_BUILDER_FINGERPRINT> environment variable.
=item B<--firstboot> SCRIPT
=item B<--firstboot-command> 'CMD ARGS ...'
Install C<SCRIPT> inside the guest, so that when the guest first boots
up, the script runs (as root, late in the boot process).
The script is automatically chmod +x after installation in the guest.
The alternative version I<--firstboot-command> is the same, but it
conveniently wraps the command up in a single line script for you.
You can have multiple I<--firstboot> and I<--firstboot-command>
options. They run in the same order that they appear on the command
line.
See also I<--run>.
=item B<--firstboot-install> PKG[,PKG,...]
Install the named packages (a comma-separated list). These are
installed when the guest first boots using the guest's package manager
(eg. apt, yum, etc.) and the guest's network connection.
For an overview on the different ways to install packages, see
L</INSTALLING PACKAGES>.
=item B<--format> qcow2
=item B<--format> raw
Select the output format. The default is I<raw>.
=item B<--get-kernel> IMAGE
This option extracts the kernel and initramfs from a previously built
disk image called C<IMAGE> (in fact it works for any VM disk image,
not just ones built using virt-builder).
The kernel and initramfs are written to the current directory, unless
you also specify the I<--output> C<outputdir> B<directory> name.
The format of the disk image is automatically detected unless you
specify it by using the I<--format> option.
In the case where the guest contains multiple kernels, the one with
the highest version number is chosen. To extract arbitrary kernels
from the disk image, see L<guestfish(1)>. To extract the entire
C</boot> directory of a guest, see L<virt-copy-out(1)>.
=item B<--gpg> GPG
Specify an alternate L<gpg(1)> (GNU Privacy Guard) binary. You can
also use this to add gpg parameters, for example to specify an
alternate home directory:
virt-builder --gpg "gpg --homedir /tmp" [...]
=item B<--install> PKG[,PKG,...]
Install the named packages (a comma-separated list). These are
installed during the image build using the guest's package manager
(eg. apt, yum, etc.) and the host's network connection.
For an overview on the different ways to install packages, see
L</INSTALLING PACKAGES>.
=item B<-l>
=item B<--list>
=item B<--list --long>
List available templates.
The alternative I<--list --long> form shows lots more details about
each operating system option.
See also: I<--source>, L</CREATING YOUR OWN TEMPLATES>.
=item B<--network>
=item B<--no-network>
Enable or disable network access from the guest during the installation.
Enabled is the default. Use I<--no-network> to disable access.
If you use I<--no-network> then certain other options such as
I<--install> will not work.
This does not affect whether the guest can access the network once it
has been booted, because that is controlled by your hypervisor or
cloud environment and has nothing to do with virt-builder.
Generally speaking you should I<not> use I<--no-network>. But here
are some reasons why you might want to:
=over 4
=item 1.
Because the libguestfs backend that you are using doesn't support the
network. (See: L<guestfs(3)/BACKEND>).
=item 2.
Any software you need to install comes from an attached ISO, so you
don't need the network.
=item 3.
You don't want untrusted guest code trying to access your host network
when running virt-builder. This is particularly an issue when you
don't trust the source of the operating system templates.
=item 4.
You don't have a host network (eg. in secure/restricted environments).
=back
=item B<-o> filename
=item B<--output> filename
Write the output to C<filename>. If you don't specify this option,
then the output filename is generated by taking the C<os-version> or
basename of the template, removing any extensions, and adding C<.img>
(for raw format) or C<.qcow2> (for qcow2 format).
Note that the output filename could be a device, partition or logical
volume.
=item B<--password-crypto> password-crypto
Set the password encryption to C<md5>, C<sha256> or C<sha512>.
C<sha256> and C<sha512> require glibc E<ge> 2.7 (check crypt(3) inside
the guest).
C<md5> will work with relatively old Linux guests (eg. RHEL 3), but
is not secure against modern attacks.
The default is C<sha512> unless libguestfs detects an old guest that
didn't have support for SHA-512, in which case it will use C<md5>.
You can override libguestfs by specifying this option.
=item B<--quiet>
Don't print ordinary progress messages.
=item B<--root-password> PASSWORD
Set the root password.
See L</USERS AND PASSWORDS> below for the format of the C<PASSWORD>
field, and also how to set up user accounts.
Note if you I<don't> set I<--root-password> then the guest is given
a I<random> root password.
=item B<--run> SCRIPT
=item B<--run-command> 'CMD ARGS ...'
Run the shell script (or any program) called C<SCRIPT> on the disk
image. The script runs virtualized inside a small appliance, chrooted
into the guest filesystem.
The script is automatically chmod +x.
If libguestfs supports it then a limited network connection is
available but it only allows outgoing network connections. You can
also attach data disks (eg. ISO files) as another way to provide data
(eg. software packages) to the script without needing a network
connection.
The alternative version I<--run-command> is the same, but it
conveniently wraps the command up in a single line script for you.
You can have multiple I<--run> and I<--run-command> options. They run
in the same order that they appear on the command line.
See also I<--firstboot>, I<--attach>.
=item B<--size> SIZE
Select the size, where the size can be specified using common names
such as C<32G> (32 gigabytes) etc.
If the size is not specified, then one of two things happens. If the
output is a file, then the size is the same as the template (this is
most likely I<not> what you want). If the output is a device,
partition, etc then the size of that device is used.
=item B<--source> URL
Set the source URL to look for templates. If not specified it
defaults to L<http://libguestfs.org/download/builder/index.asc>
See also L</CREATING YOUR OWN TEMPLATES> below.
You can also set the C<VIRT_BUILDER_SOURCE> environment variable.
Note that you should not point I<--source> to sources that you don't
trust (unless the source is signed by someone you do trust). See also
the I<--no-network> option.
=item B<--upload> FILE:DEST
Upload local file C<FILE> to destination C<DEST> in the disk image.
File owner and permissions from the original are preserved, so you
should set them to what you want them to be in the disk image.
=item B<-v>
=item B<--verbose>
Enable debug messages and/or produce verbose output.
When reporting bugs, use this option and attach the complete output to
your bug report.
=item B<-V>
=item B<--version>
Display version number and exit.
=back
=head1 REFERENCE
=head2 INSTALLING PACKAGES
There are several approaches to installing packages or applications in
the guest which have different trade-offs.
=head3 Installing packages at build time
If the guest OS you are installing is similar to the host OS (eg.
both are Linux), and if libguestfs supports network connections, then
you can use I<--install> to install packages like this:
virt-builder fedora-20 --install inkscape
This uses the guest's package manager but the host's network
connection.
=head3 Installing packages at first boot
Another option is to install the packages when the guest first boots:
virt-builder fedora-20 --firstboot-install inkscape
This uses the guest's package manager and the guest's network
connection.
The downsides are that it will take the guest a lot longer to boot
first time, and there's nothing much you can do if package
installation fails (eg. because a network problem means the guest
can't reach the package repositories).
=head3 Installing packages at build time from a side repository
If the software you want to install is not available in the main
package repository of the guest, then you can add a side repository.
Usually this is presented as an ISO (CD disk image) file containing
extra packages.
Create a script that mounts the ISO and sets up the repository. For
yum, create /tmp/install.sh containing:
mkdir /tmp/mount
# Assume the volume label of the CD is 'EXTRA':
mount LABEL=EXTRA /tmp/mount
cat <<'EOF' > /etc/yum.repos.d/extra.repo
[extra]
name=extra
baseurl=file:///tmp/mount
enabled=1
EOF
yum -y install famousdatabase
For apt, create /tmp/install.sh containing:
mkdir /tmp/mount
# Assume the volume label of the CD is 'EXTRA':
mount LABEL=EXTRA /tmp/mount
apt-cdrom -d=/tmp/mount add
apt-get -y install famousdatabase
Use the I<--attach> option to attach the CD:
virt-builder fedora 20 --attach extra.iso --run /tmp/install.sh
=head2 USERS AND PASSWORDS
The I<--root-password> option is used to change the root password
(otherwise a random password is used). This option has the following
formats:
=over 4
=item B<--root-password> file:FILENAME
Read the root password from C<FILENAME>. The whole first line
of this file is the replacement password. Any other lines are
ignored. You should create the file with mode 0600 to ensure
no one else can read it.
=item B<--root-password> password:PASSWORD
Set the root password to the literal string C<PASSWORD>.
B<Note: this is not secure> since any user on the same machine can
see the cleartext password using L<ps(1)>.
=back
=head3 Creating user accounts
To create user accounts, use the L<useradd(8)> command with
L<--firstboot-command> like this:
virt-sysprep --firstboot-command \
'useradd -m -p "" rjones ; chage -d 0 rjones'
The above command will create an C<rjones> account with no password,
and force the user to set a password when they first log in. There
are other ways to manage passwords, see L<useradd(8)> for details.
=head2 INSTALLATION PROCESS
When you invoke virt-builder, installation proceeds as follows:
=over 4
=item *
The template image is downloaded.
If the template image is present in the cache, the cached version
is used instead. (See L</CACHING>).
=item *
The template signature is checked.
=item *
If the template image is xz-compressed: If L<nbdkit(1)> and
L<nbdkit-xz-plugin(1)> are both installed, nbdkit is used to
transparently uncompress the image on the fly. Else it is
uncompressed to a temporary disk which takes more disk space.
=item *
The template image is resized into the destination, using
L<virt-resize(1)>.
=item *
Extra disks are attached (I<--attach>).
=item *
A new random seed is generated for the guest.
=item *
Packages are installed (I<--install>).
=item *
The root password is changed (I<--root-password>).
=item *
Files are uploaded (I<--upload>).
=item *
Firstboot scripts are installed (I<--firstboot>,
I<--firstboot-command>, I<--firstboot-install>).
Note that although firstboot scripts are installed at this step, they
do not run until the guest is booted first time. Firstboot scripts
will run in the order they appear on the command line.
=item *
Scripts are run (I<--run>, I<--run-command>).
Scripts run in the order they appear on the command line.
=back
=head2 CREATING YOUR OWN TEMPLATES
For serious virt-builder use, you may want to create your own
repository of templates.
Out of the box, virt-builder downloads the file
L<http://libguestfs.org/download/builder/index.asc> which is an index
of available templates plus some information about each one, wrapped
up in a digital signature. The command C<virt-builder --list> lists
out the information in this index file.
You can set up your own site containing an index file and some
templates, and then point virt-builder at the site by using the
I<--source> option:
virt-builder --source https://example.com/builder/index.asc \
--fingerprint 'AAAA BBBB ...' \
--list
(Note setting the environment variables C<VIRT_BUILDER_SOURCE> and
C<VIRT_BUILDER_FINGERPRINT> may be easier to type!)
=head3 Setting up a GPG key
If you don't have a GnuPG key, you will need to set one up. (Strictly
speaking this is optional, but if your index and template files are
not signed then virt-builder users will have to use the
I<--no-check-signature> flag every time they use virt-builder.)
To create a key, see the GPG manual
L<http://www.gnupg.org/gph/en/manual.html>.
Export your GPG public key and add it to the keyring of all
virt-builder users:
gpg --export -a "you@example.com" > pubkey
# For each virt-builder user:
gpg --import pubkey
Also find the fingerprint of your key:
gpg --list-keys --fingerprint
=head3 Create the templates
There are many ways to create the templates. For example you could
clone existing guests (see L<virt-sysprep(1)>), or you could install a
guest by hand (L<virt-install(1)>). To see how the templates were
created for virt-builder, look at the scripts in
C<libguestfs.git/builder/website>
For best results when compressing the templates, use the following xz
options (see L<nbdkit-xz-plugin(1)> for further explanation):
xz --best --block-size=16777216 disk
=head3 Creating and signing the index file
The index file has a simple text format (shown here without the
digital signature):
[fedora-18]
name=Fedora® 18
osinfo=fedora18
file=fedora-18.xz
sig=fedora-18.xz.sig
format=raw
size=6442450944
compressed_size=148947524
expand=/dev/sda3
[fedora-19]
name=Fedora® 19
osinfo=fedora19
file=fedora-19.xz
sig=fedora-19.xz.sig
revision=3
format=raw
size=4294967296
compressed_size=172190964
expand=/dev/sda3
The part in square brackets is the C<os-version>, which is the same
string that is used on the virt-builder command line to build that OS.
After preparing the C<index> file in the correct format, clearsign it
using the following command:
gpg --clearsign --armor index
This will create the final file called C<index.asc> which can be
uploaded to the server (and is the I<--source> URL). As noted above,
signing the index file is optional, but recommended.
The following fields can appear:
=over 4
=item C<name=NAME>
The user-friendly name of this template. This is displayed in the
I<--list> output but is otherwise not significant.
=item C<osinfo=ID>
This optional field maps the operating system to the associated
libosinfo ID. Virt-builder does not use it (yet).
=item C<file=PATH>
The path (relative to the index) of the xz-compressed template.
Note that absolute paths or URIs are B<not> permitted here. This is
because virt-builder has a "same origin" policy for templates so they
cannot come from other servers.
=item C<sig=PATH>
The path (relative to the index) of the GPG detached signature of the
xz file.
Note that absolute paths or URIs are B<not> permitted here. This is
because virt-builder has a "same origin" policy for templates so they
cannot come from other servers.
The file can be created as follows:
gpg --detach-sign --armor -o disk.xz.sig disk.xz
The signature is optional, but if you don't have it then virt-builder
users will need to use the I<--no-check-signature> option in order
to install from this template.
=item C<revision=N>
The revision is an integer which is used to control the template
cache. Increasing the revision number causes clients to download the
template again even if they have a copy in the cache.
The revision number is optional. If omitted it defaults to C<1>.
=item C<format=raw>
=item C<format=qcow2>
Specify the format of the disk image (before it was compressed). If
not given, the format is autodetected, but generally it is better to
be explicit about the intended format.
Note this is the source format, which is different from the
I<--format> option (requested output format). Virt-builder does
on-the-fly conversion from the source format to the requested output
format.
=item C<size=NNN>
The virtual size of the image in bytes. This is the size of the image
when uncompressed. If using a non-raw format such as qcow2 then it
means the virtual disk size, not the size of the qcow2 file.
This field is required.
Virt-builder also uses this as the minimum size that users can request
via the I<--size> option, or as the default size if there is no
I<--size> option.
=item C<compressed_size=NNN>
The compressed size of the disk image in bytes. This is just used for
information (when using I<--list --long>).
=item C<expand=/dev/sdaX>
When expanding the image to its final size, instruct L<virt-resize(1)>
to expand the named partition in the guest image to fill up all
available space. This works like the virt-resize I<--expand> option.
You should usually put the device name of the guest's root filesystem here.
It's a good idea to use this, but not required. If the field is
omitted then virt-resize will create an extra partition at the end of
the disk to cover the free space, which is much less user-friendly.
=item C<lvexpand=/dev/VolGroup/LogVol>
When expanding the image to its final size, instruct L<virt-resize(1)>
to expand the named logical volume in the guest image to fill up all
available space. This works like the virt-resize I<--lv-expand> option.
If the guest uses LVM2 you should usually put the LV of the guest's
root filesystem here. If the guest does not use LVM2 or its root
filesystem is not on an LV, don't use this option.
=item C<notes=NOTES>
Any notes that go with this image, especially notes describing what
packages are in the image, how the image was prepared, and licensing
information.
You can use multi-line notes here by indenting each new line with at
least one character of whitespace (even on blank lines):
notes=This image was prepared using
the following kickstart script:
<-- one space at beginning of line
timezone Europe/London
part /boot --fstype ext3
=item C<hidden=true>
Using the hidden flag prevents the template from being listed by the
I<--list> option (but it is still installable). This is used for test
images.
=back
=head3 Running virt-builder against the alternate repository
Ensure each virt-builder user has imported your public key into
their gpg keyring (see above).
Each virt-builder user should export these environment variables:
=over 4
=item *
C<VIRT_BUILDER_SOURCE> to point to the URL of the C<index.asc> file.
=item *
C<VIRT_BUILDER_FINGERPRINT> to contain the fingerprint (long hex
string) of the user who signed the index file and the templates.
=back
Now run virt-builder commands as normal, eg:
virt-builder --list --long
virt-builder os-version
To debug problems, add the C<-v> option to these commands.
=head3 Licensing of templates
You should be aware of the licensing of images that you distribute.
For open source guests, provide a link to the source code in the
C<notes> field and comply with other requirements (eg. around
trademarks).
=head2 CACHING
Since the templates are usually very large, downloaded templates are
cached in the user's home directory.
The location of the cache is C<$XDG_CACHE_HOME/virt-builder/> or
C<$HOME/.cache/virt-builder>. This directory can be deleted after use
if you want to save space by doing:
virt-builder --delete-cache
To disable the template cache, use I<--no-cache>.
Only templates are cached. The index and detached digital signatures
are not cached.
Virt-builder uses L<curl(1)> to download files. Since curl obeys
C<http_proxy> (etc) environment variables, files might also be cached
by your proxy if you have one.
=head2 DIGITAL SIGNATURES
Virt-builder uses GNU Privacy Guard (GnuPG or gpg) to verify that the
index and templates have not been tampered with.
The source points to an index file, which is optionally signed.
Virt-builder downloads the index and checks that the signature is
valid and the signer's fingerprint matches the specified fingerprint
(ie. I<--fingerprint>, C<VIRT_BUILDER_FINGERPRINT>, or a built-in
fingerprint, in that order).
For checking against the built-in public key/fingerprint, this
requires importing the public key into the user's local gpg keyring
(that's just the way that gpg works).
When a template is downloaded, its signature is checked in the same
way.
Although the signatures are optional, if you don't have them then
virt-builder users will have to use I<--no-check-signature> on the
command line. This prevents an attacker from replacing the signed
index file with an unsigned index file and having virt-builder
silently work without checking the signature. In any case it is
highly recommended that you always create signed index and templates.
=head1 ENVIRONMENT VARIABLES
For other environment variables which affect all libguestfs programs,
see L<guestfs(3)/ENVIRONMENT VARIABLES>.
=over 4
=item C<http_proxy>
=item C<https_proxy>
=item C<no_proxy>
Set the proxy for downloads. These environment variables (and more)
are actually interpreted by L<curl(1)>, not virt-builder.
=item C<HOME>
Used to determine the location of the template cache. See L</CACHING>.
=item C<VIRT_BUILDER_FINGERPRINT>
Set the default value for the GPG signature fingerprint (see
I<--fingerprint> option).
=item C<VIRT_BUILDER_SOURCE>
Set the default value for the source URL for the template repository
(see I<--source> option).
=item C<XDG_CACHE_HOME>
Used to determine the location of the template cache. See L</CACHING>.
=back
=head1 EXIT STATUS
This program returns 0 if successful, or non-zero if there was an
error.
=head1 SEE ALSO
L<virt-resize(1)>,
L<virt-install(1)>,
L<virt-sysprep(1)>,
L<oz-install(1)>,
L<guestmount(1)>,
L<nbdkit(1)>,
L<nbdkit-xz-plugin(1)>,
L<gpg(1)>,
L<guestfs(3)>,
L<guestfish(1)>,
L<virt-copy-out(1)>,
L<curl(1)>,
L<http://libguestfs.org/>.
=head1 AUTHOR
Richard W.M. Jones L<http://people.redhat.com/~rjones/>
=head1 COPYRIGHT
Copyright (C) 2013 Red Hat Inc.

1
builder/website/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.xz

42
builder/website/README Normal file
View File

@@ -0,0 +1,42 @@
If you are looking at this file at http://libguestfs.org/download/builder
-------------------------------------------------------------------------
This directory is used by the libguestfs 'virt-builder' to store the
clean, signed OS templates used for building new virtual machines.
The index file is the default source URL and links to the other OS
templates. It has the canonical URL:
http://libguestfs.org/download/builder/index.asc
If you are looking at this file in the git repository
-----------------------------------------------------
libguestfs.git/builder/website/ contains a copy of the website, minus
the huge OS template files (because of their size, they are stored
elsewhere and merged into the website when it is uploaded).
When you use the ./run script to run virt-builder without installing,
the ./run script sets $VIRT_BUILDER_SOURCE to point to this directory.
If you actually want to use this configuration for anything except
simple testing, you will have to download one or more OS templates
from the libguestfs website and put them into the builder/website/
directory.
ie:
./run ./builder/virt-builder fedora-20
will fail unless you have downloaded fedora-20.xz here.
Fedora guests
-------------
The general plan for using kickstart and virt-install is outlined
by Kashyap here:
http://kashyapc.wordpress.com/2011/08/18/unattended-guest-install-with-a-local-kickstart/
If you want to reproduce the builds then the kickstart files are
located in fedora-<N>.ks and the virt-install + other commands are in
fedora-<N>.sh.

View File

@@ -0,0 +1,38 @@
# virt-builder
# Copyright (C) 2013 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.
install
text
reboot
lang en_US.UTF-8
keyboard us
network --bootproto dhcp
rootpw builder
firewall --enabled --ssh
selinux --enforcing
timezone --utc America/New_York
bootloader --location=mbr --append="console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"
zerombr
clearpart --all --initlabel
autopart --type=plain
# Halt the system once configuration has finished.
poweroff
%packages
@core
%end

75
builder/website/fedora-18.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/bash -
# virt-builder
# Copyright (C) 2013 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.
set -e
set -x
# Some configuration.
export http_proxy=http://cache.home.annexia.org:3128
export https_proxy=$http_proxy
export ftp_proxy=$http_proxy
tree=http://mirror.bytemark.co.uk/fedora/linux/releases/18/Fedora/x86_64/os/
# Currently you have to run this script as root.
if [ `id -u` -ne 0 ]; then
echo "You have to run this script as root."
exit 1
fi
# Make sure it's being run from the correct directory.
if [ ! -f fedora-18.ks ]; then
echo "You are running this script from the wrong directory."
exit 1
fi
pwd=`pwd`
virsh undefine tmpf18 ||:
rm -f fedora-18 fedora-18.old
virt-install \
--name=tmpf18 \
--ram 2048 \
--cpu=host --vcpus=2 \
--os-type=linux --os-variant=fedora18 \
--initrd-inject=$pwd/fedora-18.ks \
--extra-args="ks=file:/fedora-18.ks console=tty0 console=ttyS0,115200 proxy=$http_proxy" \
--disk $pwd/fedora-18,size=6 \
--location=$tree \
--nographics \
--noreboot
# The virt-install command should exit after complete installation.
# Remove the guest, we don't want it to be defined in libvirt.
virsh undefine tmpf18
# Sysprep (removes logfiles and so on).
# Note this also touches /.autorelabel so the further installation
# changes that we make will be labelled properly at first boot.
virt-sysprep -a fedora-18
# Sparsify.
mv fedora-18 fedora-18.old
virt-sparsify fedora-18.old fedora-18
rm fedora-18.old
# Compress.
rm -f fedora-18.xz
xz --best --block-size=16777216 fedora-18
# Result:
ls -lh fedora-18.xz

View File

@@ -0,0 +1,17 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.14 (GNU/Linux)
iQIcBAABAgAGBQJSSt3OAAoJEJFzj3Pht2ig8x4QAKoQVfMWxXiFvV3lrHUVsjpG
n+fDcoFJqMKeE/kMLhmlmzF+QDz1qRs3Xlypy7C8B5ii5jbFVCv6q5edeEWrVYE3
HZOeNgUgmVJEiCSgScEXPpVMAOL/zppJ2PjAbg7CNLHQ64HweNkv6F1ePg4NCIoA
Op5yIvzm0gUHN9ONfsJRBLlZGQRu8bdsNhdYOAr3rmdxhuuunIi/17qNrT6eaWFO
2pnGFIyYMn9q2ReXBG8mFIkHlac9ZpT2sR7EKY3LBt0FvGz0qQrNM+9M9bQBwbIQ
dD/IRfSUUQqjCLYLqiFvFVC/pqAAcR1G2rQ20jRLPkpSUFceFL/K5ueI2flEPEJk
mF6WR4MsN4lX4w0iiJvpWwE0jqlLVQ1GnhoE2GDZEHgkth/4l+0pN0Jos8OKINDJ
wB4W1Xc2aXNXaD1JAaebu+CthZNnFEXpa5TrXMFAOnBY4oQ4DgOt/ad1s9Ju4zLX
EI+Zn0Q++l+iMyU2InXnTHoaTagqKqtngvHWGmSuK55dM8jW9HoYsjZZl0oC0lVz
GbXQ/1t1loTBqh+crr4kcP0oKvRFT9YPASadUVThmeYlxOyhvQ0e30IBdz0Te1OR
rqfymVGR8NsGdLW+jCKOfbLEaYShTksTnUmr+yF5w5D2uzZHqZasE7cpwPXAEhuZ
TF7ZoSqz1utSQF9/xBnz
=L4x6
-----END PGP SIGNATURE-----

View File

@@ -0,0 +1,38 @@
# virt-builder
# Copyright (C) 2013 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.
install
text
reboot
lang en_US.UTF-8
keyboard us
network --bootproto dhcp
rootpw builder
firewall --enabled --ssh
selinux --enforcing
timezone --utc America/New_York
bootloader --location=mbr --append="console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"
zerombr
clearpart --all --initlabel
autopart --type=plain
# Halt the system once configuration has finished.
poweroff
%packages
@core
%end

75
builder/website/fedora-19.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/bash -
# virt-builder
# Copyright (C) 2013 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.
set -e
set -x
# Some configuration.
export http_proxy=http://cache.home.annexia.org:3128
export https_proxy=$http_proxy
export ftp_proxy=$http_proxy
tree=http://mirror.bytemark.co.uk/fedora/linux/releases/19/Fedora/x86_64/os/
# Currently you have to run this script as root.
if [ `id -u` -ne 0 ]; then
echo "You have to run this script as root."
exit 1
fi
# Make sure it's being run from the correct directory.
if [ ! -f fedora-19.ks ]; then
echo "You are running this script from the wrong directory."
exit 1
fi
pwd=`pwd`
virsh undefine tmpf19 ||:
rm -f fedora-19 fedora-19.old
virt-install \
--name=tmpf19 \
--ram 2048 \
--cpu=host --vcpus=2 \
--os-type=linux --os-variant=fedora19 \
--initrd-inject=$pwd/fedora-19.ks \
--extra-args="ks=file:/fedora-19.ks console=tty0 console=ttyS0,115200 proxy=$http_proxy" \
--disk $pwd/fedora-19,size=4 \
--location=$tree \
--nographics \
--noreboot
# The virt-install command should exit after complete installation.
# Remove the guest, we don't want it to be defined in libvirt.
virsh undefine tmpf19
# Sysprep (removes logfiles and so on).
# Note this also touches /.autorelabel so the further installation
# changes that we make will be labelled properly at first boot.
virt-sysprep -a fedora-19
# Sparsify.
mv fedora-19 fedora-19.old
virt-sparsify fedora-19.old fedora-19
rm fedora-19.old
# Compress.
rm -f fedora-19.xz
xz --best --block-size=16777216 fedora-19
# Result:
ls -lh fedora-19.xz

View File

@@ -0,0 +1,17 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.14 (GNU/Linux)
iQIcBAABAgAGBQJSSt3WAAoJEJFzj3Pht2igSeMQAI42FK1YU6Fr/LreD9/BdjNH
TUJ3L/B3ZMJwlMT+F4cX9r1uVD5udzpXdClUEcKgdwPdqzPsQvuwLSBS4fn38nZM
mryzNeUjbcNPSPSY6bj/MNuYNlYNaWShhL6UjHBD6XYIhAs6hYp0RGcogcpgRU9Z
0peHFyJBp+uUqCpnBDz00zJht49mgT6ovUA3cGd/vPhHrsrJUusgzzi46X3F90+H
9eRoe6/xzCbmbiD7HUlARnMK8ec0b7H/CWsNNCd0v9KBNyW9QROkbwBMtRcWtQZW
p9h9JGT2N3AmIqRbUbKwfWxGN6AfK+Xtl9YjJb/ugwSlw0Pif055EKNeUZjGUUzk
V4YA9b1Z6mncyL4wxV8VD9w8en+dr5Iu6ga02r7Xr0rIHuTQyQQaKfHvNoWZFmhq
WI2xsSBLr1EYfLEetdqUkEo2f2gpy7f6UKL1bsHi/6oqA3NaOOXiPdVlbVDNOQFe
kXPfjz/8hnzQ/8O/zIiSiDefQObdM4DQDTk5ha/vOu4pt5XevHkusaciPNGUAhx1
JmxEmPF6sqg2Y0xmeFZB2ab5s6VL/CfVGBF5ZFq7QmDj9eNFjrElfv+uN9NqD+ll
YlR/g3f7vFLHSG64ez4yV/Hgmfv1+4DMGi9MNmhFF3u0W5AIxznTsQzj1KFhbvmV
8QUyTDG7lGNqsgsSgb7W
=l9GJ
-----END PGP SIGNATURE-----

35
builder/website/index Normal file
View File

@@ -0,0 +1,35 @@
[fedora-18]
name=Fedora® 18
osinfo=fedora18
file=fedora-18.xz
sig=fedora-18.xz.sig
format=raw
size=6442450944
compressed_size=148947524
expand=/dev/sda3
notes=This Fedora image contains only unmodified @Core group packages.
It is thus very minimal. The kickstart and install script can be
found in the libguestfs git tree:
libguestfs.git/builder/website/fedora-18.ks
libguestfs.git/builder/website/fedora-18.sh
Fedora and the Infinity design logo are trademarks of Red Hat, Inc.
Source and further information is available from http://fedoraproject.org/
[fedora-19]
name=Fedora® 19
osinfo=fedora19
file=fedora-19.xz
sig=fedora-19.xz.sig
format=raw
size=4294967296
compressed_size=172190964
expand=/dev/sda3
notes=This Fedora image contains only unmodified @Core group packages.
It is thus very minimal. The kickstart and install script can be
found in the libguestfs git tree:
libguestfs.git/builder/website/fedora-19.ks
libguestfs.git/builder/website/fedora-19.sh
Fedora and the Infinity design logo are trademarks of Red Hat, Inc.
Source and further information is available from http://fedoraproject.org/

55
builder/website/index.asc Normal file
View File

@@ -0,0 +1,55 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
[fedora-18]
name=Fedora® 18
osinfo=fedora18
file=fedora-18.xz
sig=fedora-18.xz.sig
format=raw
size=6442450944
compressed_size=148947524
expand=/dev/sda3
notes=This Fedora image contains only unmodified @Core group packages.
It is thus very minimal. The kickstart and install script can be
found in the libguestfs git tree:
libguestfs.git/builder/website/fedora-18.ks
libguestfs.git/builder/website/fedora-18.sh
Fedora and the Infinity design logo are trademarks of Red Hat, Inc.
Source and further information is available from http://fedoraproject.org/
[fedora-19]
name=Fedora® 19
osinfo=fedora19
file=fedora-19.xz
sig=fedora-19.xz.sig
format=raw
size=4294967296
compressed_size=172190964
expand=/dev/sda3
notes=This Fedora image contains only unmodified @Core group packages.
It is thus very minimal. The kickstart and install script can be
found in the libguestfs git tree:
libguestfs.git/builder/website/fedora-19.ks
libguestfs.git/builder/website/fedora-19.sh
Fedora and the Infinity design logo are trademarks of Red Hat, Inc.
Source and further information is available from http://fedoraproject.org/
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.14 (GNU/Linux)
iQIcBAEBAgAGBQJSSuCLAAoJEJFzj3Pht2igR3kP/12WWghoeAebywPbRoY0gERN
WvYxYoE5YiFgKnsjEOCuTw5S+0X69sKmUbW/I6eCVOVon/VDJqHNto7IPF3eYL9J
yHSHHOu3PBwzkwXevCYpn+ji6pMw8BFBm08JxwGnLNoTh/1OKTb1RqP2XLepJ/7F
c9AhGhRRo7f0hHHVcuceK3IV4BfKiXCPBvAeCmnHPwc1onMtsex9ztXohjw7N+7m
I7y85K9GJ1gA0fzPuWElBpeDvZlRiINqXLoDSM+5X0AVcPXspFBhT42Tos/N50nW
0SXZgwgp5hLxn/6ytS8UCZqgerWQsJz5YooHuUQ8LKzXUqRYUz7YXkW60nq9VWj9
zS0e6xpasAw+8JukrY/fFZaxx3PDbqFOA/pD3kioQBUNwl2VJl5z58vKD1FEx7EL
HRa8/EB8xlU5dj8bMZ3VKGefYEq6b3iLskhQYVNgzOYoK5k43LPXZAJ0krFGNUrG
UH5JM7hd87xzoMvzIlJQ1T7dCzCyAVrDnUOT8mUAPQJcJIAoMXLqGRnG19odwOfh
SvJ1TnK70SxSxFji4q3XpsTBH+1CZi2l4suju0bXQkfaWhwj9SXDhnwhJR06CkZO
nPFD1MJZuqepitMgV0y4l9THn84piNzKHx9LkZJPeq31vPIJHSCxUFEiAG9c3KU2
kHaqUU4zIWlxaO/yHQmc
=aPYy
-----END PGP SIGNATURE-----

View File

@@ -1639,6 +1639,7 @@ AC_CONFIG_FILES([Makefile
align/Makefile
appliance/Makefile
bash/Makefile
builder/Makefile
cat/Makefile
csharp/Makefile
daemon/Makefile

View File

@@ -1557,6 +1557,7 @@ a different filename by using the C<filename=> prefix.
L<guestfs(3)>,
L<http://libguestfs.org/>,
L<virt-alignment-scan(1)>,
L<virt-builder(1)>,
L<virt-cat(1)>,
L<virt-copy-in(1)>,
L<virt-copy-out(1)>,

View File

@@ -59,6 +59,7 @@ OBJECTS = \
progress-c.o \
uri-c.o \
crypt-c.o \
libdir.cmx \
common_gettext.cmx \
common_utils.cmx \
random_seed.cmx \
@@ -101,6 +102,13 @@ dummy: $(OBJECTS)
.ml.cmx:
$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -c $< -o $@
# This OCaml module has to be generated by make (configure will put
# unexpanded prefix macro in).
libdir.ml: Makefile
echo 'let libdir = "$(libdir)"' > $@-t
mv $@-t $@
# automake will decide we don't need C support in this file. Really
# we do, so we have to provide it ourselves.

View File

@@ -146,6 +146,37 @@ let string_random8 =
) [1;2;3;4;5;6;7;8]
)
(* Drop elements from a list while a predicate is true. *)
let rec dropwhile f = function
| [] -> []
| x :: xs when f x -> dropwhile f xs
| xs -> xs
(* Take elements from a list while a predicate is true. *)
let rec takewhile f = function
| x :: xs when f x -> x :: takewhile f xs
| _ -> []
let rec filter_map f = function
| [] -> []
| x :: xs ->
match f x with
| Some y -> y :: filter_map f xs
| None -> filter_map f xs
(* Timestamped progress messages, used for ordinary messages when not
* --quiet.
*)
let start_t = Unix.time ()
let make_message_function ~quiet fs =
let p str =
if not quiet then (
let t = sprintf "%.1f" (Unix.time () -. start_t) in
printf "[%8s] %s\n%!" t str
)
in
ksprintf p fs
let error ~prog fs =
let display str =
wrap ~chan:stderr (sprintf (f_"%s: error: %s") prog str);
@@ -175,6 +206,26 @@ let read_whole_file path =
close_in chan;
Buffer.contents buf
(* Parse a size field, eg. "10G". *)
let parse_size =
let const_re = Str.regexp "^\\([.0-9]+\\)\\([bKMG]\\)$" in
fun ~prog field ->
let matches rex = Str.string_match rex field 0 in
let sub i = Str.matched_group i field in
let size_scaled f = function
| "b" -> Int64.of_float f
| "K" -> Int64.of_float (f *. 1024.)
| "M" -> Int64.of_float (f *. 1024. *. 1024.)
| "G" -> Int64.of_float (f *. 1024. *. 1024. *. 1024.)
| _ -> assert false
in
if matches const_re then (
size_scaled (float_of_string (sub 1)) (sub 2)
)
else
error ~prog "%s: cannot parse size field" field
(* Parse a size field, eg. "10G", "+20%" etc. Used particularly by
* virt-resize --resize and --resize-force options.
*)

View File

@@ -51,6 +51,7 @@ MANPAGES = \
libguestfs-make-fixed-appliance.1 \
libguestfs-test-tool.1 \
virt-alignment-scan.1 \
virt-builder.1 \
virt-cat.1 \
virt-copy-in.1 \
virt-copy-out.1 \

View File

@@ -1,5 +1,6 @@
../align/virt-alignment-scan.pod
../appliance/libguestfs-make-fixed-appliance.pod
../builder/virt-builder.pod
../cat/virt-cat.pod
../cat/virt-filesystems.pod
../cat/virt-ls.pod

View File

@@ -51,6 +51,7 @@ MANPAGES = \
libguestfs-make-fixed-appliance.1 \
libguestfs-test-tool.1 \
virt-alignment-scan.1 \
virt-builder.1 \
virt-cat.1 \
virt-copy-in.1 \
virt-copy-out.1 \

View File

@@ -1,8 +1,15 @@
builder/builder.ml
builder/downloader.ml
builder/get_kernel.ml
builder/index_parser.ml
builder/list_entries.ml
builder/sigchecker.ml
mllib/common_gettext.ml
mllib/common_utils.ml
mllib/common_utils_tests.ml
mllib/crypt.ml
mllib/firstboot.ml
mllib/libdir.ml
mllib/password.ml
mllib/progress.ml
mllib/random_seed.ml

14
run.in
View File

@@ -72,11 +72,12 @@ run: warning: has not been set automatically.
EOF
fi
# Set the PATH to contain guestunmount (called by umount-local API).
# Set the PATH to contain guestunmount (called by umount-local API)
# and virt-resize (called by virt-builder).
if [ -z "$PATH" ]; then
PATH="$b/fuse"
PATH="$b/fuse:$b/resize"
else
PATH="$b/fuse:$PATH"
PATH="$b/fuse:$b/resize:$PATH"
fi
export PATH
@@ -88,6 +89,13 @@ else
fi
export LD_LIBRARY_PATH
# Make virt-builder use the local website copy to avoid hitting
# the network all the time.
if [ -z "$VIRT_BUILDER_SOURCE" ]; then
VIRT_BUILDER_SOURCE="file://$s/builder/website/index.asc"
export VIRT_BUILDER_SOURCE
fi
# For Perl.
if [ -z "$PERL5LIB" ]; then
PERL5LIB="$b/perl/blib/lib:$b/perl/blib/arch"

View File

@@ -4210,6 +4210,10 @@ Bash tab-completion scripts.
Various build scripts used by autotools.
=item C<builder>
L<virt-builder(1)> command and documentation.
=item C<cat>
The L<virt-cat(1)>, L<virt-filesystems(1)> and L<virt-ls(1)> commands
@@ -4661,6 +4665,7 @@ L<guestfs-ruby(3)>,
L<guestfish(1)>,
L<guestmount(1)>,
L<virt-alignment-scan(1)>,
L<virt-builder(1)>,
L<virt-cat(1)>,
L<virt-copy-in(1)>,
L<virt-copy-out(1)>,