[gnome-boxes] installer: Use libosinfo APIs for the automated installations
- From: Zeeshan Ali Khattak <zeeshanak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes] installer: Use libosinfo APIs for the automated installations
- Date: Fri, 7 Dec 2012 19:48:16 +0000 (UTC)
commit cd3a70548fba61ec279b2aeb7c092c6d947e2b3b
Author: Fabiano FidÃncio <fabiano fidencio org>
Date: Thu Nov 29 03:14:51 2012 +0200
installer: Use libosinfo APIs for the automated installations
Let's drop all OSes-specific automation code in favor of new
automated installation API provided by libosinfo.
Known issues:
1. Direct boot commandline should come from libosinfo.
2. We still have os-specific code for deciding what disk to use for
unatteded files.
3. This breaks viostor drivers installation.
I'll work on #1 next but hardcoded value for now isn't that bad as what
we do currently isn't any different. Same for #2 and IMO the solution for
that would go under (yet to be created) libvirt-builder.
As for #3, a following patch in this series fixes that using new
libosinfo API.
co-author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
https://bugzilla.gnome.org/show_bug.cgi?id=676537
src/media-manager.vala | 29 +--
src/unattended-installer.vala | 420 ++++++++++++++++++++++++++++-------------
2 files changed, 300 insertions(+), 149 deletions(-)
---
diff --git a/src/media-manager.vala b/src/media-manager.vala
index bc4d67a..f9091a4 100644
--- a/src/media-manager.vala
+++ b/src/media-manager.vala
@@ -151,27 +151,14 @@ private class Boxes.MediaManager : Object {
if (media.os == null)
return media;
- switch (media.os.distro) {
- case "fedora":
- return new FedoraInstaller.from_media (media);
-
- case "win":
- switch (media.os.short_id) {
- case "win7":
- case "win2k8":
- return new Win7Installer.from_media (media);
-
- case "winxp":
- case "win2k":
- case "win2k3":
- return new WinXPInstaller.from_media (media);
-
- default:
- return media;
- }
-
- default:
+ var install_scripts = media.os.get_install_script_list ();
+ var filter = new Filter ();
+ filter.add_constraint (INSTALL_SCRIPT_PROP_PROFILE, INSTALL_SCRIPT_PROFILE_DESKTOP);
+ install_scripts = install_scripts.new_filtered (filter);
+
+ if (install_scripts.get_length () > 0)
+ return new UnattendedInstaller.from_media (media, install_scripts);
+ else
return media;
- }
}
}
diff --git a/src/unattended-installer.vala b/src/unattended-installer.vala
index 86c8681..c5a43b9 100644
--- a/src/unattended-installer.vala
+++ b/src/unattended-installer.vala
@@ -1,19 +1,26 @@
// This file is part of GNOME Boxes. License: LGPLv2+
using GVirConfig;
+using Osinfo;
public errordomain UnattendedInstallerError {
SETUP_INCOMPLETE
}
-private abstract class Boxes.UnattendedInstaller: InstallerMedia {
+private class Boxes.UnattendedInstaller: InstallerMedia {
public override bool need_user_input_for_vm_creation {
get {
return !live; // No setup required by live media (and unknown medias are not UnattendedInstaller instances)
}
}
- public virtual bool ready_for_express { get { return username != ""; } }
+ public bool ready_for_express {
+ get {
+ return username != "" &&
+ (product_key_format == null ||
+ key_entry.text_length == product_key_format.length);
+ }
+ }
public override bool ready_to_create {
get {
@@ -21,6 +28,12 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
}
}
+ public override bool supports_virtio_disk {
+ get {
+ return base.supports_virtio_disk || has_viostor_drivers;
+ }
+ }
+
public bool express_install {
get { return express_toggle.active; }
}
@@ -46,16 +59,17 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
}
}
- public DataStreamNewlineType newline_type;
public File? disk_file;
+ public File? kernel_file;
+ public File? initrd_file;
+ public InstallConfig config;
+ public InstallScriptList scripts;
- private string _extra_iso;
- protected string extra_iso {
- owned get { return lookup_extra_iso (_extra_iso); }
- set { _extra_iso = value; }
- }
+ private bool has_viostor_drivers;
+ private string? product_key_format;
protected GLib.List<UnattendedFile> unattended_files;
+ private UnattendedAvatarFile avatar_file;
protected Gtk.Grid setup_grid;
protected int setup_grid_n_rows;
@@ -65,33 +79,17 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
protected Gtk.Switch express_toggle;
protected Gtk.Entry username_entry;
protected Gtk.Entry password_entry;
+ private Gtk.Entry key_entry;
protected string timezone;
protected string lang;
protected string hostname;
+ private string kbd;
- protected AvatarFormat avatar_format;
+ ulong key_inserted_id; // ID of key_entry.insert_text signal handler
- private static Regex username_regex;
- private static Regex password_regex;
- private static Regex timezone_regex;
- private static Regex lang_regex;
- private static Regex host_regex;
private static Fdo.Accounts? accounts;
- static construct {
- try {
- username_regex = new Regex ("BOXES_USERNAME");
- password_regex = new Regex ("BOXES_PASSWORD");
- timezone_regex = new Regex ("BOXES_TZ");
- lang_regex = new Regex ("BOXES_LANG");
- host_regex = new Regex ("BOXES_HOSTNAME");
- } catch (RegexError error) {
- // This just can't fail
- assert_not_reached ();
- }
- }
-
construct {
/* We can't do this in the class constructor as the sync call can
cause deadlocks, see bug #676679. */
@@ -104,10 +102,7 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
}
}
- public UnattendedInstaller.from_media (InstallerMedia media,
- string unattended_src_path,
- string unattended_dest_name,
- AvatarFormat? avatar_format = null) throws GLib.Error {
+ public UnattendedInstaller.from_media (InstallerMedia media, InstallScriptList scripts) throws GLib.Error {
os = media.os;
os_media = media.os_media;
label = media.label;
@@ -116,10 +111,15 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
mount_point = media.mount_point;
resources = media.resources;
- newline_type = DataStreamNewlineType.LF;
+ this.scripts = scripts;
+ config = new InstallConfig ("http://live.gnome.org/Boxes/unattended");
unattended_files = new GLib.List<UnattendedFile> ();
- add_unattended_file (new UnattendedTextFile (this, unattended_src_path, unattended_dest_name));
+ foreach (var s in scripts.get_elements ()) {
+ var script = s as InstallScript;
+ var filename = script.get_expected_filename ();
+ add_unattended_file (new UnattendedTextFile (this, script, filename));
+ }
var time = TimeVal ();
var date = new DateTime.from_timeval_local (time);
@@ -127,10 +127,8 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
var langs = Intl.get_language_names ();
lang = langs[0];
-
- this.avatar_format = avatar_format;
- if (avatar_format == null)
- this.avatar_format = new AvatarFormat ();
+ kbd = lang;
+ product_key_format = get_product_key_format ();
setup_ui ();
}
@@ -148,6 +146,15 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
foreach (var unattended_file in unattended_files)
yield unattended_file.copy (cancellable);
+
+ //FIXME: Linux-specific. Any generic way to achieve this?
+ if (os_media.kernel_path != null && os_media.initrd_path != null) {
+ var extractor = new ISOExtractor (device_file);
+
+ yield extractor.mount_media (cancellable);
+
+ yield extract_boot_files (extractor, cancellable);
+ }
} catch (GLib.Error error) {
clean_up ();
// An error occurred when trying to setup unattended installation, but it's likely that a non-unattended
@@ -165,18 +172,43 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
base.setup_domain_config (domain);
var disk = get_unattended_disk_config ();
- if (disk == null)
- return;
-
- domain.add_device (disk);
+ if (disk != null)
+ domain.add_device (disk);
}
- // Allows us to add an extra install media, for example with more windows drivers
- public void add_extra_iso (Domain domain) {
- if (extra_iso == null)
- return;
+ public void configure_install_script (InstallScript script) {
+ if (password != null) {
+ config.set_user_password (password);
+ config.set_admin_password (password);
+ }
+ if (username != null) {
+ config.set_user_login (username);
+ config.set_user_realname (username);
+ }
+ if (key_entry != null && key_entry.text != null)
+ config.set_reg_product_key (key_entry.text);
+ config.set_l10n_timezone (timezone);
+ config.set_l10n_language (lang);
+ config.set_l10n_keyboard (kbd);
+ config.set_hostname (hostname);
+ config.set_hardware_arch (os_media.architecture);
+
+ // FIXME: Ideally, we shouldn't need to check for distro
+ if (os.distro == "win")
+ config.set_target_disk ("C");
+ else
+ config.set_target_disk (supports_virtio_disk? "vda" : "sda");
+
+ string device_name;
+ get_unattended_disk_info (script.path_format, out device_name);
+ config.set_script_disk (device_name);
+
+ if (avatar_file != null) {
+ var location = ((script.path_format == PathFormat.UNIX)? "/" : "\\") + avatar_file.dest_name;
+ config.set_avatar_location (location);
+ config.set_avatar_disk (config.get_script_disk ());
+ }
- add_cd_config (domain, DomainDiskType.FILE, extra_iso, "hdd", false);
}
public override void populate_setup_vbox (Gtk.VBox setup_vbox) {
@@ -188,7 +220,7 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
setup_vbox.show_all ();
}
- public override List<Pair> get_vm_properties () {
+ public override GLib.List<Pair> get_vm_properties () {
var properties = base.get_vm_properties ();
if (express_install) {
@@ -199,14 +231,17 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
return properties;
}
- public virtual string fill_unattended_data (string data) throws RegexError {
- var str = username_regex.replace (data, data.length, 0, username_entry.text);
- str = password_regex.replace (str, str.length, 0, password);
- str = timezone_regex.replace (str, str.length, 0, timezone);
- str = lang_regex.replace (str, str.length, 0, lang);
- str = host_regex.replace (str, str.length, 0, hostname);
+ public override void set_direct_boot_params (GVirConfig.DomainOs os) {
+ if (kernel_file == null || initrd_file == null)
+ return;
+
+ // FIXME: This commandline should come from libosinfo somehow
+ var script = scripts.get_nth (0) as InstallScript;
+ var cmdline = "ks=hd:sda:/" + script.get_expected_filename ();
- return str;
+ os.set_kernel (kernel_file.get_path ());
+ os.set_ramdisk (initrd_file.get_path ());
+ os.set_cmdline (cmdline);
}
public string get_user_unattended (string? suffix = null) {
@@ -307,14 +342,102 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
foreach (var child in setup_grid.get_children ())
if (child != express_toggle)
express_toggle.bind_property ("active", child, "sensitive", BindingFlags.SYNC_CREATE);
+
+ if (product_key_format == null)
+ return;
+
+ label = new Gtk.Label (_("Product Key"));
+ label.margin_top = 15;
+ label.margin_right = 10;
+ label.halign = Gtk.Align.END;
+ label.valign = Gtk.Align.CENTER;
+ setup_grid.attach (label, 0, setup_grid_n_rows, 2, 1);
+ express_toggle.bind_property ("active", label, "sensitive", 0);
+
+ key_entry = create_input_entry ("");
+ key_entry.width_chars = product_key_format.length;
+ key_entry.max_length = product_key_format.length;
+ key_entry.margin_top = 15;
+ key_entry.halign = Gtk.Align.FILL;
+ key_entry.valign = Gtk.Align.CENTER;
+ key_entry.get_style_context ().add_class ("boxes-product-key-entry");
+ setup_grid.attach (key_entry, 2, setup_grid_n_rows, 1, 1);
+ express_toggle.bind_property ("active", key_entry, "sensitive", 0);
+ setup_grid_n_rows++;
+
+ key_inserted_id = key_entry.insert_text.connect (on_key_text_inserted);
+ }
+
+ private void on_key_text_inserted (string text, int text_length, ref int position) {
+ var result = "";
+
+ for (uint i = 0, j = position; i < text_length && j < product_key_format.length; ) {
+ var character = text.get (i);
+ var allowed_char = product_key_format.get (j);
+
+ var skip_input_char = false;
+ switch (allowed_char) {
+ case '@': // Any character
+ break;
+
+ case '%': // Alphabet
+ if (!character.isalpha ())
+ skip_input_char = true;
+ break;
+
+ case '#': // Numeric
+ if (!character.isdigit ())
+ skip_input_char = true;
+ break;
+
+ case '$': // Alphnumeric
+ if (!character.isalnum ())
+ skip_input_char = true;
+ break;
+
+ default: // Hardcoded character required
+ if (character != allowed_char) {
+ result += allowed_char.to_string ();
+ j++;
+
+ continue;
+ }
+
+ break;
+ }
+
+ i++;
+ if (skip_input_char)
+ continue;
+
+ result += character.to_string ();
+ j++;
+ }
+
+ if (result != "") {
+ SignalHandler.block (key_entry, key_inserted_id);
+ key_entry.insert_text (result.up (), result.length, ref position);
+ SignalHandler.unblock (key_entry, key_inserted_id);
+ }
+
+ Signal.stop_emission_by_name (key_entry, "insert-text");
}
protected virtual void clean_up () throws GLib.Error {
if (disk_file != null) {
delete_file (disk_file);
-
disk_file = null;
}
+
+ if (kernel_file != null) {
+ delete_file (kernel_file);
+ kernel_file = null;
+ }
+
+ if (initrd_file != null) {
+ delete_file (initrd_file);
+ initrd_file = null;
+ }
}
protected virtual DomainDisk? get_unattended_disk_config () {
@@ -325,15 +448,35 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
var disk = new DomainDisk ();
disk.set_type (DomainDiskType.FILE);
- disk.set_guest_device_type (DomainDiskGuestDeviceType.DISK);
disk.set_driver_name ("qemu");
disk.set_driver_type ("raw");
disk.set_source (disk_file.get_path ());
- disk.set_target_dev ("sdb");
+
+ string device_name;
+ var device_type = get_unattended_disk_info (PathFormat.UNIX, out device_name);
+ disk.set_guest_device_type (device_type);
+ disk.set_target_dev (device_name);
return disk;
}
+ private DomainDiskGuestDeviceType get_unattended_disk_info (PathFormat path_format, out string device_name) {
+ // FIXME: Ideally, we shouldn't need to check for distro
+ if (os.distro == "win") {
+ device_name = (path_format == PathFormat.DOS)? "A" : "fd";
+
+ return DomainDiskGuestDeviceType.FLOPPY;
+ } else {
+ // Path format checks below are most probably practically redundant but a small price for future safety
+ if (supports_virtio_disk)
+ device_name = (path_format == PathFormat.UNIX)? "sda" : "E";
+ else
+ device_name = (path_format == PathFormat.UNIX)? "sdb" : "E";
+
+ return DomainDiskGuestDeviceType.DISK;
+ }
+ }
+
protected void add_unattended_file (UnattendedFile file) {
unattended_files.append (file);
}
@@ -364,20 +507,6 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
debug ("Floppy image for unattended installation created at '%s'", disk_path);
}
- private string? lookup_extra_iso (string name)
- {
- var path = get_user_pkgcache (name);
-
- if (FileUtils.test (path, FileTest.IS_REGULAR))
- return path;
-
- path = get_unattended (name);
- if (FileUtils.test (path, FileTest.IS_REGULAR))
- return path;
-
- return null;
- }
-
private async void fetch_user_avatar (Gtk.Image avatar) {
if (accounts == null)
return;
@@ -396,11 +525,61 @@ private abstract class Boxes.UnattendedInstaller: InstallerMedia {
try {
var pixbuf = new Gdk.Pixbuf.from_file_at_scale (avatar_path, 64, 64, true);
avatar.pixbuf = pixbuf;
- add_unattended_file (new UnattendedAvatarFile (this, avatar_path, avatar_format));
+
+ AvatarFormat avatar_format = null;
+ foreach (var s in scripts.get_elements ()) {
+ var script = s as InstallScript;
+ avatar_format = script.avatar_format;
+ if (avatar_format != null)
+ break;
+ }
+
+ avatar_file = new UnattendedAvatarFile (this, avatar_path, avatar_format);
+ add_unattended_file (avatar_file);
} catch (GLib.Error error) {
debug ("Failed to load user avatar file '%s': %s", avatar_path, error.message);
}
}
+
+ private async void extract_boot_files (ISOExtractor extractor, Cancellable cancellable) throws GLib.Error {
+ string src_path = extractor.get_absolute_path (os_media.kernel_path);
+ string dest_path = get_user_unattended ("kernel");
+ kernel_file = yield copy_file (src_path, dest_path, cancellable);
+
+ src_path = extractor.get_absolute_path (os_media.initrd_path);
+ dest_path = get_user_unattended ("initrd");
+ initrd_file = yield copy_file (src_path, dest_path, cancellable);
+ }
+
+ private async File copy_file (string src_path, string dest_path, Cancellable cancellable) throws GLib.Error {
+ var src_file = File.new_for_path (src_path);
+ var dest_file = File.new_for_path (dest_path);
+
+ try {
+ debug ("Copying '%s' to '%s'..", src_path, dest_path);
+ yield src_file.copy_async (dest_file, 0, Priority.DEFAULT, cancellable);
+ debug ("Copied '%s' to '%s'.", src_path, dest_path);
+ } catch (IOError.EXISTS error) {}
+
+ return dest_file;
+ }
+
+ private string? get_product_key_format () {
+ // FIXME: We don't support the case of multiple scripts requiring different kind of product keys.
+ foreach (var s in scripts.get_elements ()) {
+ var script = s as InstallScript;
+
+ var param = script.get_config_param (INSTALL_CONFIG_PROP_REG_PRODUCTKEY);
+ if (param == null || param.is_optional ())
+ continue;
+
+ var format = script.get_product_key_format ();
+ if (format != null)
+ return format;
+ }
+
+ return null;
+ }
}
private interface Boxes.UnattendedFile : GLib.Object {
@@ -445,13 +624,14 @@ private class Boxes.UnattendedTextFile : GLib.Object, Boxes.UnattendedFile {
public string dest_name { get; set; }
public string src_path { get; set; }
- protected UnattendedInstaller installer { get; set; }
+ protected UnattendedInstaller installer { get; set; }
+ protected InstallScript script { get; set; }
private File unattended_tmp;
- public UnattendedTextFile (UnattendedInstaller installer, string src_path, string dest_name) {
+ public UnattendedTextFile (UnattendedInstaller installer, InstallScript script, string dest_name) {
this.installer = installer;
- this.src_path = src_path;
+ this.script = script;
this.dest_name = dest_name;
}
@@ -464,32 +644,12 @@ private class Boxes.UnattendedTextFile : GLib.Object, Boxes.UnattendedFile {
}
protected async File get_source_file (Cancellable? cancellable) throws GLib.Error {
- var source = File.new_for_path (src_path);
- var destination_path = installer.get_user_unattended (dest_name);
- var destination = File.new_for_path (destination_path);
-
- debug ("Creating unattended file at '%s'..", destination.get_path ());
- var input_stream = yield source.read_async (Priority.DEFAULT, cancellable);
- var output_stream = yield destination.replace_async (null,
- false,
- FileCreateFlags.REPLACE_DESTINATION,
- Priority.DEFAULT,
- cancellable);
- var data_stream = new DataInputStream (input_stream);
- data_stream.newline_type = DataStreamNewlineType.ANY;
- string? str;
- while ((str = yield data_stream.read_line_async (Priority.DEFAULT, cancellable)) != null) {
- str = installer.fill_unattended_data (str);
-
- str += (installer.newline_type == DataStreamNewlineType.LF) ? "\n" : "\r\n";
-
- yield output_stream.write_async (str.data, Priority.DEFAULT, cancellable);
- }
- yield output_stream.close_async (Priority.DEFAULT, cancellable);
- debug ("Created unattended file at '%s'..", destination.get_path ());
- unattended_tmp = destination;
+ installer.configure_install_script (script);
+ var output_dir = File.new_for_path (get_user_pkgcache ());
- return destination;
+ unattended_tmp = script.generate_output (installer.os, installer.config, output_dir, cancellable);
+
+ return unattended_tmp;
}
}
@@ -501,13 +661,36 @@ private class Boxes.UnattendedAvatarFile : GLib.Object, Boxes.UnattendedFile {
private File unattended_tmp;
- private AvatarFormat dest_format;
+ private AvatarFormat? avatar_format;
+ private Gdk.PixbufFormat pixbuf_format;
- public UnattendedAvatarFile (UnattendedInstaller installer, string src_path, AvatarFormat dest_format) {
+ public UnattendedAvatarFile (UnattendedInstaller installer, string src_path, AvatarFormat? avatar_format)
+ throws Boxes.Error {
this.installer = installer;
this.src_path = src_path;
- this.dest_format = dest_format;
+ this.avatar_format = avatar_format;
+
+ foreach (var format in Gdk.Pixbuf.get_formats ()) {
+ if (avatar_format != null) {
+ foreach (var mime_type in avatar_format.mime_types) {
+ if (mime_type in format.get_mime_types ()) {
+ pixbuf_format = format;
+
+ break;
+ }
+ }
+ } else if (format.get_name () == "png")
+ pixbuf_format = format; // Fallback to PNG if supported
+
+ if (pixbuf_format != null)
+ break;
+ }
+
+ if (pixbuf_format == null)
+ throw new Boxes.Error.INVALID ("Failed to find suitable format to save user avatar file in.");
+
+ dest_name = installer.username + "." + pixbuf_format.get_extensions ()[0];
}
~UnattendedAvatarFile () {
@@ -518,18 +701,19 @@ private class Boxes.UnattendedAvatarFile : GLib.Object, Boxes.UnattendedFile {
}
}
- protected async File get_source_file (Cancellable? cancellable) throws GLib.Error {
- dest_name = installer.username + dest_format.extension;
+ protected async File get_source_file (Cancellable? cancellable) throws GLib.Error {
var destination_path = installer.get_user_unattended (dest_name);
try {
- var pixbuf = new Gdk.Pixbuf.from_file_at_scale (src_path, dest_format.width, dest_format.height, true);
+ var width = (avatar_format != null)? avatar_format.width : -1;
+ var height = (avatar_format != null)? avatar_format.height : -1;
+ var pixbuf = new Gdk.Pixbuf.from_file_at_scale (src_path, width, height, true);
- if (!dest_format.alpha && pixbuf.get_has_alpha ())
+ if (avatar_format != null && !avatar_format.alpha && pixbuf.get_has_alpha ())
pixbuf = remove_alpha (pixbuf);
debug ("Saving user avatar file at '%s'..", destination_path);
- pixbuf.save (destination_path, dest_format.type);
+ pixbuf.save (destination_path, pixbuf_format.get_name ());
debug ("Saved user avatar file at '%s'.", destination_path);
} catch (GLib.Error error) {
warning ("Failed to save user avatar: %s.", error.message);
@@ -540,23 +724,3 @@ private class Boxes.UnattendedAvatarFile : GLib.Object, Boxes.UnattendedFile {
return unattended_tmp;
}
}
-
-private class AvatarFormat {
- public string type;
- public string extension;
- public bool alpha;
- public int width;
- public int height;
-
- public AvatarFormat (string type = "png",
- string extension = "",
- bool alpha = true,
- int width = -1,
- int height = -1) {
- this.type = type;
- this.extension = extension;
- this.alpha = alpha;
- this.width = width;
- this.height = height;
- }
-}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]