diff --git a/initial_setup/__init__.py b/initial_setup/__init__.py index f43f235..30fa8b9 100644 --- a/initial_setup/__init__.py +++ b/initial_setup/__init__.py @@ -11,16 +11,14 @@ from initial_setup.product import eula_available from initial_setup import initial_setup_log +from initial_setup.task import InitialSetupTask -from pyanaconda.core.dbus import DBus from pyanaconda.core.util import get_os_release_value from pyanaconda.localization import setup_locale_environment, setup_locale -from pyanaconda.core.constants import FIRSTBOOT_ENVIRON, SETUP_ON_BOOT_RECONFIG, \ - SETUP_ON_BOOT_DEFAULT +from pyanaconda.core.constants import FIRSTBOOT_ENVIRON, SETUP_ON_BOOT_RECONFIG from pyanaconda.flags import flags from pyanaconda.core.startup.dbus_launcher import AnacondaDBusLauncher -from pyanaconda.modules.common.task import sync_run_task -from pyanaconda.modules.common.constants.services import BOSS, LOCALIZATION, TIMEZONE, USERS, \ +from pyanaconda.modules.common.constants.services import BOSS, LOCALIZATION, USERS, \ SERVICES, NETWORK from pyanaconda.modules.common.structures.kickstart import KickstartReport @@ -282,74 +280,14 @@ def _apply(self): # Do not execute sections that were part of the original # anaconda kickstart file (== have .seen flag set) - log.info("applying changes") - - services_proxy = SERVICES.get_proxy() - reconfig_mode = services_proxy.SetupOnBoot == SETUP_ON_BOOT_RECONFIG - - # data.selinux - # data.firewall - - # Configure the timezone. - timezone_proxy = TIMEZONE.get_proxy() - for task_path in timezone_proxy.InstallWithTasks(): - task_proxy = TIMEZONE.get_proxy(task_path) - sync_run_task(task_proxy) - - # Configure the localization. - localization_proxy = LOCALIZATION.get_proxy() - for task_path in localization_proxy.InstallWithTasks(): - task_proxy = LOCALIZATION.get_proxy(task_path) - sync_run_task(task_proxy) - - # Configure persistent hostname - network_proxy = NETWORK.get_proxy() - network_task = network_proxy.ConfigureHostnameWithTask(True) - task_proxy = NETWORK.get_proxy(network_task) - sync_run_task(task_proxy) - # Set current hostname - network_proxy.SetCurrentHostname(network_proxy.Hostname) - - # Configure groups, users & root account - # - # NOTE: We only configure groups, users & root account if the respective - # kickstart commands are *not* seen in the input kickstart. - # This basically means that we will configure only what was - # set in the Initial Setup UI and will not attempt to configure - # anything that looks like it was configured previously in - # the Anaconda UI or installation kickstart. - users_proxy = USERS.get_proxy() - - if self._groups_already_configured and not reconfig_mode: - log.debug("skipping user group configuration - already configured") - elif users_proxy.Groups: # only run of there are some groups to create - groups_task = users_proxy.ConfigureGroupsWithTask() - task_proxy = USERS.get_proxy(groups_task) - log.debug("configuring user groups via %s task", task_proxy.Name) - sync_run_task(task_proxy) - - if self._users_already_configured and not reconfig_mode: - log.debug("skipping user configuration - already configured") - elif users_proxy.Users: # only run if there are some users to create - users_task = users_proxy.ConfigureUsersWithTask() - task_proxy = USERS.get_proxy(users_task) - log.debug("configuring users via %s task", task_proxy.Name) - sync_run_task(task_proxy) - - if self._root_password_already_configured and not reconfig_mode: - log.debug("skipping root password configuration - already configured") - else: - root_task = users_proxy.SetRootPasswordWithTask() - task_proxy = USERS.get_proxy(root_task) - log.debug("configuring root password via %s task", task_proxy.Name) - sync_run_task(task_proxy) - - # Configure all addons - log.info("executing addons") - boss_proxy = BOSS.get_proxy() - for service_name, object_path in boss_proxy.CollectInstallSystemTasks(): - task_proxy = DBus.get_proxy(service_name, object_path) - sync_run_task(task_proxy) + if not self.gui_mode: + # GUI has it already executed, but in TUI do it here + task = InitialSetupTask( + groups_already_configured=self._groups_already_configured, + users_already_configured=self._users_already_configured, + root_password_already_configured=self._root_password_already_configured, + ) + task.start() if self.external_reconfig: # prevent the reconfig flag from being written out diff --git a/initial_setup/gui/spokes/setup_progress.glade b/initial_setup/gui/spokes/setup_progress.glade new file mode 100644 index 0000000..b7547dd --- /dev/null +++ b/initial_setup/gui/spokes/setup_progress.glade @@ -0,0 +1,168 @@ + + + + + + + False + INSTALLATION PROGRESS + + + False + vertical + + + False + + + False + + + + + + + + + + + False + False + 0 + + + + + False + 0 + 12 + 6 + + + False + vertical + 6 + + + True + False + center + vertical + 6 + + + True + False + center + 6 + + + True + False + True + + + False + True + 0 + + + + + True + False + start + Preparing to install + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + center + + + False + True + 1 + + + + + True + True + 0 + + + + + True + False + end + 10 + False + False + + + True + False + + + + + + + + True + False + end + end + %s is now successfully installed and ready for you to use! +Go ahead and reboot to start using it! + right + True + + + 1 + + + + + + + + False + True + 1 + + + + + + + True + True + 1 + + + + + + + INSTALLATION PROGRESS + + + + diff --git a/initial_setup/gui/spokes/setup_progress.py b/initial_setup/gui/spokes/setup_progress.py new file mode 100644 index 0000000..ef8faa9 --- /dev/null +++ b/initial_setup/gui/spokes/setup_progress.py @@ -0,0 +1,179 @@ +# +# Copyright (C) 2011-2013 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +import logging + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +from initial_setup import InitialSetupTask +from initial_setup.gui.hubs import InitialSetupMainHub +from pyanaconda.core.i18n import _, C_ +from pyanaconda.modules.common.constants.services import USERS +from pyanaconda.product import productName +from pyanaconda.core import util +from pyanaconda.core.configuration.anaconda import conf +from pyanaconda.core.constants import IPMI_FINISHED +from pyanaconda.ui.common import FirstbootOnlySpokeMixIn +from pyanaconda.ui.gui.spokes import StandaloneSpoke +from pyanaconda.ui.gui.utils import gtk_call_once + +log = logging.getLogger("initial-setup") + +__all__ = ["ProgressSpoke"] + + +class ProgressSpoke(FirstbootOnlySpokeMixIn, StandaloneSpoke): + """ + .. inheritance-diagram:: ProgressSpoke + :parts: 3 + """ + + builderObjects = ["progressWindow"] + mainWidgetName = "progressWindow" + uiFile = "setup_progress.glade" + postForHub = InitialSetupMainHub + + @staticmethod + def get_screen_id(): + """Return a unique id of this UI screen.""" + return "installation-progress" + + def __init__(self, data, storage, payload): + super().__init__(data, storage, payload) + self._progressBar = self.builder.get_object("progressBar") + self._progressLabel = self.builder.get_object("progressLabel") + self._progressNotebook = self.builder.get_object("progressNotebook") + self._spinner = self.builder.get_object("progressSpinner") + self._task = None + + # Record if groups, users or root password has been set before Initial Setup + # has been started, so that we don't trample over existing configuration. + log.info("collecting initial state") + users_proxy = USERS.get_proxy() + self._groups_already_configured = bool(users_proxy.Groups) + self._users_already_configured = bool(users_proxy.Users) + self._root_password_already_configured = users_proxy.IsRootPasswordSet + + @property + def completed(self): + """This spoke is never completed, initially.""" + return False + + def apply(self): + """There is nothing to apply.""" + pass + + def _on_installation_done(self): + log.debug("The initial setup has finished.") + + # Stop the spinner. + gtk_call_once(self._spinner.stop) + gtk_call_once(self._spinner.hide) + + # Finish the installation task. Re-raise tracebacks if any. + try: + self._task.finish() + except Exception as e: + log.exception("Initial setup failed") + self.showErrorMessageHelper(str(e)) + + util.ipmi_report(IPMI_FINISHED) + + if conf.license.eula: + self.set_warning(_("Use of this product is subject to the license agreement " + "found at %s") % conf.license.eula) + self.window.show_all() + + # Show the reboot message. + self._progressNotebook.set_current_page(1) + + # Enable the continue button. + self.window.set_may_continue(True) + + # Hide the quit button. + quit_button = self.window.get_quit_button() + quit_button.hide() + + # automatically close; if there was an error, showErrorMessageHelper + # already waited for the user to click ok + self.window.emit("continue-clicked") + + def initialize(self): + super().initialize() + # Disable the continue button. + self.window.set_may_continue(False) + + # Set the label of the continue button. + continue_label = C_("GUI|Progress", "_Finish Installation") + + continue_button = self.window.get_continue_button() + continue_button.set_label(continue_label) + + # Set the reboot label. + continue_text = _( + "%s is now successfully installed and ready for you to use!\n" + "Go ahead and quit the application to start using it!" + ) % productName + + label = self.builder.get_object("rebootLabel") + label.set_text(continue_text) + + # Don't show the reboot message. + self._progressNotebook.set_current_page(0) + + def refresh(self): + from pyanaconda.installation import RunInstallationTask + super().refresh() + + # Initialize the progress bar. + gtk_call_once(self._progressBar.set_fraction, 0.0) + + # Start the installation task. + self._task = InitialSetupTask( + groups_already_configured=self._groups_already_configured, + users_already_configured=self._users_already_configured, + root_password_already_configured=self._root_password_already_configured, + ) + self._task.progress_changed_signal.connect( + self._on_progress_changed + ) + self._task.stopped_signal.connect( + self._on_installation_done + ) + self._task.start() + + # Start the spinner. + gtk_call_once(self._spinner.start) + + log.debug("The installation has started.") + + def showErrorMessageHelper(self, text): + dlg = Gtk.MessageDialog(title="Error", message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text=text) + dlg.set_position(Gtk.WindowPosition.CENTER) + dlg.set_modal(True) + dlg.set_transient_for(self.main_window) + dlg.run() + dlg.destroy() + + def _on_progress_changed(self, step, message): + """Handle a new progress report.""" + if message: + gtk_call_once(self._progressLabel.set_text, message) + + if self._task.steps > 0: + gtk_call_once(self._progressBar.set_fraction, step/self._task.steps) diff --git a/initial_setup/task.py b/initial_setup/task.py new file mode 100644 index 0000000..848dd11 --- /dev/null +++ b/initial_setup/task.py @@ -0,0 +1,141 @@ +import logging + +from pyanaconda.core.constants import SETUP_ON_BOOT_RECONFIG, SETUP_ON_BOOT_DEFAULT +from pyanaconda.core.dbus import DBus +from pyanaconda.installation_tasks import TaskQueue, DBusTask +from pyanaconda.modules.common.constants.services import SERVICES, TIMEZONE, LOCALIZATION, NETWORK, USERS, BOSS +from pyanaconda.modules.common.task import Task, sync_run_task +from pyanaconda.core.i18n import _ + +log = logging.getLogger("initial-setup") + + +class InitialSetupTask(Task): + def __init__(self, + groups_already_configured=False, + users_already_configured=False, + root_password_already_configured=False): + super().__init__() + self._total_steps = 0 + self._groups_already_configured = groups_already_configured + self._users_already_configured = users_already_configured + self._root_password_already_configured = root_password_already_configured + + @property + def name(self): + """Name of the task""" + return "Run the initial setup queue." + + @property + def steps(self): + """Total number of steps.""" + return self._total_steps + + def _queue_started_cb(self, task): + """The installation queue was started.""" + self.report_progress(task.status_message) + + def _task_completed_cb(self, task): + """The installation task was completed.""" + self.report_progress("", step_size=1) + + def _task_started_cb(self, task): + """The installation task was completed.""" + self.report_progress(task.name) + + def _progress_report_cb(self, step, message): + """Handle a progress report of a task.""" + self.report_progress(message) + + def run(self): + """Run the task.""" + log.info("applying changes") + + queue = TaskQueue("Initial Setup") + + # connect progress reporting + queue.queue_started.connect(self._queue_started_cb) + queue.task_completed.connect(self._task_completed_cb) + queue.task_started.connect(self._task_started_cb) + + services_proxy = SERVICES.get_proxy() + reconfig_mode = services_proxy.SetupOnBoot == SETUP_ON_BOOT_RECONFIG + + # data.selinux + # data.firewall + + # Configure the timezone. + timezone_proxy = TIMEZONE.get_proxy() + for task_path in timezone_proxy.InstallWithTasks(): + task_proxy = TIMEZONE.get_proxy(task_path) + queue.append(DBusTask(task_proxy)) + + # Configure the localization. + localization_proxy = LOCALIZATION.get_proxy() + for task_path in localization_proxy.InstallWithTasks(): + task_proxy = LOCALIZATION.get_proxy(task_path) + queue.append(DBusTask(task_proxy)) + + # Configure persistent hostname + network_proxy = NETWORK.get_proxy() + network_task = network_proxy.ConfigureHostnameWithTask(True) + task_proxy = NETWORK.get_proxy(network_task) + queue.append(DBusTask(task_proxy)) + # Set current hostname + network_proxy.SetCurrentHostname(network_proxy.Hostname) + + # Configure groups, users & root account + # + # NOTE: We only configure groups, users & root account if the respective + # kickstart commands are *not* seen in the input kickstart. + # This basically means that we will configure only what was + # set in the Initial Setup UI and will not attempt to configure + # anything that looks like it was configured previously in + # the Anaconda UI or installation kickstart. + users_proxy = USERS.get_proxy() + + if self._groups_already_configured and not reconfig_mode: + log.debug("skipping user group configuration - already configured") + elif users_proxy.Groups: # only run of there are some groups to create + groups_task = users_proxy.ConfigureGroupsWithTask() + task_proxy = USERS.get_proxy(groups_task) + log.debug("configuring user groups via %s task", task_proxy.Name) + queue.append(DBusTask(task_proxy)) + + if self._users_already_configured and not reconfig_mode: + log.debug("skipping user configuration - already configured") + elif users_proxy.Users: # only run if there are some users to create + users_task = users_proxy.ConfigureUsersWithTask() + task_proxy = USERS.get_proxy(users_task) + log.debug("configuring users via %s task", task_proxy.Name) + queue.append(DBusTask(task_proxy)) + + if self._root_password_already_configured and not reconfig_mode: + log.debug("skipping root password configuration - already configured") + else: + root_task = users_proxy.SetRootPasswordWithTask() + task_proxy = USERS.get_proxy(root_task) + log.debug("configuring root password via %s task", task_proxy.Name) + queue.append(DBusTask(task_proxy)) + + # Configure all addons + log.info("executing addons") + boss_proxy = BOSS.get_proxy() + for service_name, object_path in boss_proxy.CollectInstallSystemTasks(): + task_proxy = DBus.get_proxy(service_name, object_path) + queue.append(DBusTask(task_proxy)) + + for item in queue.nested_items: + if isinstance(item, DBusTask): + item._progress_cb = self._progress_report_cb + + self._total_steps = queue.task_count + + # log contents of the main task queue + log.info(queue.summary) + + # start the task queue + queue.start() + + # done + self.report_progress(_("Complete!"), step_number=self.steps)