This commit is contained in:
Michael Hudson-Doyle 2018-05-25 11:26:14 +12:00
parent 293793fa49
commit 15a2c1867c
6 changed files with 151 additions and 95 deletions

View File

@ -83,8 +83,10 @@ def parse_options(argv):
help=("Load snap details from examples/snaps instead of store. "
"See examples/snaps/README.md for more."))
parser.add_argument(
'--snap-section', action='store', default='developers', # will change default to server when that exists
help=("Show snaps from this section of the store in the snap list screen."))
# will change default to server when that exists
'--snap-section', action='store', default='developers',
help=("Show snaps from this section of the store in the snap "
"list screen."))
return parser.parse_args(argv)

View File

@ -134,7 +134,8 @@ class DownloadSnapTask(BackgroundTask):
self.channel = channel
def start(self):
self.controller._install_event_start(_("downloading {}").format(self.snap_name))
self.controller._install_event_start(
_("downloading {}").format(self.snap_name))
os.mkdir(self.this_snap_download_dir)
self.proc = utils.start_command(
['snap', 'download', '--channel='+self.channel, self.snap_name],
@ -144,13 +145,14 @@ class DownloadSnapTask(BackgroundTask):
stdout, stderr = self.proc.communicate()
if self.proc.returncode != 0:
raise subprocess.CalledProcessError(
self.proc.returncode, self.proc.args, output=stdout, stderr=stderr)
self.proc.returncode, self.proc.args, output=stdout,
stderr=stderr)
def end(self, observer, fut):
self.controller._install_event_finish()
try:
fut.result()
except:
except BaseException:
shutil.rmtree(self.this_snap_download_dir)
raise
else:
@ -161,10 +163,14 @@ class UpdateSnapSeed(BackgroundTask):
def __init__(self, controller, root):
self.controller = controller
self.seed_yaml = os.path.join(root, "var/lib/snapd/seed/seed.yaml")
self.tmp_dir = os.path.join(root, "var/lib/snapd/seed/tmp")
self.snap_dir = os.path.join(root, "var/lib/snapd/seed/snaps")
self.assertions_dir = os.path.join(root, "var/lib/snapd/seed/assertions")
self.seed_yaml = os.path.join(
root, "var/lib/snapd/seed/seed.yaml")
self.tmp_dir = os.path.join(
root, "var/lib/snapd/seed/tmp")
self.snap_dir = os.path.join(
root, "var/lib/snapd/seed/snaps")
self.assertions_dir = os.path.join(
root, "var/lib/snapd/seed/assertions")
def start(self):
self.controller._install_event_start(_("updating snap seed"))
@ -175,21 +181,28 @@ class UpdateSnapSeed(BackgroundTask):
with open(self.seed_yaml) as fp:
seed = yaml.safe_load(fp)
to_install = self.controller.base_model.snaplist.to_install
for snap_name in os.listdir(self.tmp_dir):
this_snap_download_dir = os.path.join(self.tmp_dir, snap_name)
[snap_path] = glob.glob(os.path.join(this_snap_download_dir, "*.snap"))
[assertion_path] = glob.glob(os.path.join(this_snap_download_dir, "*.assert"))
[snap_path] = glob.glob(
os.path.join(this_snap_download_dir, "*.snap"))
[assertion_path] = glob.glob(
os.path.join(this_snap_download_dir, "*.assert"))
snap_file = os.path.basename(snap_path)
assertion_file = os.path.basename(assertion_path)
os.rename(snap_path, os.path.join(self.snap_dir, snap_file))
os.rename(assertion_path, os.path.join(self.assertions_dir, assertion_file))
os.rename(
snap_path,
os.path.join(self.snap_dir, snap_file))
os.rename(
assertion_path,
os.path.join(self.assertions_dir, assertion_file))
# If this directory is not empty, something very
# unexpected has happened and we should fail.
os.rmdir(this_snap_download_dir)
selection = self.controller.base_model.snaplist.to_install[snap_name]
selection = to_install[snap_name]
seedinfo = {
'name': snap_name,
'file': snap_file,
@ -212,6 +225,39 @@ class UpdateSnapSeed(BackgroundTask):
observer.task_succeeded()
class SnapSeedTaskWatcher(TaskWatcher):
def __init__(self, controller):
self.controller = controller
self.tasklist = []
def task_complete(self, stage):
self.tasklist.pop(0)
def task_error(self, stage, info):
if stage.startswith("download"):
curtask = self.tasklist[0][1]
explanation = None
if isinstance(info, tuple):
log.debug("xxx %s", info)
if isinstance(info[1], subprocess.CalledProcessError):
explanation = info[1].stderr.strip()
else:
explanation = "".join(traceback.format_exception(*info))
self.controller.progress_view.ask_for_retry_snap(
self, curtask.snap_name, explanation)
return
if isinstance(info, tuple):
tb = traceback.format_exception(*info)
self.controller.curtin_error("".join(tb))
else:
self.controller.curtin_error()
def tasks_finished(self):
self.controller._install_event_finish()
self.controller.loop.set_alarm_in(
0.0, lambda loop, ud: self.controller.postinstall_complete())
class InstallProgressController(BaseController):
signals = [
('installprogress:filesystem-config-done', 'filesystem_config_done'),
@ -237,13 +283,15 @@ class InstallProgressController(BaseController):
self.curtin_start_install()
def identity_config_done(self):
if self.install_state == InstallState.DONE and self._snap_config_done:
if self.install_state == InstallState.DONE and \
self._snap_config_done:
self.postinstall_configuration()
else:
self._identity_config_done = True
def snap_config_done(self):
if self.install_state == InstallState.DONE and self._identity_config_done:
if self.install_state == InstallState.DONE and \
self._identity_config_done:
self.postinstall_configuration()
else:
self._snap_config_done = True
@ -295,23 +343,7 @@ class InstallProgressController(BaseController):
if event_type not in ['start', 'finish']:
return
if event_type == 'start':
<<<<<<< HEAD
self._install_event_start(event.get("CURTIN_MESSAGE", "??"))
||||||| merged common ancestors
message = event.get("CURTIN_MESSAGE", "??")
if not self.progress_view_showing is None:
self.footer_description.set_text(message)
self.progress_view.add_event(self._event_indent + message)
self._event_indent += " "
self.footer_spinner.start()
=======
message = event.get("CURTIN_MESSAGE", "??")
if self.progress_view_showing is not None:
self.footer_description.set_text(message)
self.progress_view.add_event(self._event_indent + message)
self._event_indent += " "
self.footer_spinner.start()
>>>>>>> master
if event_type == 'finish':
self._install_event_finish()
@ -417,54 +449,41 @@ class InstallProgressController(BaseController):
self.copy_logs_to_target()
if self.base_model.snaplist.to_install:
class watcher(TaskWatcher):
def __init__(self, controller):
self.controller = controller
self.tasklist = []
def task_complete(self, stage):
self.tasklist.pop(0)
def task_error(self, stage, info):
if stage.startswith("download"):
curtask = self.tasklist[0][1]
explanation = None
if isinstance(info, tuple):
log.debug("xxx %s", info)
if isinstance(info[1], subprocess.CalledProcessError):
explanation = info[1].stderr.strip()
else:
explanation = "".join(traceback.format_exception(*info))
self.controller.progress_view.ask_for_retry_snap(self, curtask.snap_name, explanation)
return
if isinstance(info, tuple):
tb = traceback.format_exception(*info)
self.controller.curtin_error("".join(tb))
else:
self.controller.curtin_error()
def tasks_finished(self):
self.controller._install_event_finish()
self.controller.loop.set_alarm_in(0.0, lambda loop, ud:self.controller.postinstall_complete())
w = watcher(self)
w = SnapSeedTaskWatcher(self)
if self.opts.dry_run:
root = '.subiquity'
shutil.rmtree(os.path.join(root, 'var/lib/snapd/seed'), ignore_errors=True)
os.makedirs(os.path.join(root, 'var/lib/snapd/seed/snaps'))
os.makedirs(os.path.join(root, 'var/lib/snapd/seed/assertions'))
with open(os.path.join(root, 'var/lib/snapd/seed/seed.yaml'), 'w') as fp:
fp.write("snaps:\n- name: core\n channel: stable\n file: core_XXXX.snap")
shutil.rmtree(
os.path.join(root, 'var/lib/snapd/seed'),
ignore_errors=True)
os.makedirs(
os.path.join(root, 'var/lib/snapd/seed/snaps'))
os.makedirs(
os.path.join(root, 'var/lib/snapd/seed/assertions'))
fake_seed = ("snaps:\n"
"- name: core\n"
" channel: stable\n"
" file: core_XXXX.snap")
seed_path = os.path.join(root, 'var/lib/snapd/seed/seed.yaml')
with open(seed_path, 'w') as fp:
fp.write(fake_seed)
else:
root = TARGET
tmp_dir = os.path.join(root, 'var/lib/snapd/seed/tmp')
os.mkdir(tmp_dir)
w.tasklist.append(('drain', WaitForCurtinEventsTask(self)))
for snap_name, selection in sorted(self.base_model.snaplist.to_install.items()):
w.tasklist.append(("download " + snap_name, DownloadSnapTask(self, tmp_dir, snap_name, selection.channel)))
for snap_name, selection in sorted(
self.base_model.snaplist.to_install.items()):
w.tasklist.append((
"download " + snap_name,
DownloadSnapTask(
self, tmp_dir, snap_name, selection.channel)
))
w.tasklist.append(("snapseed", UpdateSnapSeed(self, root)))
ts = TaskSequence(self.run_in_bg, w.tasklist, w)
ts.run()
else:
self.postinstall_complete()
def postinstall_complete(self):
self.ui.set_header(_("Installation complete!"))
self.progress_view.set_status(_("Finished install!"))

View File

@ -68,11 +68,12 @@ class SnapdSnapInfoLoader:
self.session = requests_unixsocket.Session()
self.pending_info_snaps = []
self.ongoing = {} # {snap:[callbacks]}
self.ongoing = {} # {snap:[callbacks]}
def start(self):
self.running = True
log.debug("loading list of snaps")
def cb(snap_list):
if not self.running:
return
@ -87,7 +88,8 @@ class SnapdSnapInfoLoader:
self.running = False
def _bg_fetch_list(self):
return self.session.get(self.url_base + 'section=' + self.store_section, timeout=60)
return self.session.get(
self.url_base + 'section=' + self.store_section, timeout=60)
def _fetched_list(self, fut):
if not self.running:
@ -139,7 +141,8 @@ class SnapdSnapInfoLoader:
self._fetch_info_for_snap(snap, self._fetch_next_info)
def _bg_fetch_next_info(self, snap):
return self.session.get(self.url_base + 'name=' + snap.name, timeout=60)
return self.session.get(
self.url_base + 'name=' + snap.name, timeout=60)
def _fetched_info(self, snap, fut):
if not self.running:
@ -184,9 +187,15 @@ class SnapListController(BaseController):
if self.opts.snaps_from_examples:
self.loader = SampleDataSnapInfoLoader(
self.model,
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "examples", "snaps"))
os.path.join(
os.path.dirname(
os.path.dirname(
os.path.dirname(__file__))),
"examples", "snaps"))
else:
self.loader = SnapdSnapInfoLoader(self.model, self.run_in_bg, self.snapd_socket_path, self.opts.snap_section)
self.loader = SnapdSnapInfoLoader(
self.model, self.run_in_bg, self.snapd_socket_path,
self.opts.snap_section)
self.loader.start()
def network_config_done(self, netplan_path):
@ -197,8 +206,9 @@ class SnapListController(BaseController):
if self.opts.dry_run:
cmds = [['sleep', '0.5']]
else:
os.makedirs('/etc/systemd/system/snapd.service.d', exist_ok=True)
with open('/etc/systemd/system/snapd.service.d/snap_proxy.conf', 'w') as fp:
dropin_dir = '/etc/systemd/system/snapd.service.d'
os.makedirs(dropin_dir, exist_ok=True)
with open(os.path.join(dropin_dir, 'snap_proxy.conf'), 'w') as fp:
fp.write(self.base_model.proxy.proxy_systemd_dropin())
cmds = [
['systemctl', 'daemon-reload'],

View File

@ -48,13 +48,14 @@ class SnapSelection:
risks = ["stable", "candidate", "beta", "edge"]
class SnapListModel:
"""The overall model for subiquity."""
def __init__(self):
self._snap_info = []
self._snaps_by_name = {}
self.to_install = {} # snap_name -> SnapSelection
self.to_install = {} # snap_name -> SnapSelection
def load_find_data(self, data):
for s in data['result']:

View File

@ -37,24 +37,31 @@ class MyLineBox(LineBox):
else:
return ""
class AskForRetryStretchy(Stretchy):
def __init__(self, parent, watcher, snap_name, explanation):
self.parent = parent
self.watcher = watcher
if explanation is None:
widgets = [
Text(_('Downloading the snap "{}" failed for an unknown reason.').format(snap_name)),
Text(_('Downloading the snap "{}" failed for an unknown '
'reason.').format(snap_name)),
]
stretchy_index = 0
else:
widgets = [
Text(_('Downloading the snap "{}" failed with the following output:').format(snap_name)),
Text(_('Downloading the snap "{}" failed with the following '
'output:').format(snap_name)),
Text(""),
Text(explanation),
]
stretchy_index = 2
retry = other_btn(label=_("Try again"), on_press=self.cont, user_arg=True)
give_up = other_btn(label=_("Give up on this snap"), on_press=self.cont, user_arg=False)
retry = other_btn(
label=_("Try again"),
on_press=self.cont, user_arg=True)
give_up = other_btn(
label=_("Give up on this snap"),
on_press=self.cont, user_arg=False)
widgets.extend([
Text(""),
Text(_("Would you like to try to download this snap again?")),
@ -68,6 +75,7 @@ class AskForRetryStretchy(Stretchy):
self.parent.remove_overlay()
self.parent.controller.resume_snap_downloads(self.watcher, retry_cur)
class ProgressView(BaseView):
def __init__(self, controller):
self.controller = controller
@ -141,7 +149,8 @@ class ProgressView(BaseView):
p.focus_position = 1
def ask_for_retry_snap(self, watcher, snap_name, explanation):
self.show_stretchy_overlay(AskForRetryStretchy(self, watcher, snap_name, explanation))
self.show_stretchy_overlay(
AskForRetryStretchy(self, watcher, snap_name, explanation))
def reboot(self, btn):
self.controller.reboot()

View File

@ -79,7 +79,7 @@ class SnapInfoView(Widget):
self.needs_focus = True
channel_width = (max(len(csi.channel_name) for csi in snap.channels)
+ StarRadioButton.reserve_columns + 1)
+ StarRadioButton.reserve_columns + 1)
max_version = max(len(csi.version) for csi in snap.channels)
max_revision = max(len(str(csi.revision)) for csi in snap.channels) + 2
max_size = max(len(humanize_size(csi.size)) for csi in snap.channels)
@ -108,13 +108,15 @@ class SnapInfoView(Widget):
('pack', Text(notes)),
], dividechars=1)))
self.lb_channels = Padding.center_79(NoTabCyclingListBox(self.channels))
self.lb_channels = Padding.center_79(
NoTabCyclingListBox(self.channels))
contents = [
('pack', Text("")),
('pack', Padding.center_79(Columns([
Text(snap.name),
('pack', Text("Publisher: {}".format(snap.publisher), align='right')),
('pack', Text(
"Publisher: {}".format(snap.publisher), align='right')),
], dividechars=1))),
('pack', Text("")),
('pack', Padding.center_79(Text(snap.summary))),
@ -123,7 +125,9 @@ class SnapInfoView(Widget):
('pack', Text("")),
('weight', 1, self.lb_channels),
('pack', Text("")),
('pack', button_pile([other_btn(label=_("Close"), on_press=self.close)])),
('pack', button_pile([
other_btn(label=_("Close"), on_press=self.close),
])),
('pack', Text("")),
]
self.description_index = contents.index(self.lb_description)
@ -149,7 +153,8 @@ class SnapInfoView(Widget):
if o == pack_option:
rows_available -= w.rows((maxcol,), focus)
rows_wanted_description = Padding.center_79(self.description).rows((maxcol,), False)
padded_description = Padding.center_79(self.description)
rows_wanted_description = padded_description.rows((maxcol,), False)
rows_wanted_channels = len(self.channels)
if rows_wanted_channels + rows_wanted_description <= rows_available:
@ -161,7 +166,8 @@ class SnapInfoView(Widget):
channel_rows = min(rows_wanted_channels, int(rows_available/3))
description_rows = rows_available - channel_rows
self.pile.contents[self.description_index] = (self.lb_description, self.pile.options('given', description_rows))
self.pile.contents[self.description_index] = (
self.lb_description, self.pile.options('given', description_rows))
if description_rows >= rows_wanted_description:
self.lb_description.original_widget._selectable = False
else:
@ -188,7 +194,9 @@ class FetchingInfo(WidgetWrap):
Pile([
('pack', Text(' ' + text)),
('pack', self.spinner),
('pack', button_pile([cancel_btn(label=_("Cancel"), on_press=self.close)])),
('pack', button_pile([
cancel_btn(label=_("Cancel"), on_press=self.close),
])),
])))
def close(self, sender=None):
@ -198,6 +206,7 @@ class FetchingInfo(WidgetWrap):
self.spinner.stop()
self.parent.remove_overlay()
class FetchingFailed(WidgetWrap):
def __init__(self, row, snap):
@ -233,7 +242,8 @@ class SnapListRow(WidgetWrap):
self.parent = parent
self.snap = snap
self.box = StarCheckBox(snap.name, on_state_change=self.state_change)
self.name_and_publisher_width = max_name_len + self.box.reserve_columns + max_publisher_len + 2
self.name_and_publisher_width = (
max_name_len + self.box.reserve_columns + max_publisher_len + 2)
self.two_column = Color.menu_button(Columns([
(max_name_len+self.box.reserve_columns, self.box),
Text(snap.summary, wrap='clip'),
@ -248,23 +258,26 @@ class SnapListRow(WidgetWrap):
def load_info(self):
called = False
fi = None
def callback():
nonlocal called
called = True
if fi is not None:
fi.close()
if len(self.snap.channels) == 0: # or other indication of failure
if len(self.snap.channels) == 0: # or other indication of failure
ff = FetchingFailed(self, self.snap)
self.parent.show_overlay(ff, width=ff.width)
else:
cur_channel = None
cur_chan = None
if self.snap.name in self.parent.to_install:
cur_channel = self.parent.to_install[self.snap.name].channel
self.parent._w = SnapInfoView(self.parent, self.snap, cur_channel)
cur_chan = self.parent.to_install[self.snap.name].channel
self.parent._w = SnapInfoView(self.parent, self.snap, cur_chan)
self.parent.controller.get_snap_info(self.snap, callback)
# If we didn't get callback synchronously, display a dialog while the info loads.
# If we didn't get callback synchronously, display a dialog
# while the info loads.
if not called:
fi = FetchingInfo(self.parent, self.snap, self.parent.controller.loop)
fi = FetchingInfo(
self.parent, self.snap, self.parent.controller.loop)
self.parent.show_overlay(fi, width=fi.width)
def keypress(self, size, key):
@ -288,6 +301,7 @@ class SnapListRow(WidgetWrap):
else:
return self.two_column.render(size, focus)
class SnapListView(BaseView):
title = _("Featured Server Snaps")
@ -295,12 +309,13 @@ class SnapListView(BaseView):
def __init__(self, model, controller):
self.model = model
self.controller = controller
self.to_install = {} # {snap_name: (channel, is_classic)}
self.to_install = {} # {snap_name: (channel, is_classic)}
self.load()
def load(self, sender=None):
spinner = None
called = False
def callback(snap_list):
nonlocal called
called = True