console_conf: start initial console-conf tree with a custom controller story

And a custom Welcome screen to boot; to show the overriding of settings.

Signed-off-by: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com>
This commit is contained in:
Mathieu Trudel-Lapierre 2016-06-30 14:18:54 -04:00
parent e1970b6477
commit 848ccabc7d
12 changed files with 480 additions and 2 deletions

83
bin/console-conf-tui Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env python3
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
import argparse
import sys
import logging
import signal
from subiquitycore.log import setup_logger, LOGFILE
from subiquitycore import __version__ as VERSION
from console_conf.core import Controller as ConsoleConf
from subiquitycore.core import CoreControllerError
from subiquitycore.ui.frame import SubiquityUI
from subiquitycore.utils import environment_check
def parse_options(argv):
parser = argparse.ArgumentParser(
description='console-conf - Pre-Ownership Configuration for Ubuntu Core',
prog='console-conf')
parser.add_argument('--dry-run', action='store_true',
dest='dry_run',
help='menu-only, do not call installer function')
# XXX Defaults to firstboot mode unless one runs subiquity --install.
parser.add_argument('--install', action='store_false',
dest='firstboot', default=True,
help='run installer in firstboot mode')
parser.add_argument('--serial', action='store_true',
dest='run_on_serial',
help='Run the installer over serial console.')
parser.add_argument('--machine-config', metavar='CONFIG',
dest='machine_config',
help="Don't Probe. Use probe data file")
parser.add_argument('--uefi', action='store_true',
dest='uefi',
help='run in uefi support mode')
return parser.parse_args(argv)
def control_c_handler(signum, frame):
sys.exit(1)
def main():
opts = parse_options(sys.argv[1:])
setup_logger()
logger = logging.getLogger('console-conf')
logger.info("Starting console-conf v{}".format(VERSION))
logger.info("Arguments passed: {}".format(sys.argv))
signal.signal(signal.SIGINT, control_c_handler)
env_ok = environment_check()
if env_ok is False and not opts.dry_run:
print('Failed environment check. '
'Check {} for errors.'.format(LOGFILE))
return 1
ui = SubiquityUI()
try:
interface = ConsoleConf(ui, opts)
except CoreControllerError as e:
logger.exception('Failed to load ConsoleConf interface')
print(e)
return 1
interface.run()
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,21 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
""" console-conf controllers """
from .welcome import WelcomeController
from subiquitycore.controllers.network import NetworkController
from subiquitycore.controllers.login import LoginController
from subiquitycore.controllers.identity import IdentityController

View File

@ -0,0 +1,53 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
import logging
from subiquitycore.controller import ControllerPolicy
from subiquitycore.models import IdentityModel
from subiquitycore.ui.views import IdentityView, LoginView
log = logging.getLogger('subiquitycore.controllers.identity')
class IdentityController(ControllerPolicy):
def __init__(self, common):
super().__init__(common)
self.model = IdentityModel(self.opts)
def identity(self):
title = "Profile setup"
excerpt = ("Input your username and password to log in to the system.")
footer = ""
self.ui.set_header(title, excerpt)
self.ui.set_footer(footer, 40)
self.ui.set_body(IdentityView(self.model, self.signal, self.opts))
def login(self):
log.debug("Identity login view")
title = ("Configuration Complete")
footer = ("View configured user and device access methods")
self.ui.set_header(title)
self.ui.set_footer(footer)
net_model = self.controllers['Network'].model
configured_ifaces = net_model.get_configured_interfaces()
login_view = LoginView(self.model,
self.signal,
self.model.user,
configured_ifaces)
self.ui.set_body(login_view)

View File

@ -0,0 +1,32 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
from subiquitycore.ui.views import LoginView
from subiquitycore.models import LoginModel
from subiquitycore.controller import ControllerPolicy
class LoginController(ControllerPolicy):
def __init__(self, common):
super().__init__(common)
self.model = LoginModel()
def login(self):
title = "Configuration Complete"
excerpt = "Your device is now configured. Login details below."
self.ui.set_header(title, excerpt)
view = LoginView(self.model, self.signal, self.model.user)
self.ui.set_body(view)

View File

@ -0,0 +1,94 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
import logging
from subiquitycore.controller import ControllerPolicy
from subiquitycore.models import NetworkModel
from subiquitycore.ui.views import (NetworkView,
NetworkSetDefaultRouteView,
NetworkBondInterfacesView,
NetworkConfigureInterfaceView,
NetworkConfigureIPv4InterfaceView)
from subiquitycore.ui.dummy import DummyView
from subiquitycore.curtin import curtin_write_network_actions
from subiquitycore.curtin import curtin_apply_networking
log = logging.getLogger("subiquitycore.controller.network")
class NetworkController(ControllerPolicy):
def __init__(self, common):
super().__init__(common)
self.model = NetworkModel(self.prober, self.opts)
def network(self):
title = "Network connections"
excerpt = ("Configure at least the main interface this server will "
"use to talk to other machines, and preferably provide "
"sufficient access for updates.")
footer = ("Additional networking info here")
self.ui.set_header(title, excerpt)
self.ui.set_footer(footer, 20)
self.ui.set_body(NetworkView(self.model, self.signal))
def network_finish(self, actions):
try:
curtin_write_network_actions(actions)
except (PermissionError, FileNotFoundError):
log.exception('Failed to obtain write permission')
self.signal.emit_signal('filesystem:error',
'curtin_write_network_actions')
return None
curtin_apply_networking(actions, dryrun=self.opts.dry_run)
# switch to identity view
self.signal.emit_signal('menu:identity:main')
def set_default_route(self):
self.ui.set_header("Default route")
self.ui.set_body(NetworkSetDefaultRouteView(self.model,
self.signal))
def bond_interfaces(self):
self.ui.set_header("Bond interfaces")
self.ui.set_body(NetworkBondInterfacesView(self.model,
self.signal))
def network_configure_interface(self, iface):
self.ui.set_header("Network interface {}".format(iface))
self.ui.set_body(NetworkConfigureInterfaceView(self.model,
self.signal,
iface))
def network_configure_ipv4_interface(self, iface):
self.model.prev_signal = ('Back to configure interface menu',
'network:configure-interface-menu',
'network_configure_interface')
self.ui.set_header("Network interface {} manual IPv4 "
"configuration".format(iface))
self.ui.set_body(NetworkConfigureIPv4InterfaceView(self.model,
self.signal,
iface))
def network_configure_ipv6_interface(self, iface):
self.model.prev_signal = ('Back to configure interface menu',
'network:configure-interface-menu',
'network_configure_interface')
self.ui.set_body(DummyView(self.signal))
def install_network_driver(self):
self.ui.set_body(DummyView(self.signal))

View File

@ -0,0 +1,31 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
from console_conf.ui.views import WelcomeView
from subiquitycore.models import WelcomeModel
from subiquitycore.controllers.welcome import WelcomeController as WelcomeControllerBase
class WelcomeController(WelcomeControllerBase):
def welcome(self):
title = " Welcome!"
excerpt = "Please choose your preferred language"
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
"select your language.")
self.ui.set_header(title, excerpt)
self.ui.set_footer(footer)
view = WelcomeView(self.model, self.signal)
self.ui.set_body(view)

94
console_conf/core.py Normal file
View File

@ -0,0 +1,94 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
import logging
import urwid
from tornado.ioloop import IOLoop
from tornado.util import import_object
from subiquitycore.signals import Signal
from subiquitycore.palette import STYLES, STYLES_MONO
from subiquitycore.prober import Prober, ProberException
from subiquitycore.core import CoreControllerError, Controller as ControllerBase
log = logging.getLogger('console_conf.core')
class CoreControllerError(Exception):
""" Basecontroller exception """
pass
class Controller(ControllerBase):
def __init__(self, ui, opts):
try:
prober = Prober(opts)
except ProberException as e:
err = "Prober init failed: {}".format(e)
log.exception(err)
raise CoreControllerError(err)
self.common = {
"ui": ui,
"opts": opts,
"signal": Signal(),
"prober": prober,
"loop": None
}
self.controllers = {
"Welcome": None,
"Network": None,
"Identity": None,
"Login": None,
}
self.common['controllers'] = self.controllers
def run(self):
if not hasattr(self, 'loop'):
palette = STYLES
additional_opts = {
'screen': urwid.raw_display.Screen(),
'unhandled_input': self.header_hotkeys,
'handle_mouse': False
}
if self.common['opts'].run_on_serial:
palette = STYLES_MONO
else:
additional_opts['screen'].set_terminal_properties(colors=256)
additional_opts['screen'].reset_default_terminal_palette()
evl = urwid.TornadoEventLoop(IOLoop())
self.common['loop'] = urwid.MainLoop(
self.common['ui'], palette, event_loop=evl, **additional_opts)
log.debug("Running event loop: {}".format(
self.common['loop'].event_loop))
try:
self.set_alarm_in(0.05, self.welcome)
for k in self.controllers.keys():
log.debug("Importing controller: {}".format(k))
klass = import_object(
"subiquitycore.controllers.{}Controller".format(
k))
klass = import_object(
"console_conf.controllers.{}Controller".format(
k))
self.controllers[k] = klass(self.common)
self._connect_base_signals()
self.common['loop'].run()
except:
log.exception("Exception in controller.run():")
raise

View File

@ -0,0 +1,16 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
""" Subiquity UI Components """

View File

@ -0,0 +1,18 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
""" ConsoleConf UI Views """
from .welcome import WelcomeView

View File

@ -0,0 +1,36 @@
# Copyright 2015 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 <http://www.gnu.org/licenses/>.
""" Welcome
Welcome provides user with language selection
"""
import logging
from urwid import (ListBox, Pile, BoxAdapter)
from subiquitycore.ui.lists import SimpleList
from subiquitycore.ui.buttons import menu_btn, ok_btn, cancel_btn
from subiquitycore.ui.utils import Padding, Color
from subiquitycore.view import ViewPolicy
from subiquitycore.ui.views.welcome import CoreWelcomeView
log = logging.getLogger("console_conf.views.welcome")
class WelcomeView(CoreWelcomeView):
def confirm(self, result):
self.model.selected_language = result.label
log.debug('calling network')
self.signal.emit_signal('menu:network:main')

View File

@ -30,6 +30,6 @@ from .network_configure_ipv4_interface import NetworkConfigureIPv4InterfaceView
from .network_bond_interfaces import NetworkBondInterfacesView # NOQA from .network_bond_interfaces import NetworkBondInterfacesView # NOQA
from .installpath import InstallpathView # NOQA from .installpath import InstallpathView # NOQA
from .installprogress import ProgressView # NOQA from .installprogress import ProgressView # NOQA
from .welcome import WelcomeView # NOQA from .welcome import CoreWelcomeView as WelcomeView # NOQA
from .identity import IdentityView # NOQA from .identity import IdentityView # NOQA
from .login import LoginView # NOQA from .login import LoginView # NOQA

View File

@ -28,7 +28,7 @@ from subiquitycore.view import ViewPolicy
log = logging.getLogger("subiquitycore.views.welcome") log = logging.getLogger("subiquitycore.views.welcome")
class WelcomeView(ViewPolicy): class CoreWelcomeView(ViewPolicy):
def __init__(self, model, signal): def __init__(self, model, signal):
self.model = model self.model = model
self.signal = signal self.signal = signal