Merge ~seb128/software-properties:ubuntu-pro-focal into ~robert-ancell/software-properties:ubuntu-pro-focal

Proposed by Sebastien Bacher
Status: Merged
Merged at revision: 0dd69a30987b34691c153aa9cab2d4b6bc31901c
Proposed branch: ~seb128/software-properties:ubuntu-pro-focal
Merge into: ~robert-ancell/software-properties:ubuntu-pro-focal
Diff against target: 514 lines (+280/-101)
6 files modified
data/gtkbuilder/dialog-ua-attach.ui (+151/-75)
data/gtkbuilder/main.ui (+1/-1)
debian/changelog (+6/-0)
debian/control (+1/-0)
softwareproperties/SoftwareProperties.py (+2/-1)
softwareproperties/gtk/DialogUaAttach.py (+119/-24)
Reviewer Review Type Date Requested Status
Robert Ancell Pending
Review via email: mp+434791@code.launchpad.net

Description of the change

Updated version include the work for Nathan on the magic attach workflow

Uploaded also to https://launchpad.net/~ubuntu-desktop/+archive/ubuntu/ppa

To post a comment you must log in.
Revision history for this message
Sebastien Bacher (seb128) wrote :

and it's not working because glib in that serie doesn't have https://gitlab.gnome.org/GNOME/glib/-/issues/602 so we will need to tweak...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/data/gtkbuilder/dialog-ua-attach.ui b/data/gtkbuilder/dialog-ua-attach.ui
2index 3ae3b1b..8563e86 100644
3--- a/data/gtkbuilder/dialog-ua-attach.ui
4+++ b/data/gtkbuilder/dialog-ua-attach.ui
5@@ -1,119 +1,195 @@
6-<?xml version="1.0"?>
7+<?xml version="1.0" encoding="UTF-8"?>
8 <interface>
9+ <requires lib="gtk+" version="3.16"/>
10 <object class="GtkDialog" id="dialog_ua_attach">
11- <property name="border_width">18</property>
12+ <property name="can-focus">False</property>
13+ <property name="border-width">18</property>
14 <property name="title" translatable="yes">Enable Ubuntu Pro</property>
15 <property name="resizable">False</property>
16 <property name="modal">True</property>
17- <property name="type_hint">dialog</property>
18- <property name="skip_taskbar_hint">True</property>
19+ <property name="type-hint">dialog</property>
20+ <property name="skip-taskbar-hint">True</property>
21+ <!-- interface-requires gtk+ 3.0 -->
22 <child internal-child="vbox">
23 <object class="GtkBox">
24+ <property name="visible">True</property>
25+ <property name="can-focus">False</property>
26+ <property name="no-show-all">True</property>
27 <property name="orientation">vertical</property>
28- <property name="spacing">18</property>
29+ <child>
30+ <object class="GtkLabel">
31+ <property name="visible">True</property>
32+ <property name="can-focus">True</property>
33+ <property name="label" translatable="yes">To upgrade to Ubuntu Pro, use your existing free personal, or company Ubuntu One account, or provide a token. &lt;a href="https://ubuntu.com/login"&gt;Register a new account&lt;/a&gt;.</property>
34+ <property name="use-markup">True</property>
35+ <property name="wrap">True</property>
36+ <property name="max-width-chars">130</property>
37+ </object>
38+ </child>
39+ <child>
40+ <object class="GtkRadioButton" id="magic_radio">
41+ <property name="label" translatable="yes">Log in with Ubuntu One</property>
42+ <property name="visible">True</property>
43+ <property name="can-focus">True</property>
44+ <property name="receives-default">False</property>
45+ <property name="margin-top">30</property>
46+ <property name="xalign">0</property>
47+ <property name="group">magic_radio</property>
48+ <signal name="toggled" handler="on_radio_toggled" swapped="no"/>
49+ <signal name="clicked" handler="on_magic_radio_clicked" swapped="no"/>
50+ </object>
51+ </child>
52 <child>
53 <object class="GtkBox">
54 <property name="visible">True</property>
55- <property name="orientation">vertical</property>
56- <property name="spacing">18</property>
57+ <property name="can-focus">False</property>
58+ <property name="orientation">horizontal</property>
59 <child>
60- <object class="GtkLabel">
61+ <object class="GtkBox" id="pin_label_box">
62 <property name="visible">True</property>
63- <property name="label" translatable="yes">Enter your Ubuntu Pro token here.
64-From &lt;a href="https://ubuntu.com/pro"&gt;ubuntu.com/pro&lt;/a&gt; or your system administrator</property>
65- <property name="use_markup">True</property>
66- <property name="xalign">0</property>
67+ <property name="margin-left">20</property>
68+ <property name="margin-top">12</property>
69+ <property name="margin-bottom">8</property>
70+ <property name="can-focus">False</property>
71+ <child>
72+ <object class="GtkLabel" id="pin_label">
73+ <property name="label"> </property>
74+ <property name="visible">True</property>
75+ <property name="selectable">True</property>
76+ <property name="can-focus">False</property>
77+ <property name="halign">start</property>
78+ <property name="margin-left">6</property>
79+ <property name="margin-right">6</property>
80+ <property name="margin-top">4</property>
81+ <property name="margin-bottom">4</property>
82+ <attributes>
83+ <attribute name="scale" value="2"/>
84+ </attributes>
85+ </object>
86+ </child>
87 </object>
88 </child>
89 <child>
90- <object class="GtkGrid">
91+ <object class="GtkFixed">
92 <property name="visible">True</property>
93- <property name="row_spacing">6</property>
94- <property name="column_spacing">12</property>
95+ <property name="valign">center</property>
96+ <property name="margin">3</property>
97 <child>
98- <object class="GtkLabel">
99+ <object class="GtkSpinner" id="pin_spinner">
100 <property name="visible">True</property>
101- <property name="label" translatable="yes">Token:</property>
102+ <property name="valign">center</property>
103 </object>
104- <packing>
105- <property name="left_attach">0</property>
106- <property name="top_attach">0</property>
107- </packing>
108 </child>
109 <child>
110- <object class="GtkBox">
111- <property name="visible">True</property>
112- <property name="spacing">12</property>
113- <child>
114- <object class="GtkEntry" id="entry_token">
115- <property name="visible">True</property>
116- <property name="max-width-chars">30</property>
117- <signal name="changed" handler="on_token_entry_changed"/>
118- <signal name="activate" handler="on_token_entry_activate"/>
119- </object>
120- </child>
121- <child>
122- <object class="GtkSpinner" id="spinner">
123- <property name="visible">True</property>
124- </object>
125- </child>
126+ <object class="GtkImage" id="pin_status_icon">
127+ <property name="visible">False</property>
128+ <property name="valign">center</property>
129 </object>
130- <packing>
131- <property name="left_attach">1</property>
132- <property name="top_attach">0</property>
133- </packing>
134 </child>
135+ </object>
136+ </child>
137+ <child>
138+ <object class="GtkLabel" id="pin_status">
139+ <property name="margin">3</property>
140+ <property name="visible">True</property>
141+ <property name="valign">center</property>
142+ <property name="use-markup">True</property>
143+ </object>
144+ </child>
145+ </object>
146+ </child>
147+ <child>
148+ <object class="GtkRadioButton" id="token_radio">
149+ <property name="label" translatable="yes">Or add token manually</property>
150+ <property name="visible">True</property>
151+ <property name="can-focus">True</property>
152+ <property name="receives-default">False</property>
153+ <property name="margin-top">6</property>
154+ <property name="xalign">0</property>
155+ <property name="group">magic_radio</property>
156+ <signal name="toggled" handler="on_radio_toggled" swapped="no"/>
157+ </object>
158+ </child>
159+ <child>
160+ <object class="GtkBox">
161+ <property name="visible">True</property>
162+ <property name="can-focus">False</property>
163+ <property name="no-show-all">True</property>
164+ <child>
165+ <object class="GtkEntry" id="token_field">
166+ <property name="visible">True</property>
167+ <property name="sensitive">False</property>
168+ <property name="can-focus">True</property>
169+ <property name="margin-left">20</property>
170+ <property name="margin-top">12</property>
171+ <property name="margin-bottom">12</property>
172+ <property name="margin-right">4</property>
173+ <property name="width-chars">35</property>
174+ <property name="placeholder-text" translatable="yes">Token</property>
175+ <property name="halign">start</property>
176+ <signal name="activate" handler="on_token_entry_activate" swapped="no"/>
177+ <signal name="changed" handler="on_token_typing" swapped="no"/>
178+ </object>
179+ </child>
180+ <child>
181+ <object class="GtkFixed">
182+ <property name="visible">True</property>
183+ <property name="valign">center</property>
184+ <property name="margin">3</property>
185 <child>
186- <object class="GtkLabel">
187+ <object class="GtkSpinner" id="token_spinner">
188 <property name="visible">True</property>
189- <property name="xalign">0</property>
190- <property name="label" translatable="yes">&lt;a href="https://login.ubuntu.com"&gt;Don't have a token yet? Register&lt;/a&gt;</property>
191- <property name="use_markup">True</property>
192+ <property name="valign">center</property>
193 </object>
194- <packing>
195- <property name="left_attach">1</property>
196- <property name="top_attach">1</property>
197- </packing>
198 </child>
199 <child>
200- <object class="GtkLabel" id="label_attach_error">
201- <property name="visible">True</property>
202- <property name="xalign">0</property>
203- <attributes>
204- <attribute name="foreground" value="red"/>
205- <attribute name="scale" value="0.9"/>
206- </attributes>
207+ <object class="GtkImage" id="token_status_icon">
208+ <property name="visible">False</property>
209+ <property name="valign">center</property>
210 </object>
211- <packing>
212- <property name="left_attach">1</property>
213- <property name="top_attach">2</property>
214- </packing>
215 </child>
216 </object>
217 </child>
218+ <child>
219+ <object class="GtkLabel" id="token_status">
220+ <property name="margin">3</property>
221+ <property name="visible">True</property>
222+ <property name="valign">center</property>
223+ <property name="use-markup">True</property>
224+ </object>
225+ </child>
226 </object>
227 </child>
228- </object>
229- </child>
230- <child internal-child="action_area">
231- <object class="GtkHButtonBox">
232- <property name="visible">True</property>
233- <property name="layout_style">end</property>
234 <child>
235- <object class="GtkButton">
236+ <object class="GtkLabel">
237 <property name="visible">True</property>
238- <property name="label" translatable="yes">Cance_l</property>
239- <property name="use_underline">True</property>
240- <signal name="clicked" handler="on_cancel_clicked"/>
241+ <property name="halign">start</property>
242+ <property name="margin-left">20</property>
243+ <property name="use-markup">True</property>
244+ <property name="label" translatable="yes">From your admin, or from &lt;a href="https://ubuntu.com/pro"&gt;ubuntu.com/pro&lt;/a&gt;</property>
245 </object>
246 </child>
247 <child>
248- <object class="GtkButton" id="button_attach">
249+ <object class="GtkBox">
250 <property name="visible">True</property>
251- <property name="sensitive">False</property>
252- <property name="label" translatable="yes">_Continue</property>
253- <property name="use_underline">True</property>
254- <signal name="clicked" handler="on_attach_clicked"/>
255+ <property name="orientation">horizontal</property>
256+ <property name="halign">end</property>
257+ <child>
258+ <object class="GtkButton" id="cancel">
259+ <property name="visible">True</property>
260+ <property name="margin-right">10</property>
261+ <property name="label" translatable="yes">Cancel</property>
262+ <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
263+ </object>
264+ </child>
265+ <child>
266+ <object class="GtkButton" id="confirm">
267+ <property name="visible">True</property>
268+ <property name="label" translatable="yes">Confirm</property>
269+ <property name="sensitive">False</property>
270+ <signal name="clicked" handler="on_confirm_clicked" swapped="no"/>
271+ </object>
272+ </child>
273 </object>
274 </child>
275 </object>
276diff --git a/data/gtkbuilder/main.ui b/data/gtkbuilder/main.ui
277index a78bfe2..1cff6cf 100644
278--- a/data/gtkbuilder/main.ui
279+++ b/data/gtkbuilder/main.ui
280@@ -1285,7 +1285,7 @@
281 <object class="GtkLabel">
282 <property name="visible">True</property>
283 <property name="label" translatable="yes">&lt;b&gt;This machine is not covered by an Ubuntu Pro subscription.&lt;/b&gt;
284-Receive security updates for over 25,000 Ubuntu packages, on up to 3 machines free for personal use. &lt;a href="https://ubuntu.com/pro"&gt;Learn more&lt;/a&gt;.</property>
285+Receive security updates for over 25,000 Ubuntu packages, free for up to 5 machines. &lt;a href="https://ubuntu.com/pro"&gt;Learn more&lt;/a&gt;.</property>
286 <property name="use_markup">True</property>
287 <property name="wrap">True</property>
288 <property name="max-width-chars">90</property>
289diff --git a/debian/changelog b/debian/changelog
290index 2be2114..0a94e7e 100644
291--- a/debian/changelog
292+++ b/debian/changelog
293@@ -1,3 +1,9 @@
294+software-properties (0.99.9.8+ubuntupro13.1) focal; urgency=medium
295+
296+ * Include the magic attach workflow, thanks Nathan!
297+
298+ -- Sebastien Bacher <seb128@ubuntu.com> Fri, 16 Dec 2022 11:20:29 +0100
299+
300 software-properties (0.99.9.8+ubuntupro12) focal; urgency=medium
301
302 * Show Ubuntu Pro settings.
303diff --git a/debian/control b/debian/control
304index 7edc838..65227f5 100644
305--- a/debian/control
306+++ b/debian/control
307@@ -76,6 +76,7 @@ Description: manage the repositories that you install software from (common)
308 Package: software-properties-gtk
309 Architecture: all
310 Depends: ubuntu-advantage-desktop-daemon,
311+ ubuntu-advantage-tools (>= 27.11~),
312 gir1.2-gtk-3.0,
313 libgtk3-perl,
314 python3,
315diff --git a/softwareproperties/SoftwareProperties.py b/softwareproperties/SoftwareProperties.py
316index 5f6bf64..9532bfb 100644
317--- a/softwareproperties/SoftwareProperties.py
318+++ b/softwareproperties/SoftwareProperties.py
319@@ -138,7 +138,8 @@ class SoftwareProperties(object):
320 " wait for all running threads (PPA key fetchers) to exit "
321 for t in threading.enumerate():
322 if t.ident != threading.current_thread().ident:
323- t.join()
324+ if not t.daemon:
325+ t.join()
326
327 def backup_apt_conf(self):
328 """Backup all apt configuration options"""
329diff --git a/softwareproperties/gtk/DialogUaAttach.py b/softwareproperties/gtk/DialogUaAttach.py
330index 4649545..20bb543 100644
331--- a/softwareproperties/gtk/DialogUaAttach.py
332+++ b/softwareproperties/gtk/DialogUaAttach.py
333@@ -20,11 +20,12 @@ import os
334 from gettext import gettext as _
335 import gi
336 gi.require_version("Gtk", "3.0")
337-from gi.repository import Gtk
338-
339-from softwareproperties.gtk.utils import (
340- setup_ui,
341-)
342+from gi.repository import Gtk,Gdk,GLib
343+from softwareproperties.gtk.utils import setup_ui
344+from uaclient.api.u.pro.attach.magic.initiate.v1 import initiate
345+from uaclient.api.u.pro.attach.magic.wait.v1 import MagicAttachWaitOptions, wait
346+from uaclient.exceptions import MagicAttachTokenError
347+import threading
348
349 class DialogUaAttach:
350 def __init__(self, parent, datadir, ua_object):
351@@ -35,50 +36,144 @@ class DialogUaAttach:
352 self.dialog = self.dialog_ua_attach
353 self.dialog.set_transient_for(parent)
354
355+ self.contract_token = None
356 self.attaching = False
357+ self.poll = None
358+ self.pin_label_box.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0.5, 0.5, 0.5, 0.5))
359+
360+ self.start_magic_attach()
361
362 def run(self):
363 self.dialog.run()
364 self.dialog.hide()
365
366- def update_state(self):
367- have_token = self.entry_token.get_text() != ''
368- self.button_attach.set_sensitive(have_token and not self.attaching)
369- self.entry_token.set_sensitive(not self.attaching)
370+ def update_state(self, case = None):
371+ """
372+ fail : called by the attachment callback, and it failed.
373+ success: called by the attachment callback, and it succeeded.
374+ expired: called by the token polling when the token expires.
375+ """
376+ if self.token_radio.get_active():
377+ self.pin_label.set_opacity(0)
378+ self.confirm.set_sensitive(self.token_field.get_text() != "" and
379+ not self.attaching)
380+ icon = self.token_status_icon
381+ spinner = self.token_spinner
382+ status = self.token_status
383+ else:
384+ self.pin_label.set_text(self.pin)
385+ self.pin_label.set_opacity(1)
386+ self.confirm.set_sensitive(self.contract_token != None and
387+ not self.attaching)
388+ icon = self.pin_status_icon
389+ spinner = self.pin_spinner
390+ status = self.pin_status
391+
392 if self.attaching:
393- self.spinner.start()
394+ spinner.start()
395 else:
396- self.spinner.stop()
397+ spinner.stop()
398+
399+ def lock_radio_buttons(boolean):
400+ self.token_radio.set_sensitive(not boolean)
401+ self.magic_radio.set_sensitive(not boolean)
402+
403+ lock_radio_buttons(self.attaching)
404+ self.token_field.set_sensitive(not self.attaching
405+ and self.token_radio.get_active())
406+
407+ if (case == "fail"):
408+ status.set_markup('<span foreground="red">%s</span>' % _('Invalid token'))
409+ icon.set_from_icon_name('emblem-unreadable', 1)
410+ elif (case == "success"):
411+ self.finish()
412+ elif (case == "pin_validated"):
413+ status.set_markup('<span foreground="green">%s</span>' % _('Valid token'))
414+ icon.set_from_icon_name('emblem-default', 1)
415+ lock_radio_buttons(True)
416+ elif (case == "expired"):
417+ status.set_markup(_('Code expired'))
418+ icon.set_from_icon_name('gtk-dialog-warning', 1)
419+
420+ #Only show icons/status if case is set
421+ self.token_status_icon.set_visible(False)
422+ self.token_status.set_visible(False)
423+ self.pin_status_icon.set_visible(False)
424+ self.pin_status.set_visible(False)
425+ icon.set_visible(case)
426+ status.set_visible(case)
427
428 def attach(self):
429 if self.attaching:
430 return
431
432- token = self.entry_token.get_text()
433- if token == '':
434- return
435+ if self.token_radio.get_active():
436+ token = self.token_field.get_text()
437+ else:
438+ token = self.contract_token
439
440 self.attaching = True
441- self.label_attach_error.set_text('')
442 def on_reply():
443- self.dialog.response(Gtk.ResponseType.OK)
444+ self.attaching = False
445+ self.update_state("success")
446 def on_error(error):
447- # FIXME
448- print(error)
449- self.label_attach_error.set_text(_('Failed to attach. Please try again'))
450 self.attaching = False
451- self.update_state()
452+ if self.magic_radio.get_active():
453+ self.contract_token = None
454+ self.update_state("fail")
455 self.ua_object.Attach(token, reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Manager', timeout=600)
456 self.update_state()
457
458- def on_token_entry_changed(self, entry):
459- self.update_state()
460+ def on_token_typing(self, entry):
461+ self.confirm.set_sensitive(self.token_field.get_text() != '')
462
463 def on_token_entry_activate(self, entry):
464- self.attach()
465+ token = self.token_field.get_text()
466+ if token != '':
467+ self.attach()
468
469- def on_attach_clicked(self, button):
470+ def on_confirm_clicked(self, button):
471 self.attach()
472
473 def on_cancel_clicked(self, button):
474 self.dialog.response(Gtk.ResponseType.CANCEL)
475+
476+ def poll_for_magic_token(self):
477+ options = MagicAttachWaitOptions(magic_token=self.req_id)
478+ try:
479+ response = wait(options)
480+ self.contract_token = response.contract_token
481+ GLib.idle_add(self.update_state, 'pin_validated')
482+ except MagicAttachTokenError:
483+ GLib.idle_add(self.update_state, 'expired')
484+ finally:
485+ self.poll = None
486+
487+ def start_magic_attach(self):
488+ # Already polling, don't bother the server with a new request.
489+ if self.poll != None or self.contract_token != None:
490+ return
491+
492+ self.contract_token = None
493+
494+ # Request a magic attachment and parse relevants fields from response.
495+ # userCode: The pin the user has to type in <ubuntu.com/pro/attach>;
496+ # token: Identifies the request (used for polling for it).
497+ try:
498+ response = initiate()
499+ self.pin = response.user_code
500+ self.req_id = response.token
501+ except Exception as e:
502+ print(e)
503+ return
504+ self.update_state()
505+ threading.Thread(target=self.poll_for_magic_token, daemon=True).start()
506+
507+ def on_radio_toggled(self, button):
508+ self.update_state()
509+
510+ def on_magic_radio_clicked(self, button):
511+ self.start_magic_attach()
512+
513+ def finish(self):
514+ self.dialog.response(Gtk.ResponseType.OK)

Subscribers

People subscribed via source and target branches