diff --git a/examples/answers-bond.yaml b/examples/answers-bond.yaml
index 7d613ff5..f38de8cd 100644
--- a/examples/answers-bond.yaml
+++ b/examples/answers-bond.yaml
@@ -45,6 +45,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: false
SnapList:
diff --git a/examples/answers-guided-lvm.yaml b/examples/answers-guided-lvm.yaml
index 8bbd1c3c..35f500c4 100644
--- a/examples/answers-guided-lvm.yaml
+++ b/examples/answers-guided-lvm.yaml
@@ -20,6 +20,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: false
SnapList:
diff --git a/examples/answers-imsm.yaml b/examples/answers-imsm.yaml
index 96f64b1d..0ec69af3 100644
--- a/examples/answers-imsm.yaml
+++ b/examples/answers-imsm.yaml
@@ -20,6 +20,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: true
pwauth: false
diff --git a/examples/answers-lvm-dmcrypt.yaml b/examples/answers-lvm-dmcrypt.yaml
index 05ab12dd..251fd298 100644
--- a/examples/answers-lvm-dmcrypt.yaml
+++ b/examples/answers-lvm-dmcrypt.yaml
@@ -64,6 +64,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: true
pwauth: false
diff --git a/examples/answers-lvm.yaml b/examples/answers-lvm.yaml
index e3a1155a..d81c8f5d 100644
--- a/examples/answers-lvm.yaml
+++ b/examples/answers-lvm.yaml
@@ -45,6 +45,8 @@ Filesystem:
fstype: ext4
mount: /
- action: done
+UbuntuAdvantage:
+ token: "a1b2c3d4"
Identity:
realname: Ubuntu
username: ubuntu
diff --git a/examples/answers-preserve.yaml b/examples/answers-preserve.yaml
index 1c0eacb0..142adf68 100644
--- a/examples/answers-preserve.yaml
+++ b/examples/answers-preserve.yaml
@@ -25,6 +25,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: true
pwauth: false
diff --git a/examples/answers-raid-lvm.yaml b/examples/answers-raid-lvm.yaml
index 437e0548..b3d71e07 100644
--- a/examples/answers-raid-lvm.yaml
+++ b/examples/answers-raid-lvm.yaml
@@ -96,6 +96,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: false
SnapList:
diff --git a/examples/answers-raid.yaml b/examples/answers-raid.yaml
index 87ed33bb..16434788 100644
--- a/examples/answers-raid.yaml
+++ b/examples/answers-raid.yaml
@@ -55,6 +55,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: false
SnapList:
diff --git a/examples/answers-serial.yaml b/examples/answers-serial.yaml
index 788f3eca..0ccfa3af 100644
--- a/examples/answers-serial.yaml
+++ b/examples/answers-serial.yaml
@@ -24,6 +24,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: true
pwauth: false
diff --git a/examples/answers-swap.yaml b/examples/answers-swap.yaml
index 6eef12c1..095b8037 100644
--- a/examples/answers-swap.yaml
+++ b/examples/answers-swap.yaml
@@ -28,6 +28,8 @@ Identity:
hostname: ubuntu-server
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SSH:
install_server: false
SnapList:
diff --git a/examples/answers.yaml b/examples/answers.yaml
index fe4d736c..96c17d12 100644
--- a/examples/answers.yaml
+++ b/examples/answers.yaml
@@ -22,6 +22,8 @@ Identity:
# ubuntu
password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1'
ssh-import-id: gh:mwhudson
+UbuntuAdvantage:
+ token: "a1b2c3d4"
SnapList:
snaps:
hello:
diff --git a/examples/lsb-release-focal b/examples/lsb-release-focal
new file mode 100644
index 00000000..5de4df74
--- /dev/null
+++ b/examples/lsb-release-focal
@@ -0,0 +1,4 @@
+DISTRIB_ID=Ubuntu
+DISTRIB_RELEASE=20.04
+DISTRIB_CODENAME=focal
+DISTRIB_DESCRIPTION="Ubuntu 20.04 LTS"
diff --git a/examples/lsb-release-impish b/examples/lsb-release-impish
new file mode 100644
index 00000000..d488d79f
--- /dev/null
+++ b/examples/lsb-release-impish
@@ -0,0 +1,4 @@
+DISTRIB_ID=Ubuntu
+DISTRIB_RELEASE=21.10
+DISTRIB_CODENAME=impish
+DISTRIB_DESCRIPTION="Ubuntu 21.10"
diff --git a/subiquity/client/client.py b/subiquity/client/client.py
index d402dce5..c6e59766 100644
--- a/subiquity/client/client.py
+++ b/subiquity/client/client.py
@@ -110,6 +110,7 @@ class SubiquityClient(TuiApplication):
"Refresh",
"Filesystem",
"Identity",
+ "UbuntuAdvantage",
"SSH",
"SnapList",
"Progress",
diff --git a/subiquity/client/controllers/__init__.py b/subiquity/client/controllers/__init__.py
index 11abc3b2..4b1ed23a 100644
--- a/subiquity/client/controllers/__init__.py
+++ b/subiquity/client/controllers/__init__.py
@@ -26,6 +26,7 @@ from .serial import SerialController
from .snaplist import SnapListController
from .source import SourceController
from .ssh import SSHController
+from .ubuntu_advantage import UbuntuAdvantageController
from .welcome import WelcomeController
from .zdev import ZdevController
@@ -44,6 +45,7 @@ __all__ = [
'SnapListController',
'SourceController',
'SSHController',
+ 'UbuntuAdvantageController',
'WelcomeController',
'ZdevController',
]
diff --git a/subiquity/client/controllers/ubuntu_advantage.py b/subiquity/client/controllers/ubuntu_advantage.py
new file mode 100644
index 00000000..2e992ffd
--- /dev/null
+++ b/subiquity/client/controllers/ubuntu_advantage.py
@@ -0,0 +1,65 @@
+# Copyright 2021 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 .
+""" Module that defines the client-side controller class for Ubuntu Advantage.
+"""
+
+import logging
+from typing import Optional
+
+from subiquity.client.controller import SubiquityTuiController
+from subiquity.common.types import UbuntuAdvantageInfo
+from subiquity.ui.views.ubuntu_advantage import UbuntuAdvantageView
+
+from subiquitycore.lsb_release import lsb_release
+from subiquitycore.tuicontroller import Skip
+
+log = logging.getLogger("subiquity.client.controllers.ubuntu_advantage")
+
+
+class UbuntuAdvantageController(SubiquityTuiController):
+ """ Client-side controller for Ubuntu Advantage configuration. """
+
+ endpoint_name = "ubuntu_advantage"
+
+ async def make_ui(self) -> UbuntuAdvantageView:
+ """ Generate the UI, based on the data provided by the model. """
+
+ # TODO remove these two lines to enable this screen
+ await self.endpoint.skip.POST()
+ raise Skip("Hiding the screen until we can validate the token.")
+
+ path_lsb_release: Optional[str] = None
+ if self.app.opts.dry_run:
+ # In dry-run mode, always pretend to be on LTS
+ path_lsb_release = "examples/lsb-release-focal"
+ if "LTS" not in lsb_release(path_lsb_release)["description"]:
+ await self.endpoint.skip.POST()
+ raise Skip("Not running LTS version")
+
+ ubuntu_advantage_info = await self.endpoint.GET()
+ return UbuntuAdvantageView(self, ubuntu_advantage_info.token)
+
+ def run_answers(self) -> None:
+ if "token" in self.answers:
+ self.done(self.answers["token"])
+
+ def cancel(self) -> None:
+ self.app.prev_screen()
+
+ def done(self, token: str) -> None:
+ log.debug("UbuntuAdvantageController.done token=%s", token)
+ self.app.next_screen(
+ self.endpoint.POST(UbuntuAdvantageInfo(token=token))
+ )
diff --git a/subiquity/common/apidef.py b/subiquity/common/apidef.py
index 845248dd..ef13ab41 100644
--- a/subiquity/common/apidef.py
+++ b/subiquity/common/apidef.py
@@ -48,6 +48,7 @@ from subiquity.common.types import (
StorageResponse,
StorageResponseV2,
TimeZoneInfo,
+ UbuntuAdvantageInfo,
WLANSupportInstallState,
ZdevInfo,
WSLConfigurationBase,
@@ -312,6 +313,13 @@ class API:
def GET() -> List[str]: ...
def POST(data: Payload[List[str]]): ...
+ class ubuntu_advantage:
+ def GET() -> UbuntuAdvantageInfo: ...
+ def POST(data: Payload[UbuntuAdvantageInfo]) -> None: ...
+
+ class skip:
+ def POST() -> None: ...
+
class LinkAction(enum.Enum):
NEW = enum.auto()
diff --git a/subiquity/common/types.py b/subiquity/common/types.py
index 08e4bd56..620be578 100644
--- a/subiquity/common/types.py
+++ b/subiquity/common/types.py
@@ -389,6 +389,11 @@ class TimeZoneInfo:
from_geoip: bool
+@attr.s(auto_attribs=True)
+class UbuntuAdvantageInfo:
+ token: str
+
+
class ShutdownMode(enum.Enum):
REBOOT = enum.auto()
POWEROFF = enum.auto()
diff --git a/subiquity/models/subiquity.py b/subiquity/models/subiquity.py
index d41d6088..0f056f11 100644
--- a/subiquity/models/subiquity.py
+++ b/subiquity/models/subiquity.py
@@ -41,6 +41,7 @@ from .snaplist import SnapListModel
from .source import SourceModel
from .ssh import SSHModel
from .timezone import TimeZoneModel
+from .ubuntu_advantage import UbuntuAdvantageModel
from .updates import UpdatesModel
@@ -143,6 +144,7 @@ class SubiquityModel:
self.ssh = SSHModel()
self.source = SourceModel()
self.timezone = TimeZoneModel()
+ self.ubuntu_advantage = UbuntuAdvantageModel()
self.updates = UpdatesModel()
self.userdata = {}
diff --git a/subiquity/models/tests/test_ubuntu_advantage.py b/subiquity/models/tests/test_ubuntu_advantage.py
new file mode 100644
index 00000000..c718164c
--- /dev/null
+++ b/subiquity/models/tests/test_ubuntu_advantage.py
@@ -0,0 +1,38 @@
+# Copyright 2021 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 unittest
+
+from subiquity.models.ubuntu_advantage import UbuntuAdvantageModel
+
+
+class TestUbuntuAdvantageModel(unittest.TestCase):
+ def test_make_cloudconfig_(self):
+ model = UbuntuAdvantageModel()
+
+ # Test with a token
+ model.token = "0a1b2c3d4e5f6"
+ expected = {
+ "ubuntu_advantage": {
+ "token": "0a1b2c3d4e5f6",
+ }
+ }
+ self.assertEqual(model.make_cloudconfig(), expected)
+
+ # Test without token
+ model.token = ""
+ self.assertEqual(model.make_cloudconfig(), {})
+ model.token = None
+ self.assertEqual(model.make_cloudconfig(), {})
diff --git a/subiquity/models/ubuntu_advantage.py b/subiquity/models/ubuntu_advantage.py
new file mode 100644
index 00000000..40ce7a6a
--- /dev/null
+++ b/subiquity/models/ubuntu_advantage.py
@@ -0,0 +1,47 @@
+# Copyright 2021 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 .
+""" Module that defines the model for Ubuntu Advantage configuration. """
+
+import logging
+
+log = logging.getLogger("subiquitycore.models.ubuntu_advantage")
+
+
+class UbuntuAdvantageModel:
+ """
+ Model that represents the Ubuntu Advantage configuration.
+ Currently, we rely only on cloud-init so we have no means to validate that
+ the provided token is correct ; nor to retrieve information about the
+ subscription.
+ """
+ def __init__(self):
+ """ Initialize the model. """
+ self.token: str = ""
+
+ def make_cloudconfig(self) -> dict:
+ """
+ Return a dictionary that will be included in cloud-init config.
+ Having the token set to the empty-string disables the configuration.
+ """
+ if not self.token:
+ return {}
+ # Both "ubuntu_advantage" and "ubuntu-advantage" keys are accepted, but
+ # "ubuntu-advantage" is deprecated despite not being mentioned in the
+ # documentation.
+ return {
+ "ubuntu_advantage": {
+ "token": self.token,
+ },
+ }
diff --git a/subiquity/server/controllers/__init__.py b/subiquity/server/controllers/__init__.py
index d875238b..3e0e65d3 100644
--- a/subiquity/server/controllers/__init__.py
+++ b/subiquity/server/controllers/__init__.py
@@ -32,6 +32,7 @@ from .snaplist import SnapListController
from .source import SourceController
from .ssh import SSHController
from .timezone import TimeZoneController
+from .ubuntu_advantage import UbuntuAdvantageController
from .updates import UpdatesController
from .userdata import UserdataController
from .zdev import ZdevController
@@ -58,6 +59,7 @@ __all__ = [
'SourceController',
'SSHController',
'TimeZoneController',
+ 'UbuntuAdvantageController',
'UpdatesController',
'UserdataController',
'ZdevController',
diff --git a/subiquity/server/controllers/tests/test_ubuntu_advantage.py b/subiquity/server/controllers/tests/test_ubuntu_advantage.py
new file mode 100644
index 00000000..19564144
--- /dev/null
+++ b/subiquity/server/controllers/tests/test_ubuntu_advantage.py
@@ -0,0 +1,34 @@
+# Copyright 2021 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 unittest
+
+from subiquity.server.controllers.ubuntu_advantage import (
+ UbuntuAdvantageController,
+)
+from subiquitycore.tests.mocks import make_app
+
+
+class TestUbuntuAdvantageController(unittest.TestCase):
+ def setUp(self):
+ self.controller = UbuntuAdvantageController(make_app())
+
+ def test_serialize(self):
+ self.controller.model.token = "1a2b3C"
+ self.assertEqual(self.controller.serialize(), "1a2b3C")
+
+ def test_deserialize(self):
+ self.controller.deserialize("1A2B3C4D")
+ self.assertEqual(self.controller.model.token, "1A2B3C4D")
diff --git a/subiquity/server/controllers/ubuntu_advantage.py b/subiquity/server/controllers/ubuntu_advantage.py
new file mode 100644
index 00000000..025c10aa
--- /dev/null
+++ b/subiquity/server/controllers/ubuntu_advantage.py
@@ -0,0 +1,58 @@
+# Copyright 2021 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 .
+""" Module defining the server-side controller class for Ubuntu Advantage. """
+
+import logging
+
+from subiquity.common.apidef import API
+from subiquity.common.types import UbuntuAdvantageInfo
+from subiquity.server.controller import SubiquityController
+
+log = logging.getLogger("subiquity.server.controllers.ubuntu_advantage")
+
+
+class UbuntuAdvantageController(SubiquityController):
+ """ Represent the server-side Ubuntu Advantage controller. """
+
+ endpoint = API.ubuntu_advantage
+
+ model_name = "ubuntu_advantage"
+
+ def serialize(self) -> str:
+ """ Save the current state of the model so it can be loaded later.
+ Currently this function is called automatically by .configured().
+ """
+ return self.model.token
+
+ def deserialize(self, token: str) -> None:
+ """ Loads the last-known state of the model. """
+ self.model.token = token
+
+ async def GET(self) -> UbuntuAdvantageInfo:
+ """ Handle a GET request coming from the client-side controller. """
+ return UbuntuAdvantageInfo(token=self.model.token)
+
+ async def POST(self, data: UbuntuAdvantageInfo) -> None:
+ """ Handle a POST request coming from the client-side controller and
+ then call .configured().
+ """
+ log.debug("Received POST: %s", data)
+ self.model.token = data.token
+ await self.configured()
+
+ async def skip_POST(self) -> None:
+ """ When running on a non-LTS release, we want to call this so we can
+ skip the screen on the client side. """
+ await self.configured()
diff --git a/subiquity/server/server.py b/subiquity/server/server.py
index aaf59eaf..1ca50a4d 100644
--- a/subiquity/server/server.py
+++ b/subiquity/server/server.py
@@ -204,6 +204,7 @@ POSTINSTALL_MODEL_NAMES = ModelNames({
"packages",
"snaplist",
"ssh",
+ "ubuntu_advantage",
"userdata",
},
desktop={"timezone"})
@@ -242,6 +243,7 @@ class SubiquityServer(Application):
"Zdev",
"Source",
"Network",
+ "UbuntuAdvantage",
"Proxy",
"Mirror",
"Filesystem",
diff --git a/subiquity/tests/api/test_api.py b/subiquity/tests/api/test_api.py
index e4b7144e..d6fc7f98 100755
--- a/subiquity/tests/api/test_api.py
+++ b/subiquity/tests/api/test_api.py
@@ -215,6 +215,10 @@ class TestFlow(TestAPI):
}
await inst.post('/ssh', ssh)
await inst.post('/snaplist', [])
+ ua_params = {
+ "token": "a1b2c3d4e6f7g8h9I0K1",
+ }
+ await inst.post('/ubuntu_advantage', ua_params)
for state in 'RUNNING', 'POST_WAIT', 'POST_RUNNING', 'UU_RUNNING':
await inst.get('/meta/status', cur=state)
diff --git a/subiquity/ui/views/ubuntu_advantage.py b/subiquity/ui/views/ubuntu_advantage.py
new file mode 100644
index 00000000..73cf17f7
--- /dev/null
+++ b/subiquity/ui/views/ubuntu_advantage.py
@@ -0,0 +1,99 @@
+# Copyright 2021 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 .
+""" Module that defines the view class for Ubuntu Advantage configuration. """
+
+import logging
+import re
+
+from urwid import connect_signal
+
+from subiquitycore.view import BaseView
+from subiquitycore.ui.form import (
+ Form,
+ simple_field,
+ WantsToKnowFormField,
+)
+
+from subiquitycore.ui.interactive import StringEditor
+
+
+log = logging.getLogger('subiquity.ui.views.ubuntu_advantage')
+
+ua_help = _("If you want to enroll this system using your Ubuntu Advantage "
+ "subscription, enter your Ubuntu Advantage token here. "
+ "Otherwise, leave this blank.")
+
+
+class UATokenEditor(StringEditor, WantsToKnowFormField):
+ """ Represent a text-box editor for the Ubuntu Advantage Token. """
+ def __init__(self):
+ """ Initialize the text-field editor for UA token. """
+ self.valid_char_pat = r"[a-zA-Z0-9]"
+ self.error_invalid_char = _("The only characters permitted in this "
+ "field are alphanumeric characters.")
+ super().__init__()
+
+ def valid_char(self, ch: str) -> bool:
+ """ Tells whether the character passed is within the range of allowed
+ characters
+ """
+ if len(ch) == 1 and not re.match(self.valid_char_pat, ch):
+ self.bff.in_error = True
+ self.bff.show_extra(("info_error", self.error_invalid_char))
+ return False
+ return super().valid_char(ch)
+
+
+class UbuntuAdvantageForm(Form):
+ """
+ Represents a form requesting Ubuntu Advantage information
+ """
+ cancel_label = _("Back")
+
+ UATokenField = simple_field(UATokenEditor)
+
+ token = UATokenField(_("Ubuntu Advantage token:"), help=ua_help)
+
+
+class UbuntuAdvantageView(BaseView):
+ """ Represent the view of the Ubuntu Advantage configuration. """
+
+ title = _("Enable Ubuntu Advantage")
+ excerpt = _("Enter your Ubuntu Advantage token if you want to enroll "
+ "this system.")
+
+ def __init__(self, controller, token: str):
+ """ Initialize the view with the default value for the token. """
+ self.controller = controller
+
+ self.form = UbuntuAdvantageForm(initial={"token": token})
+
+ def on_cancel(_: UbuntuAdvantageForm):
+ self.cancel()
+
+ connect_signal(self.form, 'submit', self.done)
+ connect_signal(self.form, 'cancel', on_cancel)
+
+ super().__init__(self.form.as_screen(excerpt=_(self.excerpt)))
+
+ def done(self, form: UbuntuAdvantageForm) -> None:
+ """ Called when the user presses the Done button. """
+ log.debug("User input: %r", form.as_data())
+
+ self.controller.done(form.token.value)
+
+ def cancel(self) -> None:
+ """ Called when the user presses the Back button. """
+ self.controller.cancel()