diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9b88b0ee..1fe47dc5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,6 @@
[encoding: UTF-8]
subiquity/client/client.py
+subiquity/client/controller.py
subiquity/client/__init__.py
subiquity/client/keycodes.py
subiquity/cmd/common.py
diff --git a/subiquity/client/controller.py b/subiquity/client/controller.py
new file mode 100644
index 00000000..1740ef05
--- /dev/null
+++ b/subiquity/client/controller.py
@@ -0,0 +1,37 @@
+# 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 logging
+
+from subiquitycore.tuicontroller import (
+ TuiController,
+ )
+
+log = logging.getLogger("subiquity.client.controller")
+
+
+class Confirm(Exception):
+ pass
+
+
+class SubiquityTuiController(TuiController):
+
+ endpoint_name = None
+
+ def __init__(self, app):
+ super().__init__(app)
+ self.answers = app.answers.get(self.name, {})
+ if self.endpoint_name is not None:
+ self.endpoint = getattr(self.app.client, self.endpoint_name)
diff --git a/subiquity/server/controller.py b/subiquity/server/controller.py
index 49812b1b..90e9eabf 100644
--- a/subiquity/server/controller.py
+++ b/subiquity/server/controller.py
@@ -13,14 +13,97 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import json
import logging
+import os
+import jsonschema
+
+from subiquitycore.context import with_context
from subiquitycore.controller import (
BaseController,
)
+from subiquity.common.api.server import bind
+
log = logging.getLogger("subiquity.server.controller")
class SubiquityController(BaseController):
- pass
+
+ autoinstall_key = None
+ autoinstall_schema = None
+ autoinstall_default = None
+ endpoint = None
+
+ def __init__(self, app):
+ super().__init__(app)
+ self.context.set('controller', self)
+
+ def setup_autoinstall(self):
+ if not self.app.autoinstall_config:
+ return
+ with self.context.child("load_autoinstall_data"):
+ ai_data = self.app.autoinstall_config.get(
+ self.autoinstall_key,
+ self.autoinstall_default)
+ if ai_data is not None and self.autoinstall_schema is not None:
+ jsonschema.validate(ai_data, self.autoinstall_schema)
+ self.load_autoinstall_data(ai_data)
+
+ def load_autoinstall_data(self, data):
+ """Load autoinstall data.
+
+ This is called if there is an autoinstall happening. This
+ controller may not have any data, and this controller may still
+ be interactive.
+ """
+ pass
+
+ @with_context()
+ async def apply_autoinstall_config(self, context):
+ """Apply autoinstall configuration.
+
+ This is only called for a non-interactive controller. It should
+ block until the configuration has been applied. (self.configured()
+ is called after this is done).
+ """
+ pass
+
+ def interactive(self):
+ if not self.app.autoinstall_config:
+ return True
+ i_sections = self.app.autoinstall_config.get(
+ 'interactive-sections', [])
+ return '*' in i_sections or self.autoinstall_key in i_sections
+
+ def configured(self):
+ """Let the world know that this controller's model is now configured.
+ """
+ with open(self.app.state_path('states', self.name), 'w') as fp:
+ json.dump(self.serialize(), fp)
+ if self.model_name is not None:
+ self.app.base_model.configured(self.model_name)
+
+ def load_state(self):
+ state_path = self.app.state_path('states', self.name)
+ if not os.path.exists(state_path):
+ return
+ with open(state_path) as fp:
+ self.deserialize(json.load(fp))
+
+ def deserialize(self, state):
+ pass
+
+ def make_autoinstall(self):
+ return {}
+
+ def add_routes(self, app):
+ if self.endpoint is not None:
+ bind(app.router, self.endpoint, self)
+
+
+class NonInteractiveController(SubiquityController):
+
+ def interactive(self):
+ return False