diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7bbdfae2..1a15bd69 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -3,6 +3,7 @@ subiquity/client/client.py
subiquity/client/controller.py
subiquity/client/controllers/__init__.py
subiquity/client/controllers/progress.py
+subiquity/client/controllers/refresh.py
subiquity/client/controllers/welcome.py
subiquity/client/__init__.py
subiquity/client/keycodes.py
@@ -35,7 +36,6 @@ subiquity/controllers/mirror.py
subiquity/controllers/network.py
subiquity/controllers/proxy.py
subiquity/controllers/reboot.py
-subiquity/controllers/refresh.py
subiquity/controllers/snaplist.py
subiquity/controllers/ssh.py
subiquity/controllers/tests/__init__.py
@@ -122,6 +122,7 @@ subiquity/server/controllers/__init__.py
subiquity/server/controllers/install.py
subiquity/server/controllers/locale.py
subiquity/server/controllers/package.py
+subiquity/server/controllers/refresh.py
subiquity/server/controllers/reporting.py
subiquity/server/controllers/userdata.py
subiquity/server/dryrun.py
diff --git a/subiquity/client/client.py b/subiquity/client/client.py
index 6f455572..8291c352 100644
--- a/subiquity/client/client.py
+++ b/subiquity/client/client.py
@@ -91,6 +91,7 @@ class SubiquityClient(TuiApplication):
controllers = [
"Welcome",
+ "Refresh",
"Progress",
]
diff --git a/subiquity/client/controllers/__init__.py b/subiquity/client/controllers/__init__.py
index 0ec38317..4aa0d493 100644
--- a/subiquity/client/controllers/__init__.py
+++ b/subiquity/client/controllers/__init__.py
@@ -14,9 +14,11 @@
# along with this program. If not, see .
from .progress import ProgressController
+from .refresh import RefreshController
from .welcome import WelcomeController
__all__ = [
'ProgressController',
+ 'RefreshController',
'WelcomeController',
]
diff --git a/subiquity/client/controllers/refresh.py b/subiquity/client/controllers/refresh.py
new file mode 100644
index 00000000..05214a9e
--- /dev/null
+++ b/subiquity/client/controllers/refresh.py
@@ -0,0 +1,90 @@
+# Copyright 2019 Canonical, Ltd.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import asyncio
+import logging
+
+import aiohttp
+
+from subiquitycore.tuicontroller import (
+ Skip,
+ )
+
+from subiquity.common.types import (
+ RefreshCheckState,
+ )
+from subiquity.client.controller import (
+ SubiquityTuiController,
+ )
+from subiquity.ui.views.refresh import RefreshView
+
+
+log = logging.getLogger('subiquity.client.controllers.refresh')
+
+
+class RefreshController(SubiquityTuiController):
+
+ endpoint_name = 'refresh'
+
+ def __init__(self, app):
+ super().__init__(app)
+ self.offered_first_time = False
+
+ async def get_progress(self, change):
+ while True:
+ try:
+ return await self.endpoint.progress.GET(change_id=change)
+ except aiohttp.ClientError:
+ # Probably the server is restarting.
+ await asyncio.sleep(1)
+
+ async def make_ui(self, index=1):
+ if self.app.updated:
+ raise Skip()
+ show = False
+ self.status = await self.endpoint.GET()
+ if index == 1:
+ if self.status.availability == RefreshCheckState.AVAILABLE:
+ show = True
+ self.offered_first_time = True
+ elif index == 2:
+ if not self.offered_first_time:
+ if self.status.availability in [RefreshCheckState.UNKNOWN,
+ RefreshCheckState.AVAILABLE]:
+ show = True
+ else:
+ raise AssertionError("unexpected index {}".format(index))
+ if show:
+ return RefreshView(self)
+ else:
+ raise Skip()
+
+ async def wait_for_check(self):
+ self.status = await self.endpoint.GET(wait=True)
+ return self.status
+
+ async def start_update(self):
+ return await self.endpoint.POST()
+
+ def run_answers(self):
+ # Handled in the view
+ pass
+
+ def done(self, sender=None):
+ log.debug("RefreshController.done next_screen")
+ self.app.next_screen()
+
+ def cancel(self, sender=None):
+ self.app.prev_screen()
diff --git a/subiquity/common/apidef.py b/subiquity/common/apidef.py
index 0724bc96..b871ca88 100644
--- a/subiquity/common/apidef.py
+++ b/subiquity/common/apidef.py
@@ -22,6 +22,7 @@ from subiquity.common.types import (
ErrorReportRef,
InstallState,
InstallStatus,
+ RefreshStatus,
)
@@ -59,6 +60,18 @@ class API:
def GET() -> None:
"""Requests to this method will fail with a HTTP 500."""
+ class refresh:
+ def GET(wait: bool = False) -> RefreshStatus:
+ """Get information about the snap refresh status.
+
+ If wait is true, block until the status is known."""
+
+ def POST() -> int:
+ """Start the update and return the change id."""
+
+ class progress:
+ def GET(change_id: int) -> dict: ...
+
class install:
class status:
def GET(cur: Optional[InstallState] = None) -> InstallStatus: ...
diff --git a/subiquity/controllers/__init__.py b/subiquity/controllers/__init__.py
index cb644f2e..0f295ff2 100644
--- a/subiquity/controllers/__init__.py
+++ b/subiquity/controllers/__init__.py
@@ -21,7 +21,6 @@ from .mirror import MirrorController
from .network import NetworkController
from .proxy import ProxyController
from .reboot import RebootController
-from .refresh import RefreshController
from .snaplist import SnapListController
from .ssh import SSHController
from .zdev import ZdevController
@@ -34,7 +33,6 @@ __all__ = [
'NetworkController',
'ProxyController',
'RebootController',
- 'RefreshController',
'RepeatedController',
'SnapListController',
'SSHController',
diff --git a/subiquity/server/controllers/__init__.py b/subiquity/server/controllers/__init__.py
index d80883f4..6da15bc7 100644
--- a/subiquity/server/controllers/__init__.py
+++ b/subiquity/server/controllers/__init__.py
@@ -18,6 +18,7 @@ from .debconf import DebconfController
from .install import InstallController
from .locale import LocaleController
from .package import PackageController
+from .refresh import RefreshController
from .reporting import ReportingController
from .userdata import UserdataController
@@ -29,6 +30,7 @@ __all__ = [
'LateController',
'LocaleController',
'PackageController',
+ 'RefreshController',
'ReportingController',
'UserdataController',
]
diff --git a/subiquity/controllers/refresh.py b/subiquity/server/controllers/refresh.py
similarity index 76%
rename from subiquity/controllers/refresh.py
rename to subiquity/server/controllers/refresh.py
index 2f379a3a..faabed92 100644
--- a/subiquity/controllers/refresh.py
+++ b/subiquity/server/controllers/refresh.py
@@ -24,24 +24,23 @@ from subiquitycore.async_helpers import (
SingleInstanceTask,
)
from subiquitycore.context import with_context
-from subiquitycore.tuicontroller import (
- Skip,
- )
+from subiquity.common.apidef import API
from subiquity.common.types import (
RefreshCheckState,
RefreshStatus,
)
-from subiquity.controller import (
- SubiquityTuiController,
+from subiquity.server.controller import (
+ SubiquityController,
)
-from subiquity.ui.views.refresh import RefreshView
-log = logging.getLogger('subiquity.controllers.refresh')
+log = logging.getLogger('subiquity.server.controllers.refresh')
-class RefreshController(SubiquityTuiController):
+class RefreshController(SubiquityController):
+
+ endpoint = API.refresh
autoinstall_key = "refresh-installer"
autoinstall_schema = {
@@ -58,23 +57,24 @@ class RefreshController(SubiquityTuiController):
]
def __init__(self, app):
- self.ai_data = {}
super().__init__(app)
+ self.ai_data = {}
self.snap_name = os.environ.get("SNAP_NAME", "subiquity")
self.configure_task = None
self.check_task = None
self.status = RefreshStatus(availability=RefreshCheckState.UNKNOWN)
- self.offered_first_time = False
- if 'update' in self.ai_data:
- self.active = self.ai_data['update']
- else:
- self.active = self.interactive()
-
def load_autoinstall_data(self, data):
if data is not None:
self.ai_data = data
+ @property
+ def active(self):
+ if 'update' in self.ai_data:
+ return True
+ else:
+ return self.interactive()
+
def start(self):
if not self.active:
return
@@ -95,16 +95,9 @@ class RefreshController(SubiquityTuiController):
return
change_id = await self.start_update(context=context)
while True:
- try:
- change = await self.get_progress(change_id)
- except requests.exceptions.RequestException as e:
- raise e
- if change['status'] == 'Done':
- # Clearly if we got here we didn't get restarted by
- # snapd/systemctl (dry-run mode or logged in via SSH)
- self.app.restart(remove_last_screen=False)
- if change['status'] not in ['Do', 'Doing']:
- raise Exception("update failed")
+ change = await self.get_progress(change_id)
+ if change['status'] not in ['Do', 'Doing', 'Done']:
+ raise Exception("update failed: %s", change['status'])
await asyncio.sleep(0.1)
@with_context()
@@ -137,8 +130,6 @@ class RefreshController(SubiquityTuiController):
def get_refresh_channel(self):
"""Return the channel we should refresh subiquity to."""
- if 'channel' in self.answers:
- return self.answers['channel']
prefix = "subiquity-channel="
for arg in self.app.kernel_cmdline:
if arg.startswith(prefix):
@@ -209,39 +200,20 @@ class RefreshController(SubiquityTuiController):
async def get_progress(self, change):
result = await self.app.snapd.get('v2/changes/{}'.format(change))
- return result['result']
+ change = result['result']
+ if change['status'] == 'Done':
+ # Clearly if we got here we didn't get restarted by
+ # snapd/systemctl (dry-run mode)
+ self.app.restart()
+ return change
- def make_ui(self, index=1):
- if self.app.updated:
- raise Skip()
- show = False
- if index == 1:
- if self.status.availability == RefreshCheckState.AVAILABLE:
- show = True
- self.offered_first_time = True
- elif index == 2:
- if not self.offered_first_time:
- if self.status.availability in [RefreshCheckState.UNKNOWN,
- RefreshCheckState.AVAILABLE]:
- show = True
- else:
- raise AssertionError("unexpected index {}".format(index))
- if show:
- return RefreshView(self)
- else:
- raise Skip()
-
- async def wait_for_check(self):
- await self.check_task.task
+ async def GET(self, wait: bool = False) -> RefreshStatus:
+ if wait:
+ await self.check_task.wait()
return self.status
- def run_answers(self):
- # Handled in the view
- pass
+ async def POST(self, context) -> int:
+ return await self.start_update(context=context)
- def done(self, sender=None):
- log.debug("RefreshController.done next_screen")
- self.app.next_screen()
-
- def cancel(self, sender=None):
- self.app.prev_screen()
+ async def progress_GET(self, change_id: int) -> dict:
+ return await self.get_progress(change_id)
diff --git a/subiquity/server/server.py b/subiquity/server/server.py
index da839479..196b7284 100644
--- a/subiquity/server/server.py
+++ b/subiquity/server/server.py
@@ -119,6 +119,7 @@ class SubiquityServer(Application):
"Package",
"Debconf",
"Locale",
+ "Refresh",
"Install",
"Late",
]
diff --git a/subiquity/ui/views/refresh.py b/subiquity/ui/views/refresh.py
index 06ac64d0..5b679001 100644
--- a/subiquity/ui/views/refresh.py
+++ b/subiquity/ui/views/refresh.py
@@ -17,7 +17,7 @@ import asyncio
import json
import logging
-import requests
+import aiohttp
from urwid import (
ProgressBar,
@@ -246,15 +246,11 @@ class RefreshView(BaseView):
async def _update(self):
try:
change_id = await self.controller.start_update()
- except requests.exceptions.RequestException as e:
+ except aiohttp.ClientError as e:
self.update_failed(exc_message(e))
return
while True:
- try:
- change = await self.controller.get_progress(change_id)
- except requests.exceptions.RequestException as e:
- self.update_failed(exc_message(e))
- return
+ change = await self.controller.get_progress(change_id)
if change['status'] == 'Done':
# Clearly if we got here we didn't get restarted by
# snapd/systemctl (dry-run mode or logged in via SSH)