[gnome-boxes/wip/rishi/rhel: 14/14] wizard, wizard-source: Support gratis RHEL boxes
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes/wip/rishi/rhel: 14/14] wizard, wizard-source: Support gratis RHEL boxes
- Date: Thu, 7 Sep 2017 11:47:42 +0000 (UTC)
commit a6236e9b5de36da944c481f7b6388697e7b5e742
Author: Debarshi Ray <debarshir gnome org>
Date: Thu Aug 24 18:19:50 2017 +0200
wizard, wizard-source: Support gratis RHEL boxes
Since March 2016, it is possible to obtain an unsupported copy of Red
Hat Enterprise Linux (or RHEL) for gratis [1] that's covered by the
RHEL Developer Suite subscription. This makes it easier to set up such
VMs by teaching the wizard how to download the ISOs.
[1] https://developers.redhat.com/blog/2016/03/31/no-cost-rhel-developer-subscription-now-available/
https://bugzilla.gnome.org/show_bug.cgi?id=786679
data/ui/wizard-source.ui | 89 ++++++++++++++++
src/wizard-source.vala | 249 +++++++++++++++++++++++++++++++++++++++++++++-
src/wizard.vala | 16 +++-
3 files changed, 351 insertions(+), 3 deletions(-)
---
diff --git a/data/ui/wizard-source.ui b/data/ui/wizard-source.ui
index 0bd4e58..712d45a 100644
--- a/data/ui/wizard-source.ui
+++ b/data/ui/wizard-source.ui
@@ -117,6 +117,78 @@
</child>
<child>
+ <object class="GtkButton" id="install_rhel_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_install_rhel_button_clicked"/>
+ <style>
+ <class name="boxes-menu-row"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="install_rhel_hbox">
+ <property name="visible">True</property>
+ <property name="margin-start">15</property>
+ <property name="margin-end">15</property>
+ <property name="spacing">20</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkImage" id="install_rhel_image">
+ <property name="icon-size">0</property>
+ <property name="no-show-all">True</property>
+ <property name="pixel-size">64</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="install_rhel_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="install_rhel_label">
+ <property name="visible">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="valign">end</property>
+ <property name="use-underline">True</property>
+ <property name="label" translatable="yes">Red Hat Enterprise Linux</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="install_rhel_details_label">
+ <property name="visible">True</property>
+ <property name="ellipsize">end</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">Available with a free Red Hat developer
account</property>
+ <style>
+ <class name="boxes-step-label"/>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
<object class="GtkButton" id="enter_url_button">
<property name="visible">True</property>
<signal name="clicked" handler="on_enter_url_button_clicked"/>
@@ -225,6 +297,23 @@
</packing>
</child>
+ <!-- RHEL web view page -->
+ <child>
+ <!-- https://bugzilla.gnome.org/show_bug.cgi?id=786932 -->
+ <!-- https://bugzilla.gnome.org/show_bug.cgi?id=787033 -->
+ <!-- https://bugs.webkit.org/show_bug.cgi?id=175937 -->
+ <object class="WebKitWebView" type-func="webkit_web_view_get_type" id="rhel_web_view">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="visible">True</property>
+ <signal name="decide-policy" handler="on_rhel_web_view_decide_policy"/>
+ </object>
+
+ <packing>
+ <property name="name">rhel-web-view-page</property>
+ </packing>
+ </child>
+
<!-- URL page -->
<child>
<object class="GtkBox" id="url_menubox">
diff --git a/src/wizard-source.vala b/src/wizard-source.vala
index 37967df..5800685 100644
--- a/src/wizard-source.vala
+++ b/src/wizard-source.vala
@@ -2,6 +2,7 @@
private enum Boxes.SourcePage {
MAIN,
+ RHEL_WEB_VIEW,
URL,
LAST,
@@ -96,7 +97,7 @@ private class Boxes.WizardMediaEntry : Gtk.Button {
[GtkTemplate (ui = "/org/gnome/Boxes/ui/wizard-source.ui")]
private class Boxes.WizardSource: Gtk.Stack {
- private const string[] page_names = { "main-page", "url-page" };
+ private const string[] page_names = { "main-page", "rhel-web-view-page", "url-page" };
public Gtk.Widget? selected { get; set; }
public string uri {
@@ -123,6 +124,12 @@ private class Boxes.WizardSource: Gtk.Stack {
private Gtk.Button libvirt_sys_import_button;
[GtkChild]
private Gtk.Label libvirt_sys_import_label;
+ [GtkChild]
+ private Gtk.Button install_rhel_button;
+ [GtkChild]
+ private Gtk.Image install_rhel_image;
+ [GtkChild]
+ private WebKit.WebView rhel_web_view;
private AppWindow window;
@@ -130,6 +137,8 @@ private class Boxes.WizardSource: Gtk.Stack {
public MediaManager media_manager;
+ public string filename { get; set; }
+
public bool download_required {
get {
const string[] supported_schemes = { "http", "https" };
@@ -155,6 +164,8 @@ private class Boxes.WizardSource: Gtk.Stack {
// FIXME: grab first element in the menu list
main_vbox.grab_focus ();
break;
+ case SourcePage.RHEL_WEB_VIEW:
+ break;
case SourcePage.URL:
url_entry.changed ();
url_entry.grab_focus ();
@@ -183,6 +194,7 @@ private class Boxes.WizardSource: Gtk.Stack {
this.window = window;
var os_db = media_manager.os_db;
+
os_db.get_all_media_urls_as_store.begin ((db, result) => {
try {
var url_store = os_db.get_all_media_urls_as_store.end (result);
@@ -203,9 +215,27 @@ private class Boxes.WizardSource: Gtk.Stack {
debug ("Failed to get all known media URLs: %s", error.message);
}
});
+
+ var rhel_id = "http://redhat.com/rhel/7.4";
+ os_db.get_os_by_id.begin (rhel_id, (obj, res) => {
+ Osinfo.Os? os = null;
+ try {
+ os = os_db.get_os_by_id.end (res);
+ } catch (OSDatabaseError error) {
+ warning ("Failed to find OS with ID '%s': %s", rhel_id, error.message);
+ return;
+ }
+
+ Downloader.fetch_os_logo.begin (install_rhel_image, os, 64, (obj, res) => {
+ Downloader.fetch_os_logo.end (res);
+ var pixbuf = install_rhel_image.pixbuf;
+ install_rhel_image.visible = pixbuf != null;
+ });
+ });
}
public void cleanup () {
+ filename = null;
install_media = null;
libvirt_sys_import = false;
selected = null;
@@ -315,4 +345,221 @@ private class Boxes.WizardSource: Gtk.Stack {
warning ("Failed to setup installation media '%s': %s", media.device_file, error.message);
}
}
+
+ private string rhel_get_authentication_uri_from_json (string contents) throws GLib.Error
+ requires (contents.length > 0) {
+
+ var parser = new Json.Parser ();
+ parser.load_from_data (contents, -1);
+
+ Json.NodeType node_type = Json.NodeType.NULL;
+
+ var root_node = parser.get_root ();
+ node_type = root_node.get_node_type ();
+ if (node_type != Json.NodeType.ARRAY)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: couldn’t find root array");
+
+ var root_array = root_node.get_array ();
+ if (root_array == null)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: couldn’t find root array");
+ if (root_array.get_length () == 0)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: root array is empty");
+
+ var root_array_node_0 = root_array.get_element (0);
+ node_type = root_array_node_0.get_node_type ();
+ if (node_type != Json.NodeType.OBJECT)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: root array doesn’t have an object");
+
+ var root_array_object_0 = root_array_node_0.get_object ();
+
+ var product_code_node = root_array_object_0.get_member ("productCode");
+ if (product_code_node == null)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: couldn’t find productCode");
+ node_type = product_code_node.get_node_type ();
+ if (node_type != Json.NodeType.VALUE)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: productCode is not a VALUE");
+
+ var product_code = product_code_node.get_string ();
+ if (product_code != "rhel")
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: productCode is not rhel");
+
+ var featured_artifact_node = root_array_object_0.get_member ("featuredArtifact");
+ if (featured_artifact_node == null)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: couldn’t find featuredArtifact");
+ node_type = featured_artifact_node.get_node_type ();
+ if (node_type != Json.NodeType.OBJECT)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: featuredArtifact is not an OBJECT");
+
+ var featured_artifact_object = featured_artifact_node.get_object ();
+
+ var url_node = featured_artifact_object.get_member ("url");
+ if (url_node == null)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: couldn’t find featuredArtifact.url");
+ node_type = url_node.get_node_type ();
+ if (node_type != Json.NodeType.VALUE)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: featuredArtifact.url is not a VALUE");
+
+ var url = url_node.get_string ();
+ if (url == null || url.length == 0)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: featuredArtifact.url is empty");
+
+ return url;
+ }
+
+ private string rhel_get_authentication_uri_from_xml (string contents) throws GLib.Error
+ requires (contents.length > 0) {
+
+ var product_code = extract_xpath (contents, "string(/products/product/productCode)", true);
+ if (product_code != "rhel")
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: productCode is not rhel");
+
+ var url = extract_xpath (contents, "string(/products/product/featuredArtifact/url)", true);
+ if (url.length == 0)
+ throw new Boxes.Error.INVALID ("Failed to parse JSON: featuredArtifact.url is empty");
+
+ return url;
+ }
+
+ private void rhel_show_web_view (string cached_path, bool use_cache) {
+ if (!use_cache)
+ GLib.FileUtils.unlink (cached_path);
+
+ var downloader = Downloader.get_instance ();
+ var file = GLib.File.new_for_uri
("https://developers.redhat.com/download-manager/rest/available/rhel");
+ string[] cached_paths = { cached_path };
+ var progress = new ActivityProgress ();
+ downloader.download.begin (file, cached_paths, progress, null, (obj, res) => {
+ try {
+ file = downloader.download.end (res);
+ } catch (GLib.Error error) {
+ window.notificationbar.display_error (_("Failed to get authentication URI"));
+ warning (error.message);
+ return;
+ }
+
+ file.load_contents_async.begin (null, (obj, res) => {
+ uint8[] contents;
+ try {
+ file.load_contents_async.end (res, out contents, null);
+ } catch (GLib.Error error) {
+ window.notificationbar.display_error (_("Failed to parse response from redhat.com"));
+ warning (error.message);
+ return;
+ }
+
+ if (contents.length <= 0) {
+ window.notificationbar.display_error (_("Failed to parse response from redhat.com"));
+ warning ("Empty response from redhat.com");
+ return;
+ }
+
+ string? authentication_uri;
+ try {
+ authentication_uri = rhel_get_authentication_uri_from_json ((string) contents);
+ } catch (Boxes.Error error) {
+ window.notificationbar.display_error (_("Failed to parse response from redhat.com"));
+ warning (error.message);
+ return;
+ } catch (GLib.Error json_error) {
+ debug ("Failed to parse as JSON, could be XML: %s", json_error.message);
+ try {
+ authentication_uri = rhel_get_authentication_uri_from_xml ((string) contents);
+ } catch (GLib.Error xml_error) {
+ window.notificationbar.display_error (_("Failed to parse response from redhat.com"));
+ warning (xml_error.message);
+ return;
+ }
+ }
+
+ debug ("RHEL ISO authentication URI: %s", authentication_uri);
+
+ rhel_web_view.load_uri (authentication_uri);
+ filename = GLib.Path.get_basename (authentication_uri);
+ page = SourcePage.RHEL_WEB_VIEW;
+ });
+ });
+ }
+
+ [GtkCallback]
+ private void on_install_rhel_button_clicked () {
+ var cached_path = get_cache ("developers.redhat.com", "rhel");
+ var cached_file = GLib.File.new_for_path (cached_path);
+ cached_file.query_info_async.begin (GLib.FileAttribute.TIME_MODIFIED,
+ GLib.FileQueryInfoFlags.NONE,
+ GLib.Priority.DEFAULT,
+ null,
+ (obj, res) => {
+ GLib.FileInfo? info;
+ try {
+ info = cached_file.query_info_async.end (res);
+ } catch (GLib.IOError.NOT_FOUND error) {
+ debug ("No cached response from redhat.com");
+ rhel_show_web_view (cached_path, false);
+ return;
+ } catch (GLib.Error error) {
+ warning ("Failed to find cached response from redhat.com: %s", error.message);
+ rhel_show_web_view (cached_path, false);
+ return;
+ }
+
+ var mtime_timeval = info.get_modification_time ();
+ GLib.DateTime? mtime = new GLib.DateTime.from_timeval_utc (mtime_timeval);
+ if (mtime == null) {
+ warning ("Cached response from redhat.com has invalid modification time");
+ rhel_show_web_view (cached_path, false);
+ return;
+ }
+
+ GLib.DateTime? now = new GLib.DateTime.now_utc ();
+ if (now == null) {
+ warning ("Failed to read current time");
+ rhel_show_web_view (cached_path, false);
+ return;
+ }
+
+ var time_difference = now.difference (mtime);
+ if (time_difference > GLib.TimeSpan.DAY) {
+ debug ("Cached response from redhat.com is more than a day old");
+ rhel_show_web_view (cached_path, false);
+ return;
+ }
+
+ debug ("Cached response from redhat.com is less than a day old");
+ rhel_show_web_view (cached_path, true);
+ });
+ }
+
+ [GtkCallback]
+ private bool on_rhel_web_view_decide_policy (WebKit.WebView web_view,
+ WebKit.PolicyDecision decision,
+ WebKit.PolicyDecisionType decision_type) {
+ if (decision_type != WebKit.PolicyDecisionType.NAVIGATION_ACTION)
+ return false;
+
+ var action = (decision as WebKit.NavigationPolicyDecision).get_navigation_action ();
+ var request = action.get_request ();
+ var request_uri = request.get_uri ();
+ if (!request_uri.has_prefix ("https://developers.redhat.com/products/rhel"))
+ return false;
+
+ var soup_uri = new Soup.URI (request_uri);
+ var query = soup_uri.get_query ();
+ if (query == null)
+ return false;
+
+ var key_value_pairs = Soup.Form.decode (query);
+ var download_uri = key_value_pairs.lookup ("tcDownloadURL");
+ if (download_uri == null)
+ return false;
+
+ debug ("RHEL ISO download URI: %s", download_uri);
+
+ uri = download_uri;
+ activated ();
+
+ selected = install_rhel_button;
+
+ decision.ignore ();
+ return true;
+ }
}
diff --git a/src/wizard.vala b/src/wizard.vala
index 38b4e4f..dbc2505 100644
--- a/src/wizard.vala
+++ b/src/wizard.vala
@@ -83,6 +83,8 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
break;
case WizardPage.PREPARATION:
+ //next_button.sensitive = true;
+ //next_button.visible = true;
if (!prepare (create_preparation_progress ()))
return;
break;
@@ -119,6 +121,11 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
}
} else {
switch (page) {
+ case WizardPage.PREPARATION:
+ if (wizard_source.page == SourcePage.RHEL_WEB_VIEW)
+ wizard_source.page = SourcePage.MAIN;
+ break;
+
case WizardPage.REVIEW:
create_button.visible = false;
continue_button.visible = true;
@@ -157,6 +164,7 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
if (page != WizardPage.SOURCE)
return;
+ //next_button.visible = true;
next_button.sensitive = false;
switch (wizard_source.page) {
@@ -165,6 +173,10 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
source = null;
break;
+ case Boxes.SourcePage.RHEL_WEB_VIEW:
+ //next_button.visible = false;
+ break;
+
case Boxes.SourcePage.URL:
next_button.sensitive = false;
if (wizard_source.uri.length == 0)
@@ -397,7 +409,7 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
try {
// Validate URI
- prepare_for_location (wizard_source.uri, null, true);
+ prepare_for_location (wizard_source.uri, wizard_source.filename, true);
} catch (GLib.Error error) {
window.notificationbar.display_error (error.message);
@@ -408,7 +420,7 @@ private class Boxes.Wizard: Gtk.Stack, Boxes.UI {
if (wizard_source.download_required) {
continue_button.sensitive = false;
- download_media.begin (wizard_source.uri, null, progress);
+ download_media.begin (wizard_source.uri, wizard_source.filename, progress);
return true;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]