diff --git a/subiquity/common/types.py b/subiquity/common/types.py
new file mode 100644
index 00000000..35f6591c
--- /dev/null
+++ b/subiquity/common/types.py
@@ -0,0 +1,37 @@
+# Copyright 2020 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 .
+
+# This module defines types that will be used in the API when subiquity gets
+# split into client and server processes. View code should only use these
+# types!
+
+from typing import List
+
+import attr
+
+
+@attr.s(auto_attribs=True)
+class IdentityData:
+ realname: str = ''
+ username: str = ''
+ crypted_password: str = attr.ib(default='', repr=False)
+ hostname: str = ''
+
+
+@attr.s(auto_attribs=True)
+class SSHData:
+ install_server: bool
+ allow_pw: bool
+ authorized_keys: List[str] = attr.Factory(list)
diff --git a/subiquity/controllers/identity.py b/subiquity/controllers/identity.py
index 33a85153..c9d239a9 100644
--- a/subiquity/controllers/identity.py
+++ b/subiquity/controllers/identity.py
@@ -19,6 +19,7 @@ import attr
from subiquitycore.context import with_context
+from subiquity.common.types import IdentityData
from subiquity.controller import SubiquityTuiController
from subiquity.ui.views import IdentityView
@@ -42,7 +43,13 @@ class IdentityController(SubiquityTuiController):
def load_autoinstall_data(self, data):
if data is not None:
- self.model.add_user(data)
+ identity_data = IdentityData(
+ realname=data.get('realname', ''),
+ username=data['username'],
+ hostname=data['hostname'],
+ crypted_password=data['password'],
+ )
+ self.model.add_user(identity_data)
@with_context()
async def apply_autoinstall_config(self, context=None):
@@ -51,29 +58,32 @@ class IdentityController(SubiquityTuiController):
raise Exception("no identity data provided")
def make_ui(self):
- return IdentityView(self.model, self)
+ data = IdentityData()
+ if self.model.user is not None:
+ data.username = self.model.user.username
+ data.realname = self.model.user.realname
+ if self.model.hostname:
+ data.hostname = self.model.hostname
+ return IdentityView(self, data)
def run_answers(self):
if all(elem in self.answers for elem in
['realname', 'username', 'password', 'hostname']):
- d = {
- 'realname': self.answers['realname'],
- 'username': self.answers['username'],
- 'hostname': self.answers['hostname'],
- 'password': self.answers['password'],
- }
- self.done(d)
+ identity = IdentityData(
+ realname=self.answers['realname'],
+ username=self.answers['username'],
+ hostname=self.answers['hostname'],
+ crypted_password=self.answers['password'])
+ self.done(identity)
def cancel(self):
self.app.prev_screen()
- def done(self, user_spec):
- safe_spec = user_spec.copy()
- safe_spec['password'] = ''
+ def done(self, identity_data):
log.debug(
"IdentityController.done next_screen user_spec=%s",
- safe_spec)
- self.model.add_user(user_spec)
+ identity_data)
+ self.model.add_user(identity_data)
self.configured()
self.app.next_screen()
diff --git a/subiquity/controllers/mirror.py b/subiquity/controllers/mirror.py
index 446bbf0d..6abe4199 100644
--- a/subiquity/controllers/mirror.py
+++ b/subiquity/controllers/mirror.py
@@ -124,7 +124,7 @@ class MirrorController(SubiquityTuiController):
def make_ui(self):
self.check_state = CheckState.DONE
- return MirrorView(self.model, self)
+ return MirrorView(self, self.model.get_mirror())
def run_answers(self):
if 'mirror' in self.answers:
diff --git a/subiquity/controllers/proxy.py b/subiquity/controllers/proxy.py
index 5433f616..a61524c8 100644
--- a/subiquity/controllers/proxy.py
+++ b/subiquity/controllers/proxy.py
@@ -49,7 +49,7 @@ class ProxyController(SubiquityTuiController):
pass
def make_ui(self):
- return ProxyView(self.model, self)
+ return ProxyView(self, self.model.proxy)
def run_answers(self):
if 'proxy' in self.answers:
diff --git a/subiquity/controllers/ssh.py b/subiquity/controllers/ssh.py
index a10d97bc..476ffeb7 100644
--- a/subiquity/controllers/ssh.py
+++ b/subiquity/controllers/ssh.py
@@ -20,6 +20,7 @@ from subiquitycore.async_helpers import schedule_task
from subiquitycore.context import with_context
from subiquitycore import utils
+from subiquity.common.types import SSHData
from subiquity.controller import SubiquityTuiController
from subiquity.ui.views.ssh import SSHView
@@ -66,25 +67,25 @@ class SSHController(SubiquityTuiController):
'allow-pw', not self.model.authorized_keys)
def make_ui(self):
- return SSHView(self.model, self)
+ ssh_data = SSHData(
+ install_server=self.model.install_server,
+ allow_pw=self.model.pwauth)
+ return SSHView(self, ssh_data)
def run_answers(self):
if 'ssh-import-id' in self.answers:
import_id = self.answers['ssh-import-id']
- d = {
- "ssh_import_id": import_id.split(":", 1)[0],
- "import_username": import_id.split(":", 1)[1],
- "install_server": True,
- "pwauth": True,
- }
- self.fetch_ssh_keys(d)
+ ssh = SSHData(
+ install_server=True,
+ authorized_keys=[],
+ allow_pw=True)
+ self.fetch_ssh_keys(ssh_import_id=import_id, ssh_data=ssh)
else:
- d = {
- "install_server": self.answers.get("install_server", False),
- "authorized_keys": self.answers.get("authorized_keys", []),
- "pwauth": self.answers.get("pwauth", True),
- }
- self.done(d)
+ ssh = SSHData(
+ install_server=self.answers.get("install_server", False),
+ authorized_keys=self.answers.get("authorized_keys", []),
+ allow_pw=self.answers.get("pwauth", True))
+ self.done(ssh)
def cancel(self):
self.app.prev_screen()
@@ -103,10 +104,8 @@ class SSHController(SubiquityTuiController):
return cp
@with_context(
- name="ssh_import_id",
- description="{user_spec[ssh_import_id]}:{user_spec[import_username]}")
- async def _fetch_ssh_keys(self, *, context, user_spec):
- ssh_import_id = "{ssh_import_id}:{import_username}".format(**user_spec)
+ name="ssh_import_id", description="{ssh_import_id}")
+ async def _fetch_ssh_keys(self, *, context, ssh_import_id, ssh_data):
with self.context.child("ssh_import_id", ssh_import_id):
try:
cp = await self.run_cmd_checked(
@@ -131,22 +130,21 @@ class SSHController(SubiquityTuiController):
"").strip().splitlines()
if 'ssh-import-id' in self.app.answers.get("Identity", {}):
- user_spec['authorized_keys'] = key_material.splitlines()
- self.done(user_spec)
+ ssh_data.authorized_keys = key_material.splitlines()
+ self.done(ssh_data)
else:
self.ui.body.confirm_ssh_keys(
- user_spec, ssh_import_id, key_material, fingerprints)
+ ssh_data, ssh_import_id, key_material, fingerprints)
- def fetch_ssh_keys(self, user_spec):
+ def fetch_ssh_keys(self, ssh_import_id, ssh_data):
self._fetch_task = schedule_task(
- self._fetch_ssh_keys(user_spec=user_spec))
+ self._fetch_ssh_keys(
+ ssh_import_id=ssh_import_id, ssh_data=ssh_data))
- def done(self, result):
- log.debug("SSHController.done next_screen result=%s", result)
- self.model.install_server = result['install_server']
- self.model.authorized_keys = result.get('authorized_keys', [])
- self.model.pwauth = result.get('pwauth', True)
- self.model.ssh_import_id = result.get('ssh_import_id', None)
+ def done(self, data: SSHData):
+ self.model.install_server = data.install_server
+ self.model.authorized_keys = data.authorized_keys
+ self.model.pwauth = data.allow_pw
self.configured()
self.app.next_screen()
diff --git a/subiquity/models/identity.py b/subiquity/models/identity.py
index 02670d57..9dd4db2b 100644
--- a/subiquity/models/identity.py
+++ b/subiquity/models/identity.py
@@ -17,8 +17,6 @@ import logging
import attr
-from subiquitycore.utils import crypt_password
-
log = logging.getLogger('subiquity.models.identity')
@@ -38,12 +36,15 @@ class IdentityModel(object):
self._user = None
self._hostname = None
- def add_user(self, result):
- result = result.copy()
- self._hostname = result.pop('hostname')
- if not result.get('realname'):
- result['realname'] = result['username']
- self._user = User(**result)
+ def add_user(self, identity_data):
+ self._hostname = identity_data.hostname
+ d = {}
+ d['realname'] = identity_data.realname
+ d['username'] = identity_data.username
+ d['password'] = identity_data.crypted_password
+ if not d['realname']:
+ d['realname'] = identity_data.username
+ self._user = User(**d)
@property
def hostname(self):
@@ -53,8 +54,5 @@ class IdentityModel(object):
def user(self):
return self._user
- def encrypt_password(self, passinput):
- return crypt_password(passinput)
-
def __repr__(self):
return "".format(self.user, self.hostname)
diff --git a/subiquity/ui/views/identity.py b/subiquity/ui/views/identity.py
index f258915a..fcab46c7 100644
--- a/subiquity/ui/views/identity.py
+++ b/subiquity/ui/views/identity.py
@@ -31,8 +31,11 @@ from subiquitycore.ui.form import (
WantsToKnowFormField,
)
from subiquitycore.ui.utils import screen
+from subiquitycore.utils import crypt_password
from subiquitycore.view import BaseView
+from subiquity.common.types import IdentityData
+
log = logging.getLogger("subiquity.views.identity")
@@ -153,10 +156,8 @@ class IdentityView(BaseView):
"the system. You can configure SSH access on the next screen "
"but a password is still needed for sudo.")
- def __init__(self, model, controller):
- self.model = model
+ def __init__(self, controller, identity_data):
self.controller = controller
- self.signal = controller.signal
reserved_usernames_path = (
os.path.join(os.environ.get("SNAP", "."), "reserved-usernames"))
@@ -171,14 +172,11 @@ class IdentityView(BaseView):
else:
reserved_usernames.add('root')
- if model.user:
- initial = {
- 'realname': model.user.realname,
- 'username': model.user.username,
- 'hostname': model.hostname,
+ initial = {
+ 'realname': identity_data.realname,
+ 'username': identity_data.username,
+ 'hostname': identity_data.hostname,
}
- else:
- initial = {}
self.form = IdentityForm(reserved_usernames, initial)
@@ -193,10 +191,9 @@ class IdentityView(BaseView):
focus_buttons=False))
def done(self, result):
- result = {
- "hostname": self.form.hostname.value,
- "realname": self.form.realname.value,
- "username": self.form.username.value,
- "password": self.model.encrypt_password(self.form.password.value),
- }
- self.controller.done(result)
+ self.controller.done(IdentityData(
+ hostname=self.form.hostname.value,
+ realname=self.form.realname.value,
+ username=self.form.username.value,
+ crypted_password=crypt_password(self.form.password.value),
+ ))
diff --git a/subiquity/ui/views/mirror.py b/subiquity/ui/views/mirror.py
index 7b2ba194..ad8c7f99 100644
--- a/subiquity/ui/views/mirror.py
+++ b/subiquity/ui/views/mirror.py
@@ -46,11 +46,10 @@ class MirrorView(BaseView):
excerpt = _("If you use an alternative mirror for Ubuntu, enter its "
"details here.")
- def __init__(self, model, controller):
- self.model = model
+ def __init__(self, controller, mirror):
self.controller = controller
- self.form = MirrorForm(initial={'url': self.model.get_mirror()})
+ self.form = MirrorForm(initial={'url': mirror})
connect_signal(self.form, 'submit', self.done)
connect_signal(self.form, 'cancel', self.cancel)
diff --git a/subiquity/ui/views/proxy.py b/subiquity/ui/views/proxy.py
index 887a5e93..aa2afa42 100644
--- a/subiquity/ui/views/proxy.py
+++ b/subiquity/ui/views/proxy.py
@@ -49,11 +49,10 @@ class ProxyView(BaseView):
excerpt = _("If this system requires a proxy to connect to the internet, "
"enter its details here.")
- def __init__(self, model, controller):
- self.model = model
+ def __init__(self, controller, proxy):
self.controller = controller
- self.form = ProxyForm(initial={'url': self.model.proxy})
+ self.form = ProxyForm(initial={'url': proxy})
connect_signal(self.form, 'submit', self.done)
connect_signal(self.form, 'cancel', self.cancel)
diff --git a/subiquity/ui/views/ssh.py b/subiquity/ui/views/ssh.py
index 1f8c96ac..9a12c424 100644
--- a/subiquity/ui/views/ssh.py
+++ b/subiquity/ui/views/ssh.py
@@ -51,6 +51,7 @@ from subiquitycore.ui.utils import (
SomethingFailed,
)
+from subiquity.common.types import SSHData
from subiquity.ui.views.identity import (
UsernameField,
)
@@ -176,11 +177,9 @@ class FetchingSSHKeys(WidgetWrap):
class ConfirmSSHKeys(Stretchy):
- def __init__(self, parent, result, ssh_import_id, key_material,
- fingerprints):
+ def __init__(self, parent, ssh_data, key_material, fingerprints):
self.parent = parent
- self.result = result
- self.ssh_import_id = ssh_import_id
+ self.ssh_data = ssh_data
self.key_material = key_material
ok = ok_btn(label=_("Yes"), on_press=self.ok)
@@ -212,9 +211,8 @@ class ConfirmSSHKeys(Stretchy):
self.parent.remove_overlay()
def ok(self, sender):
- self.result['authorized_keys'] = self.key_material.splitlines()
- self.result['ssh_import_id'] = self.ssh_import_id
- self.parent.controller.done(self.result)
+ self.ssh_data.authorized_keys = self.key_material.splitlines()
+ self.parent.controller.done(self.ssh_data)
class SSHView(BaseView):
@@ -223,18 +221,13 @@ class SSHView(BaseView):
excerpt = _("You can choose to install the OpenSSH server package to "
"enable secure remote access to your server.")
- def __init__(self, model, controller):
- self.model = model
+ def __init__(self, controller, ssh_data):
self.controller = controller
initial = {
- "install_server": self.model.install_server,
- "pwauth": self.model.pwauth,
+ "install_server": ssh_data.install_server,
+ "pwauth": ssh_data.allow_pw,
}
- if self.model.ssh_import_id:
- prefix, username = self.model.ssh_import_id.split(':', 1)
- initial['ssh_import_id'] = prefix
- initial['import_username'] = username
self.form = SSHForm(initial=initial)
@@ -276,25 +269,29 @@ class SSHView(BaseView):
iu.validate()
def done(self, sender):
- result = self.form.as_data()
- log.debug("User input: {}".format(result))
+ log.debug("User input: {}".format(self.form.as_data()))
+ ssh_data = SSHData(
+ install_server=self.form.install_server.value,
+ allow_pw=self.form.pwauth.value)
# if user specifed a value, allow user to validate fingerprint
if self.form.ssh_import_id.value:
+ ssh_import_id = self.form.ssh_import_id.value + ":" + \
+ self.form.import_username.value
fsk = FetchingSSHKeys(self)
self.show_overlay(fsk, width=fsk.width, min_width=None)
- self.controller.fetch_ssh_keys(result)
+ self.controller.fetch_ssh_keys(
+ ssh_import_id=ssh_import_id, ssh_data=ssh_data)
else:
- self.controller.done(result)
+ self.controller.done(ssh_data)
def cancel(self, result=None):
self.controller.cancel()
- def confirm_ssh_keys(self, result, ssh_import_id, ssh_key, fingerprints):
+ def confirm_ssh_keys(self, ssh_data, ssh_import_id, ssh_key, fingerprints):
self.remove_overlay()
self.show_stretchy_overlay(
- ConfirmSSHKeys(
- self, result, ssh_import_id, ssh_key, fingerprints))
+ ConfirmSSHKeys(self, ssh_data, ssh_key, fingerprints))
def fetching_ssh_keys_failed(self, msg, stderr):
self.remove_overlay()
diff --git a/subiquity/ui/views/tests/test_identity.py b/subiquity/ui/views/tests/test_identity.py
index 6256e8c9..ae6c976e 100644
--- a/subiquity/ui/views/tests/test_identity.py
+++ b/subiquity/ui/views/tests/test_identity.py
@@ -4,8 +4,8 @@ from unittest import mock
from subiquitycore.signals import Signal
from subiquitycore.testing import view_helpers
-from subiquity.models.identity import IdentityModel
from subiquity.controllers.identity import IdentityController
+from subiquity.common.types import IdentityData
from subiquity.ui.views.identity import IdentityView
@@ -21,10 +21,9 @@ valid_data = {
class IdentityViewTests(unittest.TestCase):
def make_view(self):
- model = mock.create_autospec(spec=IdentityModel)
controller = mock.create_autospec(spec=IdentityController)
controller.signal = mock.create_autospec(spec=Signal)
- return IdentityView(model, controller)
+ return IdentityView(controller, IdentityData())
def test_initial_focus(self):
view = self.make_view()
@@ -46,16 +45,17 @@ class IdentityViewTests(unittest.TestCase):
def test_click_done(self):
view = self.make_view()
- view_helpers.enter_data(view.form, valid_data)
CRYPTED = ''
- view.model.encrypt_password.side_effect = lambda p: CRYPTED
- expected = valid_data.copy()
- expected['password'] = CRYPTED
- del expected['confirm_password']
-
- done_btn = view_helpers.find_button_matching(view, "^Done$")
- view_helpers.click(done_btn)
-
+ with mock.patch('subiquity.ui.views.identity.crypt_password') as cp:
+ cp.side_effect = lambda p: CRYPTED
+ view_helpers.enter_data(view.form, valid_data)
+ done_btn = view_helpers.find_button_matching(view, "^Done$")
+ view_helpers.click(done_btn)
+ expected = IdentityData(
+ realname=valid_data['realname'],
+ username=valid_data['username'],
+ hostname=valid_data['hostname'],
+ crypted_password=CRYPTED)
view.controller.done.assert_called_once_with(expected)
def test_can_tab_to_done_when_valid(self):