Ejemplo n.º 1
0
class SpecifyRepoSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Specify the repo URL here if closest mirror not selected. """
    category = SoftwareCategory

    HTTP = 1
    HTTPS = 2
    FTP = 3

    def __init__(self, data, storage, payload, instclass, protocol):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Specify Repo Options")
        self.protocol = protocol
        self._container = None

        self._url = self.data.url.url

    def refresh(self, args=None):
        """ Refresh window. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(_("Repo URL"))
        self._container.add(EntryWidget(dialog.title, self.data.method.url),
                            self._set_repo_url, dialog)

        self.window.add_with_separator(self._container)

    def _set_repo_url(self, dialog):
        self._url = dialog.run()

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            self.redraw()
            return InputState.PROCESSED
        else:
            return NormalTUISpoke.input(self, args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply all of our changes. """
        if self.protocol == SpecifyRepoSpoke.HTTP and not self._url.startswith(
                "http://"):
            url = "http://" + self._url
        elif self.protocol == SpecifyRepoSpoke.HTTPS and not self._url.startswith(
                "https://"):
            url = "https://" + self._url
        elif self.protocol == SpecifyRepoSpoke.FTP and not self._url.startswith(
                "ftp://"):
            url = "ftp://" + self._url
        else:
            # protocol either unknown or entry already starts with a protocol
            # specification
            url = self._url
        self.set_source_url(url)
Ejemplo n.º 2
0
class NTPServersSpoke(NormalTUISpoke):
    category = LocalizationCategory

    def __init__(self, data, storage, payload, instclass, time_spoke):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("NTP configuration")
        self._container = None
        self._time_spoke = time_spoke

    @property
    def indirect(self):
        return True

    def _summary_text(self):
        """Return summary of NTP configuration."""
        msg = _("NTP servers:")
        if self._time_spoke.ntp_servers:
            for status in format_ntp_status_list(self._time_spoke.ntp_servers):
                msg += "\n%s" % status
        else:
            msg += _("no NTP servers have been configured")
        return msg

    def refresh(self, args=None):
        super().refresh(args)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        self._container.add(TextWidget(_("Add NTP server")), self._add_ntp_server)

        # only add the remove option when we can remove something
        if self._time_spoke.ntp_servers:
            self._container.add(TextWidget(_("Remove NTP server")), self._remove_ntp_server)

        self.window.add_with_separator(self._container)

    def _add_ntp_server(self, data):
        new_spoke = AddNTPServerSpoke(self.data, self.storage, self.payload,
                                      self.instclass, self._time_spoke)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def _remove_ntp_server(self, data):
        new_spoke = RemoveNTPServerSpoke(self.data, self.storage, self.payload,
                                         self.instclass, self._time_spoke)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        pass
Ejemplo n.º 3
0
class PartitionSchemeSpoke(NormalTUISpoke):
    """ Spoke to select what partitioning scheme to use on disk(s). """
    category = SystemCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Partition Scheme Options")
        self._container = None
        self.part_schemes = OrderedDict()

        self._auto_part_proxy = STORAGE.get_proxy(AUTO_PARTITIONING)
        pre_select = self._auto_part_proxy.Type
        supported_choices = get_supported_autopart_choices()

        if supported_choices:
            # Fallback value (eg when default is not supported)
            self._selected_scheme_value = supported_choices[0][1]

        for item in supported_choices:
            self.part_schemes[item[0]] = item[1]
            if item[1] == pre_select:
                self._selected_scheme_value = item[1]

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)

        self._container = ListColumnContainer(1)

        for scheme, value in self.part_schemes.items():
            box = CheckboxWidget(title=_(scheme), completed=(value == self._selected_scheme_value))
            self._container.add(box, self._set_part_scheme_callback, value)

        self.window.add_with_separator(self._container)

        message = _("Select a partition scheme configuration.")
        self.window.add_with_separator(TextWidget(message))

    def _set_part_scheme_callback(self, data):
        self._selected_scheme_value = data

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

        return InputState.PROCESSED_AND_REDRAW

    def apply(self):
        """ Apply our selections. """
        self._auto_part_proxy.SetType(self._selected_scheme_value)
Ejemplo n.º 4
0
class PartitionSchemeSpoke(NormalTUISpoke):
    """ Spoke to select what partitioning scheme to use on disk(s). """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("Partition Scheme Options")
        self._container = None
        self.part_schemes = OrderedDict()

        self._auto_part_proxy = STORAGE.get_proxy(AUTO_PARTITIONING)
        pre_select = self._auto_part_proxy.Type

        if pre_select == AUTOPART_TYPE_DEFAULT:
            pre_select = DEFAULT_AUTOPART_TYPE

        for item in AUTOPART_CHOICES:
            self.part_schemes[item[0]] = item[1]
            if item[1] == pre_select:
                self._selected_scheme_value = item[1]

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)

        self._container = ListColumnContainer(1)

        for scheme, value in self.part_schemes.items():
            box = CheckboxWidget(
                title=_(scheme),
                completed=(value == self._selected_scheme_value))
            self._container.add(box, self._set_part_scheme_callback, value)

        self.window.add_with_separator(self._container)

        message = _("Select a partition scheme configuration.")
        self.window.add_with_separator(TextWidget(message))

    def _set_part_scheme_callback(self, data):
        self._selected_scheme_value = data

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

        return InputState.PROCESSED_AND_REDRAW

    def apply(self):
        """ Apply our selections. """
        self._auto_part_proxy.SetType(self._selected_scheme_value)
Ejemplo n.º 5
0
class NTPServersSpoke(NormalTUISpoke):
    category = LocalizationCategory

    def __init__(self, data, storage, payload, time_spoke):
        super().__init__(data, storage, payload)
        self.title = N_("NTP configuration")
        self._container = None
        self._time_spoke = time_spoke

    @property
    def indirect(self):
        return True

    def _summary_text(self):
        """Return summary of NTP configuration."""
        msg = _("NTP servers:")
        if self._time_spoke.ntp_servers:
            for status in format_ntp_status_list(self._time_spoke.ntp_servers):
                msg += "\n%s" % status
        else:
            msg += _("no NTP servers have been configured")
        return msg

    def refresh(self, args=None):
        super().refresh(args)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        self._container.add(TextWidget(_("Add NTP server")), self._add_ntp_server)

        # only add the remove option when we can remove something
        if self._time_spoke.ntp_servers:
            self._container.add(TextWidget(_("Remove NTP server")), self._remove_ntp_server)

        self.window.add_with_separator(self._container)

    def _add_ntp_server(self, data):
        new_spoke = AddNTPServerSpoke(self.data, self.storage, self.payload, self._time_spoke)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def _remove_ntp_server(self, data):
        new_spoke = RemoveNTPServerSpoke(self.data, self.storage, self.payload, self._time_spoke)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        pass
Ejemplo n.º 6
0
class RootSelectionSpoke(NormalTUISpoke):
    """UI for selection of installed system root to be mounted."""

    def __init__(self, roots):
        super().__init__(data=None, storage=None, payload=None)
        self.title = N_("Root Selection")
        self._roots = roots
        self._selection = roots[0]
        self._container = None

    @property
    def selection(self):
        """The selected root fs to mount."""
        return self._selection

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)
        self._container = ListColumnContainer(1)

        for root in self._roots:
            box = CheckboxWidget(
                title="{} on {}".format(root.name, root.device.path),
                completed=(self._selection == root)
            )

            self._container.add(box, self._select_root, root)

        message = _("The following installations were discovered on your system.")
        self.window.add_with_separator(TextWidget(message))
        self.window.add_with_separator(self._container)

    def _select_root(self, root):
        self._selection = root

    def prompt(self, args=None):
        """ Override the default TUI prompt."""
        prompt = Prompt()
        prompt.add_continue_option()
        return prompt

    def input(self, args, key):
        """Override any input so we can launch rescue mode."""
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_REDRAW
        elif key == Prompt.CONTINUE:
            return InputState.PROCESSED_AND_CLOSE
        else:
            return key

    def apply(self):
        """Define the abstract method."""
        pass
Ejemplo n.º 7
0
class AdditionalSoftwareSpoke(NormalTUISpoke):
    """The spoke for choosing the additional software."""
    category = SoftwareCategory

    def __init__(self, data, storage, payload, selection_cache):
        super().__init__(data, storage, payload)
        self.title = N_("Software selection")
        self._container = None
        self._selection_cache = selection_cache

    @property
    def _dnf_manager(self):
        """The DNF manager."""
        return self.payload.dnf_manager

    def refresh(self, args=None):
        """Refresh the screen."""
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(columns=2,
                                              columns_width=38,
                                              spacing=2)

        for group in self._selection_cache.available_groups:
            data = self._dnf_manager.get_group_data(group)
            selected = self._selection_cache.is_group_selected(group)

            widget = CheckboxWidget(title=data.name, completed=selected)

            self._container.add(widget,
                                callback=self._select_group,
                                data=data.id)

        if self._selection_cache.available_groups:
            msg = _("Additional software for selected environment")
        else:
            msg = _("No additional software to select.")

        self.window.add_with_separator(TextWidget(msg))
        self.window.add_with_separator(self._container)

    def _select_group(self, group):
        if not self._selection_cache.is_group_selected(group):
            self._selection_cache.select_group(group)
        else:
            self._selection_cache.deselect_group(group)

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_REDRAW
        else:
            return super().input(args, key)

    def apply(self):
        pass
Ejemplo n.º 8
0
class SpecifyRepoSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Specify the repo URL here if closest mirror not selected. """
    category = SoftwareCategory

    HTTP = 1
    HTTPS = 2
    FTP = 3

    def __init__(self, data, storage, payload, instclass, protocol):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Specify Repo Options")
        self.protocol = protocol
        self._container = None

        self._url = self.data.url.url

    def refresh(self, args=None):
        """ Refresh window. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(_("Repo URL"))
        self._container.add(EntryWidget(dialog.title, self._url), self._set_repo_url, dialog)

        self.window.add_with_separator(self._container)

    def _set_repo_url(self, dialog):
        self._url = dialog.run()

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return NormalTUISpoke.input(self, args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply all of our changes. """
        if self.protocol == SpecifyRepoSpoke.HTTP and not self._url.startswith("http://"):
            url = "http://" + self._url
        elif self.protocol == SpecifyRepoSpoke.HTTPS and not self._url.startswith("https://"):
            url = "https://" + self._url
        elif self.protocol == SpecifyRepoSpoke.FTP and not self._url.startswith("ftp://"):
            url = "ftp://" + self._url
        else:
            # protocol either unknown or entry already starts with a protocol
            # specification
            url = self._url
        self.set_source_url(url)
Ejemplo n.º 9
0
class PartitionSchemeSpoke(NormalTUISpoke):
    """ Spoke to select what partitioning scheme to use on disk(s). """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Partition Scheme Options")
        self._container = None
        self.part_schemes = OrderedDict()
        pre_select = self.data.autopart.type or DEFAULT_AUTOPART_TYPE
        for item in AUTOPART_CHOICES:
            self.part_schemes[item[0]] = item[1]
            if item[1] == pre_select:
                self._selected_scheme_value = item[1]

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        for scheme, value in self.part_schemes.items():
            box = CheckboxWidget(title=_(scheme), completed=(value == self._selected_scheme_value))
            self._container.add(box, self._set_part_scheme_callback, value)

        self.window.add_with_separator(self._container)

        message = _("Select a partition scheme configuration.")
        self.window.add_with_separator(TextWidget(message))

    def _set_part_scheme_callback(self, data):
        self._selected_scheme_value = data

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()
                self.close()
                return InputState.PROCESSED
            else:
                return super(PartitionSchemeSpoke, self).input(args, key)

        self.redraw()
        return InputState.PROCESSED

    def apply(self):
        """ Apply our selections. """
        self.data.autopart.type = self._selected_scheme_value
Ejemplo n.º 10
0
class NTPServersSpoke(NormalTUISpoke):
    category = LocalizationCategory

    def __init__(self, data, storage, payload, servers, states):
        super().__init__(data, storage, payload)
        self.title = N_("NTP configuration")
        self._container = None
        self._servers = servers
        self._states = states

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)

        summary = ntp.get_ntp_servers_summary(self._servers, self._states)

        self.window.add_with_separator(TextWidget(summary))

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)
        self._container.add(TextWidget(_("Add NTP server")),
                            self._add_ntp_server)

        # only add the remove option when we can remove something
        if self._servers:
            self._container.add(TextWidget(_("Remove NTP server")),
                                self._remove_ntp_server)

        self.window.add_with_separator(self._container)

    def _add_ntp_server(self, data):
        new_spoke = AddNTPServerSpoke(self.data, self.storage, self.payload,
                                      self._servers, self._states)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def _remove_ntp_server(self, data):
        new_spoke = RemoveNTPServerSpoke(self.data, self.storage, self.payload,
                                         self._servers, self._states)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        pass
Ejemplo n.º 11
0
class RemoveNTPServerSpoke(NormalTUISpoke):
    category = LocalizationCategory

    def __init__(self, data, storage, payload, servers, states):
        super().__init__(data, storage, payload)
        self.title = N_("Select an NTP server to remove")
        self._servers = servers
        self._states = states
        self._container = None

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)
        self._container = ListColumnContainer(1)

        for server in self._servers:
            description = ntp.get_ntp_server_summary(server, self._states)

            self._container.add(TextWidget(description),
                                self._remove_ntp_server, server)

        self.window.add_with_separator(self._container)

    def _remove_ntp_server(self, server):
        self._servers.remove(server)

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_CLOSE

        return super().input(args, key)

    def apply(self):
        pass
Ejemplo n.º 12
0
class TimeZoneSpoke(NormalTUISpoke):
    """
       .. inheritance-diagram:: TimeZoneSpoke
          :parts: 3
    """
    category = LocalizationCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.title = N_("Timezone settings")
        self._container = None
        # it's stupid to call get_all_regions_and_timezones twice, but regions
        # needs to be unsorted in order to display in the same order as the GUI
        # so whatever
        self._regions = list(timezone.get_all_regions_and_timezones().keys())
        self._timezones = dict(
            (k, sorted(v))
            for k, v in timezone.get_all_regions_and_timezones().items())
        self._lower_regions = [r.lower() for r in self._regions]

        self._zones = [
            "%s/%s" % (region, z) for region in self._timezones
            for z in self._timezones[region]
        ]
        # for lowercase lookup
        self._lower_zones = [
            z.lower().replace("_", " ") for region in self._timezones
            for z in self._timezones[region]
        ]
        self._selection = ""

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        """args is None if we want a list of zones or "zone" to show all timezones in that zone."""
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(3, columns_width=24)

        if args and args in self._timezones:
            self.window.add(
                TextWidget(_("Available timezones in region %s") % args))
            for tz in self._timezones[args]:
                self._container.add(TextWidget(tz),
                                    self._select_timezone_callback,
                                    CallbackTimezoneArgs(args, tz))
        else:
            self.window.add(TextWidget(_("Available regions")))
            for region in self._regions:
                self._container.add(TextWidget(region),
                                    self._select_region_callback, region)

        self.window.add_with_separator(self._container)

    def _select_timezone_callback(self, data):
        self._selection = "%s/%s" % (data.region, data.timezone)
        self.apply()
        self.close()

    def _select_region_callback(self, data):
        region = data
        selected_timezones = self._timezones[region]
        if len(selected_timezones) == 1:
            self._selection = "%s/%s" % (region, selected_timezones[0])
            self.apply()
            self.close()
        else:
            ScreenHandler.replace_screen(self, region)

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            if key.lower().replace("_", " ") in self._lower_zones:
                index = self._lower_zones.index(key.lower().replace("_", " "))
                self._selection = self._zones[index]
                self.apply()
                self.close()
                return InputState.PROCESSED
            elif key.lower() in self._lower_regions:
                index = self._lower_regions.index(key.lower())
                if len(self._timezones[self._regions[index]]) == 1:
                    self._selection = "%s/%s" % (
                        self._regions[index],
                        self._timezones[self._regions[index]][0])
                    self.apply()
                    self.close()
                else:
                    ScreenHandler.replace_screen(self, self._regions[index])
                return InputState.PROCESSED
            # TRANSLATORS: 'b' to go back
            elif key.lower() == C_('TUI|Spoke Navigation|Time Settings', 'b'):
                ScreenHandler.replace_screen(self)
                return InputState.PROCESSED
            else:
                return key

    def prompt(self, args=None):
        """ Customize default prompt. """
        prompt = NormalTUISpoke.prompt(self, args)
        prompt.set_message(
            _("Please select the timezone. Use numbers or type names directly")
        )
        # TRANSLATORS: 'b' to go back
        prompt.add_option(C_('TUI|Spoke Navigation|Time Settings', 'b'),
                          _("back to region list"))
        return prompt

    def apply(self):
        self.data.timezone.timezone = self._selection
        self.data.timezone.seen = False
Ejemplo n.º 13
0
class PartTypeSpoke(NormalTUISpoke):
    """ Partitioning options are presented here.

       .. inheritance-diagram:: PartTypeSpoke
          :parts: 3
    """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Partitioning Options")
        self._container = None
        self.parttypelist = sorted(PARTTYPES.keys())

        # remember the original values so that we can detect a change
        self._orig_clearpart_type = self.data.clearpart.type
        self._orig_mount_assign = len(self.data.mount.dataList()) != 0

        # default to mount point assignment if it is already (partially)
        # configured
        self._do_mount_assign = self._orig_mount_assign
        if not self._do_mount_assign:
            self.clearPartType = self.data.clearpart.type or CLEARPART_TYPE_ALL
        else:
            self.clearPartType = CLEARPART_TYPE_NONE

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)
        self._container = ListColumnContainer(1)

        for part_type in self.parttypelist:
            c = CheckboxWidget(
                title=_(part_type),
                completed=(not self._do_mount_assign
                           and PARTTYPES[part_type] == self.clearPartType))
            self._container.add(c, self._select_partition_type_callback,
                                part_type)
        c = CheckboxWidget(title=_("Manually assign mount points") +
                           _(" (EXPERIMENTAL)"),
                           completed=self._do_mount_assign)
        self._container.add(c, self._select_mount_assign)

        self.window.add_with_separator(self._container)

        message = _(
            "Installation requires partitioning of your hard drive. "
            "Select what space to use for the install target or manually assign mount points."
        )

        self.window.add_with_separator(TextWidget(message))

    def _select_mount_assign(self, data=None):
        self.clearPartType = CLEARPART_TYPE_NONE
        self._do_mount_assign = True
        self.apply()

    def _select_partition_type_callback(self, data):
        self._do_mount_assign = False
        self.clearPartType = PARTTYPES[data]
        self.apply()

    def apply(self):
        # kind of a hack, but if we're actually getting to this spoke, there
        # is no doubt that we are doing autopartitioning, so set autopart to
        # True. In the case of ks installs which may not have defined any
        # partition options, autopart was never set to True, causing some
        # issues. (rhbz#1001061)
        if not self._do_mount_assign:
            self.data.autopart.autopart = True
            self.data.clearpart.type = self.clearPartType
            self.data.clearpart.initAll = True
            self.data.mount.clear_mount_data()
        else:
            self.data.autopart.autopart = False
            self.data.clearpart.type = CLEARPART_TYPE_NONE
            self.data.clearpart.initAll = False

    def _ensure_init_storage(self):
        """
        If a different clearpart type was chosen or mount point assignment was
        chosen instead, we need to reset/rescan storage to revert all changes
        done by the previous run of doKickstartStorage() and get everything into
        the initial state.
        """
        # the only safe options are:
        # 1) if nothing was set before (self._orig_clearpart_type is None) or
        # 2) mount point assignment was done before and user just wants to tweak it
        if self._orig_clearpart_type is None or (self._orig_mount_assign
                                                 and self._do_mount_assign):
            return

        # else
        print(_("Reverting previous configuration. This may take a moment..."))
        # unset self.data.ignoredisk.onlyuse temporarily so that
        # storage_initialize() processes all devices
        ignoredisk = self.data.ignoredisk.onlyuse
        self.data.ignoredisk.onlyuse = []
        storage_initialize(self.storage, self.data,
                           self.storage.devicetree.protected_dev_names)
        self.data.ignoredisk.onlyuse = ignoredisk
        self.data.mount.clear_mount_data()

    def input(self, args, key):
        """Grab the choice and update things"""
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()
                self._ensure_init_storage()
                if self._do_mount_assign:
                    new_spoke = MountPointAssignSpoke(self.data, self.storage,
                                                      self.payload,
                                                      self.instclass)
                else:
                    new_spoke = PartitionSchemeSpoke(self.data, self.storage,
                                                     self.payload,
                                                     self.instclass)
                ScreenHandler.push_screen_modal(new_spoke)
                self.close()
                return InputState.PROCESSED
            else:
                return super(PartTypeSpoke, self).input(args, key)

        self.redraw()
        return InputState.PROCESSED
Ejemplo n.º 14
0
class StorageSpoke(NormalTUISpoke):
    """Storage spoke where users proceed to customize storage features such
       as disk selection, partitioning, and fs type.

       .. inheritance-diagram:: StorageSpoke
          :parts: 3
    """
    helpFile = "StorageSpoke.txt"
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.title = N_("Installation Destination")
        self._ready = False
        self._container = None
        self.selected_disks = self.data.ignoredisk.onlyuse[:]
        self.select_all = False

        self.autopart = None

        # This list gets set up once in initialize and should not be modified
        # except perhaps to add advanced devices. It will remain the full list
        # of disks that can be included in the install.
        self.disks = []
        self.errors = []
        self.warnings = []

        if self.data.zerombr.zerombr and arch.is_s390():
            # if zerombr is specified in a ks file and there are unformatted
            # dasds, automatically format them. pass in storage.devicetree here
            # instead of storage.disks since media_present is checked on disks;
            # a dasd needing dasdfmt will fail this media check though
            to_format = [
                d for d in getDisks(self.storage.devicetree) if
                d.type == "dasd" and blockdev.s390.dasd_needs_format(d.busid)
            ]
            if to_format:
                self.run_dasdfmt(to_format)

        if not flags.automatedInstall:
            # default to using autopart for interactive installs
            self.data.autopart.autopart = True

    @property
    def completed(self):
        retval = bool(self.storage.root_device and not self.errors)

        return retval

    @property
    def ready(self):
        # By default, the storage spoke is not ready.  We have to wait until
        # storageInitialize is done.
        return self._ready and not threadMgr.get(THREAD_STORAGE_WATCHER)

    @property
    def mandatory(self):
        return True

    @property
    def showable(self):
        return not flags.dirInstall

    @property
    def status(self):
        """ A short string describing the current status of storage setup. """
        msg = _("No disks selected")

        if flags.automatedInstall and not self.storage.root_device:
            msg = _("Kickstart insufficient")
        elif self.data.ignoredisk.onlyuse:
            msg = P_(("%d disk selected"), ("%d disks selected"),
                     len(self.data.ignoredisk.onlyuse)) % len(
                         self.data.ignoredisk.onlyuse)

            if self.errors:
                msg = _("Error checking storage configuration")
            elif self.warnings:
                msg = _("Warning checking storage configuration")
            # Maybe show what type of clearpart and which disks selected?
            elif self.data.autopart.autopart:
                msg = _("Automatic partitioning selected")
            else:
                msg = _("Custom partitioning selected")

        return msg

    def _update_disk_list(self, disk):
        """ Update self.selected_disks based on the selection."""

        name = disk.name

        # if the disk isn't already selected, select it.
        if name not in self.selected_disks:
            self.selected_disks.append(name)
        # If the disk is already selected, deselect it.
        elif name in self.selected_disks:
            self.selected_disks.remove(name)

    def _update_summary(self):
        """ Update the summary based on the UI. """
        count = 0
        capacity = 0
        free = Size(0)

        # pass in our disk list so hidden disks' free space is available
        free_space = self.storage.get_free_space(disks=self.disks)
        selected = [d for d in self.disks if d.name in self.selected_disks]

        for disk in selected:
            capacity += disk.size
            free += free_space[disk.name][0]
            count += 1

        summary = (P_(("%d disk selected; %s capacity; %s free ..."),
                      ("%d disks selected; %s capacity; %s free ..."), count) %
                   (count, str(Size(capacity)), free))

        if len(self.disks) == 0:
            summary = _(
                "No disks detected.  Please shut down the computer, connect at least one disk, and restart to complete installation."
            )
        elif count == 0:
            summary = (_(
                "No disks selected; please select at least one disk to install to."
            ))

        # Append storage errors to the summary
        if self.errors:
            summary = summary + "\n" + "\n".join(self.errors)
        elif self.warnings:
            summary = summary + "\n" + "\n".join(self.warnings)

        return summary

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        # Join the initialization thread to block on it
        # This print is foul.  Need a better message display
        print(_(PAYLOAD_STATUS_PROBING_STORAGE))
        threadMgr.wait(THREAD_STORAGE_WATCHER)

        if not any(d in self.storage.disks for d in self.disks):
            # something happened to self.storage (probably reset), need to
            # reinitialize the list of disks
            self._initialize(reinit=True)

        # synchronize our local data store with the global ksdata
        # Commment out because there is no way to select a disk right
        # now without putting it in ksdata.  Seems wrong?
        #self.selected_disks = self.data.ignoredisk.onlyuse[:]
        self.autopart = self.data.autopart.autopart

        self._container = ListColumnContainer(1, spacing=1)

        message = self._update_summary()

        # loop through the disks and present them.
        for disk in self.disks:
            disk_info = self._format_disk_info(disk)
            c = CheckboxWidget(title=disk_info,
                               completed=(disk.name in self.selected_disks))
            self._container.add(c, self._update_disk_list_callback, disk)

        # if we have more than one disk, present an option to just
        # select all disks
        if len(self.disks) > 1:
            c = CheckboxWidget(title=_("Select all"),
                               completed=self.select_all)
            self._container.add(c, self._select_all_disks_callback)

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(TextWidget(message))

    def _select_all_disks_callback(self, data):
        """ Mark all disks as selected for use in partitioning. """
        self.select_all = True
        for disk in self.disks:
            if disk.name not in self.selected_disks:
                self._update_disk_list(disk)

    def _update_disk_list_callback(self, data):
        disk = data
        self.select_all = False
        self._update_disk_list(disk)

    def _format_disk_info(self, disk):
        """ Some specialized disks are difficult to identify in the storage
            spoke, so add and return extra identifying information about them.

            Since this is going to be ugly to do within the confines of the
            CheckboxWidget, pre-format the display string right here.
        """
        # show this info for all disks
        format_str = "%s: %s (%s)" % (disk.model, disk.size, disk.name)

        disk_attrs = []
        # now check for/add info about special disks
        if (isinstance(disk, MultipathDevice)
                or isinstance(disk, iScsiDiskDevice)
                or isinstance(disk, FcoeDiskDevice)):
            if hasattr(disk, "wwid"):
                disk_attrs.append(disk.wwid)
        elif isinstance(disk, DASDDevice):
            if hasattr(disk, "busid"):
                disk_attrs.append(disk.busid)
        elif isinstance(disk, ZFCPDiskDevice):
            if hasattr(disk, "fcp_lun"):
                disk_attrs.append(disk.fcp_lun)
            if hasattr(disk, "wwpn"):
                disk_attrs.append(disk.wwpn)
            if hasattr(disk, "hba_id"):
                disk_attrs.append(disk.hba_id)

        # now append all additional attributes to our string
        for attr in disk_attrs:
            format_str += ", %s" % attr

        return format_str

    def input(self, args, key):
        """Grab the disk choice and update things"""
        self.errors = []
        if self._container.process_user_input(key):
            self.redraw()
            return InputState.PROCESSED
        else:
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                if self.selected_disks:
                    # check selected disks to see if we have any unformatted DASDs
                    # if we're on s390x, since they need to be formatted before we
                    # can use them.
                    if arch.is_s390():
                        _disks = [
                            d for d in self.disks
                            if d.name in self.selected_disks
                        ]
                        to_format = [
                            d for d in _disks if d.type == "dasd"
                            and blockdev.s390.dasd_needs_format(d.busid)
                        ]
                        if to_format:
                            self.run_dasdfmt(to_format)
                            self.redraw()
                            return InputState.PROCESSED

                    # make sure no containers were split up by the user's disk
                    # selection
                    self.errors.extend(
                        checkDiskSelection(self.storage, self.selected_disks))
                    if self.errors:
                        # The disk selection has to make sense before we can
                        # proceed.
                        self.redraw()
                        return InputState.PROCESSED

                    self.apply()
                    new_spoke = PartTypeSpoke(self.data, self.storage,
                                              self.payload, self.instclass)
                    ScreenHandler.push_screen_modal(new_spoke)
                    self.apply()
                    self.execute()
                    self.close()

                return InputState.PROCESSED
            else:
                return super(StorageSpoke, self).input(args, key)

    def run_dasdfmt(self, to_format):
        """
        This generates the list of DASDs requiring dasdfmt and runs dasdfmt
        against them.
        """
        # if the storage thread is running, wait on it to complete before taking
        # any further actions on devices; most likely to occur if user has
        # zerombr in their ks file
        threadMgr.wait(THREAD_STORAGE)

        # ask user to verify they want to format if zerombr not in ks file
        if not self.data.zerombr.zerombr:
            # prepare our msg strings; copied directly from dasdfmt.glade
            summary = _(
                "The following unformatted DASDs have been detected on your system. You can choose to format them now with dasdfmt or cancel to leave them unformatted. Unformatted DASDs cannot be used during installation.\n\n"
            )

            warntext = _(
                "Warning: All storage changes made using the installer will be lost when you choose to format.\n\nProceed to run dasdfmt?\n"
            )

            displaytext = summary + "\n".join(
                "/dev/" + d.name for d in to_format) + "\n" + warntext

            # now show actual prompt; note -- in cmdline mode, auto-answer for
            # this is 'no', so unformatted DASDs will remain so unless zerombr
            # is added to the ks file
            question_window = YesNoDialog(displaytext)
            ScreenHandler.push_screen_modal(question_window)
            if not question_window.answer:
                # no? well fine then, back to the storage spoke with you;
                return None

        for disk in to_format:
            try:
                print(
                    _("Formatting /dev/%s. This may take a moment.") %
                    disk.name)
                blockdev.s390.dasd_format(disk.name)
            except blockdev.S390Error as err:
                # Log errors if formatting fails, but don't halt the installer
                log.error(str(err))
                continue

    def apply(self):
        self.autopart = self.data.autopart.autopart
        self.data.ignoredisk.onlyuse = self.selected_disks[:]
        self.data.clearpart.drives = self.selected_disks[:]

        if self.autopart and self.data.autopart.type is None:
            self.data.autopart.type = AUTOPART_TYPE_LVM

        for disk in self.disks:
            if disk.name not in self.selected_disks and \
               disk in self.storage.devices:
                self.storage.devicetree.hide(disk)
            elif disk.name in self.selected_disks and \
                 disk not in self.storage.devices:
                self.storage.devicetree.unhide(disk)

        self.data.bootloader.location = "mbr"

        if self.data.bootloader.bootDrive and \
           self.data.bootloader.bootDrive not in self.selected_disks:
            self.data.bootloader.bootDrive = ""
            self.storage.bootloader.reset()

        self.storage.config.update(self.data)

        # If autopart is selected we want to remove whatever has been
        # created/scheduled to make room for autopart.
        # If custom is selected, we want to leave alone any storage layout the
        # user may have set up before now.
        self.storage.config.clear_non_existent = self.data.autopart.autopart

    def execute(self):
        print(_("Generating updated storage configuration"))
        try:
            doKickstartStorage(self.storage, self.data, self.instclass)
        except (StorageError, KickstartParseError) as e:
            log.error("storage configuration failed: %s", e)
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            self.data.bootloader.bootDrive = ""
            self.data.clearpart.type = CLEARPART_TYPE_ALL
            self.data.clearpart.initAll = False
            self.storage.config.update(self.data)
            self.storage.autopart_type = self.data.autopart.type
            self.storage.reset()
            # now set ksdata back to the user's specified config
            applyDiskSelection(self.storage, self.data, self.selected_disks)
        except BootLoaderError as e:
            log.error("BootLoader setup failed: %s", e)
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            self.data.bootloader.bootDrive = ""
        else:
            print(_("Checking storage configuration..."))
            report = storage_checker.check(self.storage)
            print("\n".join(report.all_errors))
            report.log(log)
            self.errors = report.errors
            self.warnings = report.warnings
        finally:
            resetCustomStorageData(self.data)
            self._ready = True

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        threadMgr.add(
            AnacondaThread(name=THREAD_STORAGE_WATCHER,
                           target=self._initialize))

        self.selected_disks = self.data.ignoredisk.onlyuse[:]
        # Probably need something here to track which disks are selected?

    def _initialize(self, reinit=False):
        """
        Secondary initialize so wait for the storage thread to complete before
        populating our disk list
        """

        threadMgr.wait(THREAD_STORAGE)

        self.disks = sorted(getDisks(self.storage.devicetree),
                            key=lambda d: d.name)
        # if only one disk is available, go ahead and mark it as selected
        if len(self.disks) == 1:
            self._update_disk_list(self.disks[0])

        self._update_summary()

        if not reinit:
            self._ready = True

            # report that the storage spoke has been initialized
            self.initialize_done()
Ejemplo n.º 15
0
class SpecifyNFSRepoSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Specify server and mount opts here if NFS selected. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, instclass, error):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Specify Repo Options")
        self._container = None
        self._error = error

        nfs = self.data.method

        self._nfs_opts = ""
        self._nfs_server = ""

        if nfs.method == "nfs" and (nfs.server and nfs.dir):
            self._nfs_server = "%s:%s" % (nfs.server, nfs.dir)
            self._nfs_opts = nfs.opts

    def refresh(self, args=None):
        """ Refresh window. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=_("SERVER:/PATH"), conditions=[self._check_nfs_server])
        self._container.add(EntryWidget(dialog.title, self._nfs_server), self._set_nfs_server, dialog)

        dialog = Dialog(title=_("NFS mount options"))
        self._container.add(EntryWidget(dialog.title, self._nfs_opts), self._set_nfs_opts, dialog)

        self.window.add_with_separator(self._container)

    def _set_nfs_server(self, dialog):
        self._nfs_server = dialog.run()

    def _check_nfs_server(self, user_input, report_func):
        if ":" not in user_input or len(user_input.split(":")) != 2:
            report_func(_("Server must be specified as SERVER:/PATH"))
            return False

        return True

    def _set_nfs_opts(self, dialog):
        self._nfs_opts = dialog.run()

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return NormalTUISpoke.input(self, args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply our changes. """
        if self._nfs_server == "" or ':' not in self._nfs_server:
            return False

        if self._nfs_server.startswith("nfs://"):
            self._nfs_server = self._nfs_server[6:]

        try:
            (self.data.method.server, self.data.method.dir) = self._nfs_server.split(":", 2)
        except ValueError as err:
            log.error("ValueError: %s", err)
            self._error = True
            return

        opts = self._nfs_opts or ""
        self.set_source_nfs(opts)
Ejemplo n.º 16
0
class SelectISOSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Select an ISO to use as install source. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, instclass, device):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Select an ISO to use as install source")
        self._container = None
        self.args = self.data.method
        self._device = device
        self._mount_device()
        self._isos = self._getISOs()

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        if self._isos:
            self._container = ListColumnContainer(1, columns_width=78, spacing=1)

            for iso in self._isos:
                self._container.add(TextWidget(iso), callback=self._select_iso_callback, data=iso)

            self.window.add_with_separator(self._container)
        else:
            message = _("No *.iso files found in device root folder")
            self.window.add_with_separator(TextWidget(message))

    def _select_iso_callback(self, data):
        self._current_iso_path = data
        self.apply()
        self.close()

    def input(self, args, key):
        if self._container is not None and self._container.process_user_input(key):
            return InputState.PROCESSED
        # TRANSLATORS: 'c' to continue
        elif key.lower() == C_('TUI|Spoke Navigation', 'c'):
            self.apply()
            return InputState.PROCESSED_AND_CLOSE
        else:
            return super().input(args, key)

    @property
    def indirect(self):
        return True

    def _mount_device(self):
        """ Mount the device so we can search it for ISOs. """
        mounts = get_mount_paths(self._device.path)
        # We have to check both ISO_DIR and the DRACUT_ISODIR because we
        # still reference both, even though /mnt/install is a symlink to
        # /run/install.  Finding mount points doesn't handle the symlink
        if ISO_DIR not in mounts and DRACUT_ISODIR not in mounts:
            # We're not mounted to either location, so do the mount
            self._device.format.mount(mountpoint=ISO_DIR)

    def _unmount_device(self):
        self._device.format.unmount()

    def _getISOs(self):
        """List all *.iso files in the root folder
        of the currently selected device.

        TODO: advanced ISO file selection
        :returns: a list of *.iso file paths
        :rtype: list
        """
        isos = []
        for filename in os.listdir(ISO_DIR):
            if fnmatch.fnmatch(filename.lower(), "*.iso"):
                isos.append(filename)
        return isos

    def apply(self):
        """ Apply all of our changes. """

        if self._current_iso_path:
            # If a hdd iso source has already been selected previously we need
            # to clear it now.
            # Otherwise we would get a crash if the same iso was selected again
            # as _unmount_device() would try to unmount a partition that is in use
            # due to the payload still holding on to the ISO file.
            if self.data.method.method == "harddrive":
                self.unset_source()
            self.set_source_hdd_iso(self._device, self._current_iso_path)
        # unmount the device - the payload will remount it anyway
        # (if it uses it)
        self._unmount_device()
Ejemplo n.º 17
0
class SoftwareSpoke(NormalTUISpoke):
    """ Spoke used to read new value of text to represent source repo.

       .. inheritance-diagram:: SoftwareSpoke
          :parts: 3
    """
    helpFile = "SoftwareSpoke.txt"
    category = SoftwareCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Software selection")
        self._container = None
        self.errors = []
        self._tx_id = None
        self._selected_environment = None
        self.environment = None
        self._addons_selection = set()
        self.addons = set()

        # for detecting later whether any changes have been made
        self._origEnv = None
        self._origAddons = set()

        # are we taking values (package list) from a kickstart file?
        self._kickstarted = flags.automatedInstall and self.data.packages.seen

        # Register event listeners to update our status on payload events
        payloadMgr.addListener(payloadMgr.STATE_START, self._payload_start)
        payloadMgr.addListener(payloadMgr.STATE_FINISHED, self._payload_finished)
        payloadMgr.addListener(payloadMgr.STATE_ERROR, self._payload_error)

    def initialize(self):
        # Start a thread to wait for the payload and run the first, automatic
        # dependency check
        self.initialize_start()
        super(SoftwareSpoke, self).initialize()
        threadMgr.add(AnacondaThread(name=THREAD_SOFTWARE_WATCHER,
                                     target=self._initialize))

    def _initialize(self):
        threadMgr.wait(THREAD_PAYLOAD)

        if not self._kickstarted:
            # If an environment was specified in the instclass, use that.
            # Otherwise, select the first environment.
            if self.payload.environments:
                environments = self.payload.environments
                instclass = self.payload.instclass

                if instclass and instclass.defaultPackageEnvironment and \
                        instclass.defaultPackageEnvironment in environments:
                    self._selected_environment = environments.index(instclass.defaultPackageEnvironment)
                else:
                    self._selected_environment = 0

        # Apply the initial selection
        self._apply()

        # Wait for the software selection thread that might be started by _apply().
        # We are already running in a thread, so it should not needlessly block anything
        # and only like this we can be sure we are really initialized.
        threadMgr.wait(THREAD_CHECK_SOFTWARE)

        # report that the software spoke has been initialized
        self.initialize_done()

    def _payload_start(self):
        # Source is changing, invalidate the software selection and clear the
        # errors
        self._selected_environment = None
        self._addons_selection = set()
        self.errors = []

    def _payload_finished(self):
        self.environment = self.data.packages.environment
        self.addons = self._get_selected_addons()

    def _payload_error(self):
        self.errors = [payloadMgr.error]

    def _translate_env_selection_to_name(self, selection):
        """ Return the selected environment name or None.
            Selection can be None during kickstart installation.
        """
        if selection is not None:
            return self.payload.environments[selection]
        else:
            return None

    def _translate_env_name_to_id(self, environment):
        """ Return the id of the selected environment or None. """
        if environment is None:
            return None
        try:
            return self.payload.environmentId(environment)
        except NoSuchGroup:
            return None

    def _get_available_addons(self, environment_id):
        """ Return all add-ons of the specific environment. """
        addons = []

        if environment_id in self.payload.environmentAddons:
            for addons_list in self.payload.environmentAddons[environment_id]:
                addons.extend(addons_list)

        return addons

    def _get_selected_addons(self):
        """ Return selected add-ons. """
        return {group.name for group in self.payload.data.packages.groupList}

    @property
    def showable(self):
        return isinstance(self.payload, PackagePayload)

    @property
    def status(self):
        """ Where we are in the process """
        if self.errors:
            return _("Error checking software selection")
        if not self.ready:
            return _("Processing...")
        if not self.payload.baseRepo:
            return _("Installation source not set up")
        if not self.txid_valid:
            return _("Source changed - please verify")

        if not self.environment:
            # Ks installs with %packages will have an env selected, unless
            # they did an install without a desktop environment. This should
            # catch that one case.
            if self._kickstarted:
                return _("Custom software selected")
            return _("Nothing selected")

        return self.payload.environmentDescription(self.environment)[0]

    @property
    def completed(self):
        """ Make sure our threads are done running and vars are set.

           WARNING: This can be called before the spoke is finished initializing
           if the spoke starts a thread. It should make sure it doesn't access
           things until they are completely setup.
        """
        processing_done = self.ready and not self.errors and self.txid_valid

        if flags.automatedInstall or self._kickstarted:
            return processing_done and self.payload.baseRepo and self.data.packages.seen
        else:
            return processing_done and self.payload.baseRepo and self.environment is not None

    def refresh(self, args=None):
        """ Refresh screen. """
        NormalTUISpoke.refresh(self, args)

        threadMgr.wait(THREAD_PAYLOAD)
        self._container = None

        if not self.payload.baseRepo:
            message = TextWidget(_("Installation source needs to be set up first."))
            self.window.add_with_separator(message)
            return

        threadMgr.wait(THREAD_CHECK_SOFTWARE)
        self._container = ListColumnContainer(2, columns_width=38, spacing=2)

        # Display the environments
        if args is None:
            environments = self.payload.environments
            msg = _("Base environment")

            for env in environments:
                name = self.payload.environmentDescription(env)[0]
                selected = (env == self._selected_environment)
                widget = CheckboxWidget(title="%s" % name, completed=selected)
                self._container.add(widget, callback=self._set_environment_callback, data=env)

        # Display the add-ons
        else:
            length = len(args)

            if length > 0:
                msg = _("Add-ons for selected environment")
            else:
                msg = _("No add-ons to select.")

            for addon_id in args:
                name = self.payload.groupDescription(addon_id)[0]
                selected = addon_id in self._addons_selection
                widget = CheckboxWidget(title="%s" % name, completed=selected)
                self._container.add(widget, callback=self._set_addons_callback, data=addon_id)

        self.window.add_with_separator(TextWidget(msg))
        self.window.add_with_separator(self._container)

    def _set_environment_callback(self, data):
        self._selected_environment = data

    def _set_addons_callback(self, data):
        addon = data
        if addon not in self._addons_selection:
            self._addons_selection.add(addon)
        else:
            self._addons_selection.remove(addon)

    def input(self, args, key):
        """ Handle the input; this chooses the desktop environment. """
        if self._container is not None and self._container.process_user_input(key):
            self.redraw()
        else:
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):

                # No environment was selected, close
                if self._selected_environment is None:
                    self.close()

                # The environment was selected, switch screen
                elif args is None:
                    # Get addons for the selected environment
                    environment = self._translate_env_selection_to_name(self._selected_environment)
                    environment_id = self._translate_env_name_to_id(environment)
                    addons = self._get_available_addons(environment_id)

                    # Switch the screen
                    ScreenHandler.replace_screen(self, addons)

                # The addons were selected, apply and close
                else:
                    self.apply()
                    self.close()

                return InputState.PROCESSED
            else:
                return super(SoftwareSpoke, self).input(args, key)

        return InputState.PROCESSED

    @property
    def ready(self):
        """ If we're ready to move on. """
        return (not threadMgr.get(THREAD_PAYLOAD) and
                not threadMgr.get(THREAD_CHECK_SOFTWARE) and
                not threadMgr.get(THREAD_SOFTWARE_WATCHER))

    def apply(self):
        """ Apply our selections """
        # no longer using values from kickstart
        self._kickstarted = False
        self.data.packages.seen = True
        # _apply depends on a value of _kickstarted
        self._apply()

    def _apply(self):
        """ Private apply. """
        self.environment = self._translate_env_selection_to_name(self._selected_environment)
        self.addons = self._addons_selection if self.environment is not None else set()

        if self.environment is None:
            return

        changed = False

        # Not a kickstart with packages, setup the selected environment and addons
        if not self._kickstarted:

            # Changed the environment or addons, clear and setup
            if not self._origEnv \
                    or self._origEnv != self.environment \
                    or set(self._origAddons) != set(self.addons):

                self.payload.data.packages.packageList = []
                self.data.packages.groupList = []
                self.payload.selectEnvironment(self.environment)

                environment_id = self._translate_env_name_to_id(self.environment)
                available_addons = self._get_available_addons(environment_id)

                for addon_id in available_addons:
                    if addon_id in self.addons:
                        self.payload.selectGroup(addon_id)

                changed = True

            self._origEnv = self.environment
            self._origAddons = set(self.addons)

        # Check the software selection
        if changed or self._kickstarted:
            threadMgr.add(AnacondaThread(name=THREAD_CHECK_SOFTWARE,
                                         target=self.checkSoftwareSelection))

    def checkSoftwareSelection(self):
        """ Depsolving """
        try:
            self.payload.checkSoftwareSelection()
        except DependencyError as e:
            self.errors = [str(e)]
            self._tx_id = None
        else:
            self._tx_id = self.payload.txID

    @property
    def txid_valid(self):
        """ Whether we have a valid dnf tx id. """
        return self._tx_id == self.payload.txID
Ejemplo n.º 18
0
class NetworkSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """ Spoke used to configure network settings.

       .. inheritance-diagram:: NetworkSpoke
          :parts: 3
    """
    helpFile = "NetworkSpoke.txt"
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Network configuration")
        self._network_module = NETWORK.get_observer()
        self._network_module.connect()
        self._container = None
        self._value = self._network_module.proxy.Hostname
        self.supported_devices = []
        self.errors = []
        self._apply = False

    def initialize(self):
        self.initialize_start()
        self._load_new_devices()

        NormalTUISpoke.initialize(self)
        if not self._network_module.proxy.Kickstarted:
            self._update_network_data()
        self.initialize_done()

    def _load_new_devices(self):
        devices = nm.nm_devices()
        intf_dumped = network.dumpMissingDefaultIfcfgs()
        if intf_dumped:
            log.debug("dumped interfaces: %s", intf_dumped)

        for name in devices:
            if name in self.supported_devices:
                continue
            if network.is_ibft_configured_device(name):
                continue
            if network.device_type_is_supported_wired(name):
                # ignore slaves
                try:
                    if nm.nm_device_setting_value(name, "connection",
                                                  "slave-type"):
                        continue
                except nm.MultipleSettingsFoundError as e:
                    log.debug("%s during initialization", e)
                self.supported_devices.append(name)

    @property
    def completed(self):
        """ Check whether this spoke is complete or not."""
        # If we can't configure network, don't require it
        return (not conf.system.can_configure_network
                or nm.nm_activated_devices())

    @property
    def mandatory(self):
        # the network spoke should be mandatory only if it is running
        # during the installation and if the installation source requires network
        return ANACONDA_ENVIRON in flags.environs and self.payload.needsNetwork

    @property
    def status(self):
        """ Short msg telling what devices are active. """
        return network.status_message()

    def _summary_text(self):
        """Devices cofiguration shown to user."""
        msg = ""
        activated_devs = nm.nm_activated_devices()
        for name in self.supported_devices:
            if name in activated_devs:
                msg += self._activated_device_msg(name)
            else:
                msg += _("Wired (%(interface_name)s) disconnected\n") \
                       % {"interface_name": name}
        return msg

    def _activated_device_msg(self, devname):
        msg = _("Wired (%(interface_name)s) connected\n") \
              % {"interface_name": devname}

        ipv4config = nm.nm_device_ip_config(devname, version=4)
        ipv6config = nm.nm_device_ip_config(devname, version=6)

        if ipv4config and ipv4config[0]:
            addr_str, prefix, gateway_str = ipv4config[0][0]
            netmask_str = network.prefix2netmask(prefix)
            dnss_str = ",".join(ipv4config[1])
        else:
            addr_str = dnss_str = gateway_str = netmask_str = ""

        msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
               {"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
        msg += _(" DNS: %s\n") % dnss_str

        if ipv6config and ipv6config[0]:
            for ipv6addr in ipv6config[0]:
                addr_str, prefix, gateway_str = ipv6addr
                # Do not display link-local addresses
                if not addr_str.startswith("fe80:"):
                    msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
                           {"addr": addr_str, "prefix": prefix}

        return msg

    def refresh(self, args=None):
        """ Refresh screen. """
        self._load_new_devices()
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))
        hostname = _("Host Name: %s\n") % self._network_module.proxy.Hostname
        self.window.add_with_separator(TextWidget(hostname))
        current_hostname = _(
            "Current host name: %s\n"
        ) % self._network_module.proxy.GetCurrentHostname()
        self.window.add_with_separator(TextWidget(current_hostname))

        # if we have any errors, display them
        while len(self.errors) > 0:
            self.window.add_with_separator(TextWidget(self.errors.pop()))

        dialog = Dialog(_("Host Name"))
        self._container.add(TextWidget(_("Set host name")),
                            callback=self._set_hostname_callback,
                            data=dialog)

        for dev_name in self.supported_devices:
            text = (_("Configure device %s") % dev_name)
            self._container.add(TextWidget(text),
                                callback=self._configure_network_interface,
                                data=dev_name)

        self.window.add_with_separator(self._container)

    def _set_hostname_callback(self, dialog):
        # set hostname
        self._value = dialog.run()
        self.redraw()
        self.apply()

    def _configure_network_interface(self, data):
        devname = data
        ndata = network.ksdata_from_ifcfg(devname)
        if not ndata:
            # There is no ifcfg file for the device.
            # Make sure there is just one connection for the device.
            try:
                nm.nm_device_setting_value(devname, "connection", "uuid")
            except nm.SettingsNotFoundError:
                log.debug("can't find any connection for %s", devname)
                return
            except nm.MultipleSettingsFoundError:
                log.debug("multiple non-ifcfg connections found for %s",
                          devname)
                return

            log.debug("dumping ifcfg file for in-memory connection %s",
                      devname)
            nm.nm_update_settings_of_device(
                devname, [['connection', 'id', devname, None]])
            ndata = network.ksdata_from_ifcfg(devname)

        new_spoke = ConfigureNetworkSpoke(self.data, self.storage,
                                          self.payload, self.instclass, ndata)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

        if ndata.ip == "dhcp":
            ndata.bootProto = "dhcp"
            ndata.ip = ""
        else:
            ndata.bootProto = "static"
            if not ndata.netmask:
                self.errors.append(
                    _("Configuration not saved: netmask missing in static configuration"
                      ))
                return

        if ndata.ipv6 == "ignore":
            ndata.noipv6 = True
            ndata.ipv6 = ""
        else:
            ndata.noipv6 = False

        uuid = network.update_settings_with_ksdata(devname, ndata)
        network.update_onboot_value(devname,
                                    ndata.onboot,
                                    ksdata=None,
                                    root_path="")
        network.logIfcfgFiles("settings of %s updated in tui" % devname)

        if new_spoke.apply_configuration:
            self._apply = True
            try:
                nm.nm_activate_device_connection(devname, uuid)
            except (nm.UnmanagedDeviceError, nm.UnknownConnectionError):
                self.errors.append(
                    _("Can't apply configuration, device activation failed."))

        self.apply()

    def input(self, args, key):
        """ Handle the input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        """Apply all of our settings."""
        self._update_network_data()
        log.debug("apply ksdata %s", self.data.network)

        if self._apply:
            self._apply = False
            if ANACONDA_ENVIRON in flags.environs:
                from pyanaconda.payload import payloadMgr
                payloadMgr.restartThread(self.storage,
                                         self.data,
                                         self.payload,
                                         self.instclass,
                                         checkmount=False)

    def _update_network_data(self):
        hostname = self._network_module.proxy.Hostname

        self.data.network.network = []
        for i, name in enumerate(nm.nm_devices()):
            if network.is_ibft_configured_device(name):
                continue
            nd = network.ksdata_from_ifcfg(name)
            if not nd:
                continue
            if name in nm.nm_activated_devices():
                nd.activate = True
            else:
                # First network command defaults to --activate so we must
                # use --no-activate explicitly to prevent the default
                if i == 0:
                    nd.activate = False
            self.data.network.network.append(nd)

        (valid, error) = network.sanityCheckHostname(self._value)
        if valid:
            hostname = self._value
        else:
            self.errors.append(_("Host name is not valid: %s") % error)
            self._value = hostname
        network.update_hostname_data(self.data, hostname)
Ejemplo n.º 19
0
class AskVNCSpoke(NormalTUISpoke):
    """
       .. inheritance-diagram:: AskVNCSpoke
          :parts: 3
    """
    title = N_("VNC")

    # This spoke is kinda standalone, not meant to be used with a hub
    # We pass in some fake data just to make our parents happy
    def __init__(self, data, storage=None, payload=None, instclass=None, message=""):
        super().__init__(data, storage, payload, instclass)
        self.input_required = True
        self.initialize_start()
        self._container = None

        # The TUI hasn't been initialized with the message handlers yet. Add an
        # exception message handler so that the TUI exits if anything goes wrong
        # at this stage.
        loop = App.get_event_loop()
        loop.register_signal_handler(ExceptionSignal, exception_msg_handler_and_exit)
        self._message = message
        self._usevnc = False
        self.initialize_done()

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)

        self.window.add_with_separator(TextWidget(self._message))

        self._container = ListColumnContainer(1, spacing=1)

        # choices are
        # USE VNC
        self._container.add(TextWidget(_(USEVNC)), self._use_vnc_callback)
        # USE TEXT
        self._container.add(TextWidget(_(USETEXT)), self._use_text_callback)

        self.window.add_with_separator(self._container)

    def _use_vnc_callback(self, data):
        self._usevnc = True
        new_spoke = VNCPassSpoke(self.data, self.storage,
                                 self.payload, self.instclass)
        ScreenHandler.push_screen_modal(new_spoke)

    def _use_text_callback(self, data):
        self._usevnc = False

    def input(self, args, key):
        """Override input so that we can launch the VNC password spoke"""
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_CLOSE
        else:
            # TRANSLATORS: 'q' to quit
            if key.lower() == C_('TUI|Spoke Navigation', 'q'):
                d = YesNoDialog(_(u"Do you really want to quit?"))
                ScreenHandler.push_screen_modal(d)
                if d.answer:
                    ipmi_abort(scripts=self.data.scripts)
                    if conf.system.can_reboot:
                        execWithRedirect("systemctl", ["--no-wall", "reboot"])
                    else:
                        sys.exit(1)
            else:
                return super().input(args, key)

    def apply(self):
        self.data.vnc.enabled = self._usevnc
Ejemplo n.º 20
0
class RescueModeSpoke(NormalTUISpoke):
    """UI offering mounting existing installation roots in rescue mode."""

    # If it acts like a spoke and looks like a spoke, is it a spoke? Not
    # always. This is independent of any hub(s), so pass in some fake data
    def __init__(self, rescue):
        super().__init__(data=None, storage=None, payload=None)
        self.title = N_("Rescue")
        self._container = None
        self._rescue = rescue

    def refresh(self, args=None):
        super().refresh(args)

        msg = _("The rescue environment will now attempt "
                "to find your Linux installation and mount it under "
                "the directory : %s.  You can then make any changes "
                "required to your system.  Choose '1' to proceed with "
                "this step.\nYou can choose to mount your file "
                "systems read-only instead of read-write by choosing "
                "'2'.\nIf for some reason this process does not work "
                "choose '3' to skip directly to a shell.\n\n") % (util.getSysroot())
        self.window.add_with_separator(TextWidget(msg))

        self._container = ListColumnContainer(1)

        self._container.add(TextWidget(_("Continue")), self._read_write_mount_callback)
        self._container.add(TextWidget(_("Read-only mount")), self._read_only_mount_callback)
        self._container.add(TextWidget(_("Skip to shell")), self._skip_to_shell_callback)
        self._container.add(TextWidget(_("Quit (Reboot)")), self._quit_callback)

        self.window.add_with_separator(self._container)

    def _read_write_mount_callback(self, data):
        self._mount_and_prompt_for_shell()

    def _read_only_mount_callback(self, data):
        self._rescue.ro = True
        self._mount_and_prompt_for_shell()

    def _skip_to_shell_callback(self, data):
        self._show_result_and_prompt_for_shell()

    def _quit_callback(self, data):
        d = YesNoDialog(_(QUIT_MESSAGE))
        ScreenHandler.push_screen_modal(d)
        self.redraw()
        if d.answer:
            self._rescue.reboot = True
            self._rescue.finish()

    def _mount_and_prompt_for_shell(self):
        self._rescue.mount = True
        self._mount_root()
        self._show_result_and_prompt_for_shell()

    def prompt(self, args=None):
        """ Override the default TUI prompt."""
        if self._rescue.automated:
            if self._rescue.mount:
                self._mount_root()
            self._show_result_and_prompt_for_shell()
            return None
        return Prompt()

    def input(self, args, key):
        """Override any input so we can launch rescue mode."""
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return InputState.DISCARDED

    def _mount_root(self):
        # decrypt all luks devices
        self._unlock_devices()
        roots = self._rescue.find_roots()

        if not roots:
            return

        if len(roots) == 1:
            root = roots[0]
        else:
            # have to prompt user for which root to mount
            root_spoke = RootSelectionSpoke(roots)
            ScreenHandler.push_screen_modal(root_spoke)
            self.redraw()

            root = root_spoke.selection

        self._rescue.mount_root(root)

    def _show_result_and_prompt_for_shell(self):
        new_spoke = RescueStatusAndShellSpoke(self._rescue)
        ScreenHandler.push_screen_modal(new_spoke)
        self.close()

    def _unlock_devices(self):
        """Attempt to unlock all locked LUKS devices.

        Returns true if all devices were unlocked.
        """
        try_passphrase = None
        passphrase = None
        for device_name in self._rescue.get_locked_device_names():
            skip = False
            unlocked = False
            while not (skip or unlocked):
                if try_passphrase is None:
                    p = PasswordDialog(device_name)
                    ScreenHandler.push_screen_modal(p)
                    if p.answer:
                        passphrase = p.answer.strip()
                else:
                    passphrase = try_passphrase

                if passphrase is None:
                    # cancelled
                    skip = True
                else:
                    unlocked = self._rescue.unlock_device(device_name, passphrase)
                    try_passphrase = passphrase if unlocked else None

        return not self._rescue.get_locked_device_names()

    def apply(self):
        """Move along home."""
        pass
Ejemplo n.º 21
0
class StorageSpoke(NormalTUISpoke):
    """Storage spoke where users proceed to customize storage features such
       as disk selection, partitioning, and fs type.

       .. inheritance-diagram:: StorageSpoke
          :parts: 3
    """
    helpFile = "StorageSpoke.txt"
    category = SystemCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Installation Destination")
        self._container = None

        self._bootloader_observer = STORAGE.get_observer(BOOTLOADER)
        self._bootloader_observer.connect()

        self._disk_init_observer = STORAGE.get_observer(DISK_INITIALIZATION)
        self._disk_init_observer.connect()

        self._disk_select_observer = STORAGE.get_observer(DISK_SELECTION)
        self._disk_select_observer.connect()

        self._auto_part_observer = STORAGE.get_observer(AUTO_PARTITIONING)
        self._auto_part_observer.connect()

        self._selected_disks = self._disk_select_observer.proxy.SelectedDisks

        # This list gets set up once in initialize and should not be modified
        # except perhaps to add advanced devices. It will remain the full list
        # of disks that can be included in the install.
        self._available_disks = []

        if not flags.automatedInstall:
            # default to using autopart for interactive installs
            self._auto_part_observer.proxy.SetEnabled(True)

        self._ready = False
        self._select_all = False
        self._auto_part_enabled = None

        self.errors = []
        self.warnings = []

    @property
    def completed(self):
        return bool(self.storage.root_device and not self.errors)

    @property
    def ready(self):
        # By default, the storage spoke is not ready.  We have to wait until
        # storageInitialize is done.
        return self._ready and not threadMgr.get(THREAD_STORAGE_WATCHER)

    @property
    def mandatory(self):
        return True

    @property
    def showable(self):
        return not conf.target.is_directory

    @property
    def status(self):
        """ A short string describing the current status of storage setup. """
        if flags.automatedInstall and not self.storage.root_device:
            return _("Kickstart insufficient")
        elif not self._disk_select_observer.proxy.SelectedDisks:
            return _("No disks selected")
        if self.errors:
            return _("Error checking storage configuration")
        elif self.warnings:
            return _("Warning checking storage configuration")
        elif self._auto_part_observer.proxy.Enabled:
            return _("Automatic partitioning selected")
        else:
            return _("Custom partitioning selected")

    def _update_disk_list(self, disk):
        """ Update self.selected_disks based on the selection."""
        name = disk.name

        # if the disk isn't already selected, select it.
        if name not in self._selected_disks:
            self._selected_disks.append(name)
        # If the disk is already selected, deselect it.
        elif name in self._selected_disks:
            self._selected_disks.remove(name)

    def _update_summary(self):
        """ Update the summary based on the UI. """
        # Get the summary message.
        if not self._available_disks:
            summary = _(WARNING_NO_DISKS_DETECTED)
        elif not self._selected_disks:
            summary = _(WARNING_NO_DISKS_SELECTED)
        else:
            disks = filter_disks_by_names(self._available_disks, self._selected_disks)
            summary = get_disks_summary(self.storage, disks)

        # Append storage errors to the summary
        if self.errors or self.warnings:
            summary = summary + "\n" + "\n".join(self.errors or self.warnings)

        return summary

    def refresh(self, args=None):
        super().refresh(args)

        # Join the initialization thread to block on it
        # This print is foul.  Need a better message display
        print(_(PAYLOAD_STATUS_PROBING_STORAGE))
        threadMgr.wait(THREAD_STORAGE_WATCHER)

        if not any(d in self.storage.disks for d in self._available_disks):
            # something happened to self.storage (probably reset), need to
            # reinitialize the list of disks
            self.update_disks()

        # synchronize our local data store with the global ksdata
        # Commment out because there is no way to select a disk right
        # now without putting it in ksdata.  Seems wrong?
        # self.selected_disks = self.data.ignoredisk.onlyuse[:]
        self._auto_part_enabled = self._auto_part_observer.proxy.Enabled

        self._container = ListColumnContainer(1, spacing=1)

        message = self._update_summary()

        # loop through the disks and present them.
        for disk in self._available_disks:
            disk_info = self._format_disk_info(disk)
            c = CheckboxWidget(title=disk_info, completed=(disk.name in self._selected_disks))
            self._container.add(c, self._update_disk_list_callback, disk)

        # if we have more than one disk, present an option to just
        # select all disks
        if len(self._available_disks) > 1:
            c = CheckboxWidget(title=_("Select all"), completed=self._select_all)
            self._container.add(c, self._select_all_disks_callback)

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(TextWidget(message))

    def _select_all_disks_callback(self, data):
        """ Mark all disks as selected for use in partitioning. """
        self._select_all = True
        for disk in self._available_disks:
            if disk.name not in self._selected_disks:
                self._update_disk_list(disk)

    def _update_disk_list_callback(self, data):
        disk = data
        self._select_all = False
        self._update_disk_list(disk)

    def _format_disk_info(self, disk):
        """ Some specialized disks are difficult to identify in the storage
            spoke, so add and return extra identifying information about them.

            Since this is going to be ugly to do within the confines of the
            CheckboxWidget, pre-format the display string right here.
        """
        # show this info for all disks
        format_str = "%s: %s (%s)" % (disk.model, disk.size, disk.name)

        disk_attrs = []
        # now check for/add info about special disks
        if (isinstance(disk, MultipathDevice) or
                isinstance(disk, iScsiDiskDevice) or
                isinstance(disk, FcoeDiskDevice)):
            if hasattr(disk, "wwn"):
                disk_attrs.append(disk.wwn)
        elif isinstance(disk, DASDDevice):
            if hasattr(disk, "busid"):
                disk_attrs.append(disk.busid)
        elif isinstance(disk, ZFCPDiskDevice):
            if hasattr(disk, "fcp_lun"):
                disk_attrs.append(disk.fcp_lun)
            if hasattr(disk, "wwpn"):
                disk_attrs.append(disk.wwpn)
            if hasattr(disk, "hba_id"):
                disk_attrs.append(disk.hba_id)

        # now append all additional attributes to our string
        for attr in disk_attrs:
            format_str += ", %s" % attr

        return format_str

    def input(self, args, key):
        """Grab the disk choice and update things"""
        self.errors = []
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_REDRAW
        else:
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                if self._selected_disks:
                    # Is DASD formatting supported?
                    if DasdFormatting.is_supported():
                        # Wait for storage.
                        threadMgr.wait(THREAD_STORAGE)

                        # Get selected disks.
                        disks = filter_disks_by_names(self._available_disks, self._selected_disks)

                        # Check if some of the disks should be formatted.
                        dasd_formatting = DasdFormatting()
                        dasd_formatting.search_disks(disks)

                        if dasd_formatting.should_run():
                            # We want to apply current selection before running dasdfmt to
                            # prevent this information from being lost afterward
                            apply_disk_selection(self.storage, self._selected_disks)

                            # Run the dialog.
                            self.run_dasdfmt_dialog(dasd_formatting)
                            return InputState.PROCESSED_AND_REDRAW

                    # make sure no containers were split up by the user's disk
                    # selection
                    self.errors.extend(check_disk_selection(self.storage,
                                                            self._selected_disks))
                    if self.errors:
                        # The disk selection has to make sense before we can
                        # proceed.
                        return InputState.PROCESSED_AND_REDRAW

                    self.apply()
                    new_spoke = PartTypeSpoke(self.data, self.storage, self.payload)
                    ScreenHandler.push_screen_modal(new_spoke)
                    self.apply()
                    self.execute()

                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

    def run_dasdfmt_dialog(self, dasd_formatting):
        """Do DASD formatting if user agrees."""
        # Prepare text of the dialog.
        text = ""
        text += _("The following unformatted or LDL DASDs have been "
                  "detected on your system. You can choose to format them "
                  "now with dasdfmt or cancel to leave them unformatted. "
                  "Unformatted DASDs cannot be used during installation.\n\n")

        text += dasd_formatting.dasds_summary + "\n\n"

        text += _("Warning: All storage changes made using the installer will "
                  "be lost when you choose to format.\n\nProceed to run dasdfmt?\n")

        # Run the dialog.
        question_window = YesNoDialog(text)
        ScreenHandler.push_screen_modal(question_window)
        if not question_window.answer:
            return None

        print(_("This may take a moment."), flush=True)

        # Do the DASD formatting.
        dasd_formatting.report.connect(self._show_dasdfmt_report)
        dasd_formatting.run(self.storage, self.data)
        dasd_formatting.report.disconnect(self._show_dasdfmt_report)

        self.update_disks()

    def _show_dasdfmt_report(self, msg):
        print(msg, flush=True)

    def run_passphrase_dialog(self):
        """Ask user for a default passphrase."""
        data_without_passphrase = self._get_data_without_passphrase()
        if not data_without_passphrase:
            return

        dialog = PasswordDialog(
            title=_("Passphrase"),
            message=_("Please provide a default LUKS passphrase for all devices "
                      "you want to encrypt. You will have to type it twice."),
            secret_type=SecretType.PASSPHRASE,
            policy=get_policy(self.data, "luks"),
            process_func=lambda x: x
        )

        passphrase = None
        while passphrase is None:
            passphrase = dialog.run()

        self._set_data_without_passphrase(data_without_passphrase, passphrase)

    def _get_data_without_passphrase(self):
        """Collect kickstart data and DBus proxies that require a passphrase."""
        result = []

        if self._auto_part_observer.proxy.Encrypted \
                and not self._auto_part_observer.proxy.Passphrase:
            result.append(self._auto_part_observer.proxy)

        for data in self.data.partition.dataList():
            if data.encrypted and not data.passphrase:
                result.append(data)

        for data in self.data.logvol.dataList():
            if data.encrypted and not data.passphrase:
                result.append(data)

        for data in self.data.raid.dataList():
            if data.encrypted and not data.passphrase:
                result.append(data)

        return result

    def _set_data_without_passphrase(self, data_without_passphrase, passphrase):
        """Set a passphrase to the collected kickstart data and DBus proxies."""
        for data in data_without_passphrase:
            if isinstance(data, BaseData):
                data.passphrase = passphrase
            else:
                data.SetPassphrase(passphrase)

    def apply(self):
        self._auto_part_enabled = self._auto_part_observer.proxy.Enabled

        self.storage.select_disks(self._selected_disks)

        self._bootloader_observer.proxy.SetPreferredLocation(BOOTLOADER_LOCATION_MBR)
        boot_drive = self._bootloader_observer.proxy.Drive

        if boot_drive and boot_drive not in self._selected_disks:
            reset_bootloader(self.storage)

        apply_disk_selection(self.storage, self._selected_disks)

    def execute(self):
        print(_("Generating updated storage configuration"))
        try:
            configure_storage(self.storage, self.data)
        except StorageConfigurationError as e:
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            reset_bootloader(self.storage)
            reset_storage(self.storage, scan_all=True)
        except BootloaderConfigurationError as e:
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            reset_bootloader(self.storage)
        else:
            print(_("Checking storage configuration..."))
            report = storage_checker.check(self.storage)
            print("\n".join(report.all_errors))
            report.log(log)
            self.errors = report.errors
            self.warnings = report.warnings
        finally:
            reset_custom_storage_data(self.data)
            self._ready = True

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        # Ask for a default passphrase.
        if flags.automatedInstall and flags.ksprompt:
            self.run_passphrase_dialog()

        threadMgr.add(AnacondaThread(name=THREAD_STORAGE_WATCHER,
                                     target=self._initialize))

        self._selected_disks = self._disk_select_observer.proxy.SelectedDisks
        # Probably need something here to track which disks are selected?

    def _initialize(self):
        """
        Secondary initialize so wait for the storage thread to complete before
        populating our disk list
        """
        # Wait for storage.
        threadMgr.wait(THREAD_STORAGE)

        # Automatically format DASDs if allowed.
        DasdFormatting.run_automatically(self.storage, self.data)

        # Update the selected disks.
        if flags.automatedInstall:
            self._selected_disks = select_all_disks_by_default(self.storage)

        # Update disk list.
        self.update_disks()

        # Storage is ready.
        self._ready = True

        # Report that the storage spoke has been initialized.
        self.initialize_done()

    def update_disks(self):
        threadMgr.wait(THREAD_STORAGE)
        self._available_disks = self.storage.usable_disks

        # if only one disk is available, go ahead and mark it as selected
        if len(self._available_disks) == 1:
            self._update_disk_list(self._available_disks[0])
Ejemplo n.º 22
0
class MountPointAssignSpoke(NormalTUISpoke):
    """ Assign mount points to block devices. """
    category = SystemCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Assign mount points")
        self._container = None

        self._disk_select_proxy = STORAGE.get_proxy(DISK_SELECTION)
        self._manual_part_proxy = STORAGE.get_proxy(MANUAL_PARTITIONING)
        self._mount_info = self._gather_mount_info()

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        """Refresh the window."""
        super().refresh(args)
        self._container = ListColumnContainer(2)

        for info in self._mount_info:
            widget = TextWidget(self._get_mount_info_description(info))
            self._container.add(widget, self._configure_mount_info, info)

        message = _(
            "Choose device from above to assign mount point and set format.\n"
            "Formats marked with * are new formats meaning ALL DATA on the "
            "original format WILL BE LOST!"
        )

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(TextWidget(message))

    def prompt(self, args=None):
        prompt = super().prompt(args)

        # TRANSLATORS: 's' to rescan devices
        prompt.add_option(C_('TUI|Spoke Navigation|Partitioning', 's'), _("rescan devices"))

        return prompt

    def input(self, args, key):
        """ Grab the choice and update things. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED

        # TRANSLATORS: 's' to rescan devices
        if key.lower() == C_('TUI|Spoke Navigation|Partitioning', 's'):
            self._rescan_devices()
            return InputState.PROCESSED_AND_REDRAW

        # TRANSLATORS: 'c' to continue
        elif key.lower() == C_('TUI|Spoke Navigation', 'c'):
            self.apply()

        return super().input(args, key)

    def apply(self):
        """ Apply our selections. """
        mount_points = []

        for _device, data in self._mount_info:
            if data[MOUNT_POINT_REFORMAT] or data[MOUNT_POINT_PATH]:
                mount_points.append({
                    MOUNT_POINT_PATH: get_variant(Str, data[MOUNT_POINT_PATH] or "none"),
                    MOUNT_POINT_DEVICE: get_variant(Str, data[MOUNT_POINT_DEVICE]),
                    MOUNT_POINT_REFORMAT: get_variant(Bool, data[MOUNT_POINT_REFORMAT]),
                    MOUNT_POINT_FORMAT: get_variant(Str, data[MOUNT_POINT_FORMAT])
                })

        self._manual_part_proxy.SetMountPoints(mount_points)

    def _gather_mount_info(self):
        """Gather info about mount points."""
        selected_disks = self._disk_select_proxy.SelectedDisks
        mount_points = self._manual_part_proxy.MountPoints

        mount_info = []

        for device in self.storage.devicetree.leaves:
            # Is the device usable?
            if device.protected or device.size == Size(0):
                continue

            # All device's disks have to be in selected disks.
            device_disks = {d.name for d in device.disks}
            if selected_disks and not set(selected_disks).issuperset(device_disks):
                continue

            # Append new info about this device.
            data = self._get_mount_point_data(device, mount_points)
            mount_info.append((device, data))

            # Use the data only once.
            if data in mount_points:
                mount_points.remove(data)

        return mount_info

    def _get_mount_point_data(self, device, mount_points):
        """Get the mount point data for the given device."""

        # Try to find existing assignment for this device.
        for data in mount_points:
            if device is self.storage.devicetree.resolve_device(data[MOUNT_POINT_DEVICE]):
                return data

        # Or create a new assignment.
        if device.format.mountable and device.format.mountpoint:
            mount_point = device.format.mountpoint
        else:
            mount_point = ""

        return {
            MOUNT_POINT_DEVICE: device.path,
            MOUNT_POINT_PATH: mount_point,
            MOUNT_POINT_FORMAT: device.format.type,
            MOUNT_POINT_REFORMAT: False
        }

    def _get_mount_info_description(self, info):
        """Get description of the given mount info."""
        device, data = info
        description = "{} ({})".format(data[MOUNT_POINT_DEVICE], device.size)

        if data[MOUNT_POINT_FORMAT]:
            description += "\n {}".format(data[MOUNT_POINT_FORMAT])

            if data[MOUNT_POINT_REFORMAT]:
                description += "*"

            if data[MOUNT_POINT_PATH]:
                description += ", {}".format(data[MOUNT_POINT_PATH])

        return description

    def _configure_mount_info(self, info):
        """Configure the given mount info."""
        spoke = ConfigureDeviceSpoke(self.data, self.storage, self.payload, *info)
        ScreenHandler.push_screen(spoke)

    def _rescan_devices(self):
        """Rescan devices."""
        text = _("Warning: This will revert all changes done so far.\n"
                 "Do you want to proceed?\n")

        question_window = YesNoDialog(text)
        ScreenHandler.push_screen_modal(question_window)

        if not question_window.answer:
            return

        print(_("Scanning disks. This may take a moment..."))
        reset_storage(self.storage, scan_all=True)
        self._manual_part_proxy.SetMountPoints([])
        self._mount_info = self._gather_mount_info()
Ejemplo n.º 23
0
class NetworkSpoke(FirstbootSpokeMixIn, EditTUISpoke):
    """ Spoke used to configure network settings.

       .. inheritance-diagram:: NetworkSpoke
          :parts: 3
    """
    helpFile = "NetworkSpoke.txt"
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        EditTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Network configuration")
        self._container = None
        self.hostname_dialog = OneShotEditTUIDialog(data, storage, payload, instclass)
        self.hostname_dialog.value = self.data.network.hostname
        self.supported_devices = []
        self.errors = []
        self._apply = False

    def initialize(self):
        self.initialize_start()
        self._load_new_devices()

        EditTUISpoke.initialize(self)
        if not self.data.network.seen:
            self._update_network_data()
        self.initialize_done()

    def _load_new_devices(self):
        devices = nm.nm_devices()
        intf_dumped = network.dumpMissingDefaultIfcfgs()
        if intf_dumped:
            log.debug("dumped interfaces: %s", intf_dumped)

        for name in devices:
            if name in self.supported_devices:
                continue
            if network.is_ibft_configured_device(name):
                continue
            if network.device_type_is_supported_wired(name):
                # ignore slaves
                try:
                    if nm.nm_device_setting_value(name, "connection", "slave-type"):
                        continue
                except nm.MultipleSettingsFoundError as e:
                    log.debug("%s during initialization", e)
                self.supported_devices.append(name)

    @property
    def completed(self):
        """ Check whether this spoke is complete or not. Do an additional
            check if we're installing from CD/DVD, since a network connection
            should not be required in this case.
        """
        return (not can_touch_runtime_system("require network connection")
                or nm.nm_activated_devices())

    @property
    def mandatory(self):
        # the network spoke should be mandatory only if it is running
        # during the installation and if the installation source requires network
        return ANACONDA_ENVIRON in flags.environs and self.payload.needsNetwork

    @property
    def status(self):
        """ Short msg telling what devices are active. """
        return network.status_message()

    def _summary_text(self):
        """Devices cofiguration shown to user."""
        msg = ""
        activated_devs = nm.nm_activated_devices()
        for name in self.supported_devices:
            if name in activated_devs:
                msg += self._activated_device_msg(name)
            else:
                msg += _("Wired (%(interface_name)s) disconnected\n") \
                    % {"interface_name": name}
        return msg

    def _activated_device_msg(self, devname):
        msg = _("Wired (%(interface_name)s) connected\n") \
                % {"interface_name": devname}

        ipv4config = nm.nm_device_ip_config(devname, version=4)
        ipv6config = nm.nm_device_ip_config(devname, version=6)

        if ipv4config and ipv4config[0]:
            addr_str, prefix, gateway_str = ipv4config[0][0]
            netmask_str = network.prefix2netmask(prefix)
            dnss_str = ",".join(ipv4config[1])
        else:
            addr_str = dnss_str = gateway_str = netmask_str = ""
        msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
                {"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
        msg += _(" DNS: %s\n") % dnss_str

        if ipv6config and ipv6config[0]:
            for ipv6addr in ipv6config[0]:
                addr_str, prefix, gateway_str = ipv6addr
                # Do not display link-local addresses
                if not addr_str.startswith("fe80:"):
                    msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
                            {"addr": addr_str, "prefix": prefix}

            dnss_str = ",".join(ipv6config[1])

        return msg

    def refresh(self, args=None):
        """ Refresh screen. """
        self._load_new_devices()
        EditTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))
        hostname = _("Host Name: %s\n") % self.data.network.hostname
        self.window.add_with_separator(TextWidget(hostname))
        current_hostname = _("Current host name: %s\n") % network.current_hostname()
        self.window.add_with_separator(TextWidget(current_hostname))

        # if we have any errors, display them
        while len(self.errors) > 0:
            self.window.add_with_separator(TextWidget(self.errors.pop()))

        self._container.add(TextWidget(_("Set host name")), callback=self._set_hostname_callback)

        for dev_name in self.supported_devices:
            text = (_("Configure device %s") % dev_name)
            self._container.add(TextWidget(text), callback=self._configure_network_interface, data=dev_name)

        self.window.add_with_separator(self._container)

    def _set_hostname_callback(self, data):
        # set hostname
        entry = Entry(_("Host Name"), "hostname", re.compile(".*$"), True)
        ScreenHandler.push_screen_modal(self.hostname_dialog, entry)
        self.redraw()
        self.apply()

    def _configure_network_interface(self, data):
        devname = data
        ndata = network.ksdata_from_ifcfg(devname)
        if not ndata:
            # There is no ifcfg file for the device.
            # Make sure there is just one connection for the device.
            try:
                nm.nm_device_setting_value(devname, "connection", "uuid")
            except nm.SettingsNotFoundError:
                log.debug("can't find any connection for %s", devname)
                return
            except nm.MultipleSettingsFoundError:
                log.debug("multiple non-ifcfg connections found for %s", devname)
                return

            log.debug("dumping ifcfg file for in-memory connection %s", devname)
            nm.nm_update_settings_of_device(devname, [['connection', 'id', devname, None]])
            ndata = network.ksdata_from_ifcfg(devname)

        new_spoke = ConfigureNetworkSpoke(self.data, self.storage,
                                          self.payload, self.instclass, ndata)
        ScreenHandler.push_screen_modal(new_spoke)
        self.redraw()

        if ndata.ip == "dhcp":
            ndata.bootProto = "dhcp"
            ndata.ip = ""
        else:
            ndata.bootProto = "static"
            if not ndata.netmask:
                self.errors.append(_("Configuration not saved: netmask missing in static configuration"))
                return

        if ndata.ipv6 == "ignore":
            ndata.noipv6 = True
            ndata.ipv6 = ""
        else:
            ndata.noipv6 = False

        uuid = network.update_settings_with_ksdata(devname, ndata)
        network.update_onboot_value(devname, ndata.onboot, ksdata=None, root_path="")
        network.logIfcfgFiles("settings of %s updated in tui" % devname)

        if ndata._apply:
            self._apply = True
            try:
                nm.nm_activate_device_connection(devname, uuid)
            except (nm.UnmanagedDeviceError, nm.UnknownConnectionError):
                self.errors.append(_("Can't apply configuration, device activation failed."))

        self.apply()

    def input(self, args, key):
        """ Handle the input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super(NetworkSpoke, self).input(args, key)

    def apply(self):
        """Apply all of our settings."""
        self._update_network_data()
        log.debug("apply ksdata %s", self.data.network)

        if self._apply:
            self._apply = False
            if ANACONDA_ENVIRON in flags.environs:
                from pyanaconda.payload import payloadMgr
                payloadMgr.restartThread(self.storage, self.data, self.payload,
                                         self.instclass, checkmount=False)

    def _update_network_data(self):
        hostname = self.data.network.hostname

        self.data.network.network = []
        for i, name in enumerate(nm.nm_devices()):
            if network.is_ibft_configured_device(name):
                continue
            nd = network.ksdata_from_ifcfg(name)
            if not nd:
                continue
            if name in nm.nm_activated_devices():
                nd.activate = True
            else:
                # First network command defaults to --activate so we must
                # use --no-activate explicitly to prevent the default
                if i == 0:
                    nd.activate = False
            self.data.network.network.append(nd)

        (valid, error) = network.sanityCheckHostname(self.hostname_dialog.value)
        if valid:
            hostname = self.hostname_dialog.value
        else:
            self.errors.append(_("Host name is not valid: %s") % error)
            self.hostname_dialog.value = hostname
        network.update_hostname_data(self.data, hostname)
Ejemplo n.º 24
0
class TimeSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    helpFile = "DateTimeSpoke.txt"
    category = LocalizationCategory

    def __init__(self, data, storage, payload):
        NormalTUISpoke.__init__(self, data, storage, payload)
        self.title = N_("Time settings")
        self._timezone_spoke = None
        self._container = None
        # we use an ordered dict to keep the NTP server insertion order
        self._ntp_servers = OrderedDict()
        self._ntp_servers_lock = RLock()

        self._timezone_module = TIMEZONE.get_observer()
        self._timezone_module.connect()

    @property
    def indirect(self):
        return False

    def initialize(self):
        self.initialize_start()
        # We get the initial NTP servers (if any):
        # - from kickstart when running inside of Anaconda
        #   during the installation
        # - from config files when running in Initial Setup
        #   after the installation
        ntp_servers = []

        if constants.ANACONDA_ENVIRON in flags.environs:
            ntp_servers = self._timezone_module.proxy.NTPServers
        elif constants.FIRSTBOOT_ENVIRON in flags.environs:
            ntp_servers = ntp.get_servers_from_config()[1]  # returns a (NPT pools, NTP servers) tupple
        else:
            log.error("tui time spoke: unsupported environment configuration %s,"
                      "can't decide where to get initial NTP servers", flags.environs)

        # check if the NTP servers appear to be working or not
        if ntp_servers:
            for server in ntp_servers:
                self._ntp_servers[server] = constants.NTP_SERVER_QUERY

            # check if the newly added NTP servers work fine
            self._check_ntp_servers_async(self._ntp_servers.keys())

        # we assume that the NTP spoke is initialized enough even if some NTP
        # server check threads might still be running
        self.initialize_done()

    def _check_ntp_servers_async(self, servers):
        """Asynchronously check if given NTP servers appear to be working.

        :param list servers: list of servers to check
        """
        for server in servers:
            threadMgr.add(AnacondaThread(prefix=constants.THREAD_NTP_SERVER_CHECK,
                                         target=self._check_ntp_server,
                                         args=(server,)))

    def _check_ntp_server(self, server):
        """Check if an NTP server appears to be working.

        :param str server: NTP server address
        :returns: True if the server appears to be working, False if not
        :rtype: bool
        """
        log.debug("checking NTP server %s", server)
        result = ntp.ntp_server_working(server)
        if result:
            log.debug("NTP server %s appears to be working", server)
            self.set_ntp_server_status(server, constants.NTP_SERVER_OK)
        else:
            log.debug("NTP server %s appears not to be working", server)
            self.set_ntp_server_status(server, constants.NTP_SERVER_NOK)

    @property
    def ntp_servers(self):
        """Return a list of NTP servers known to the Time spoke.

        :returns: a list of NTP servers
        :rtype: list of strings
        """
        return self._ntp_servers

    def add_ntp_server(self, server):
        """Add NTP server address to our internal NTP server tracking dictionary.

        :param str server: NTP server address to add
        """
        # the add & remove operations should (at least at the moment) be never
        # called from different threads at the same time, but lets just use
        # a lock there when we are at it
        with self._ntp_servers_lock:
            if server not in self._ntp_servers:
                self._ntp_servers[server] = constants.NTP_SERVER_QUERY
                self._check_ntp_servers_async([server])

    def remove_ntp_server(self, server):
        """Remove NTP server address from our internal NTP server tracking dictionary.

        :param str server: NTP server address to remove
        """
        # the remove-server and set-server-status operations need to be atomic,
        # so that we avoid reintroducing removed servers by setting their status
        with self._ntp_servers_lock:
            if server in self._ntp_servers:
                del self._ntp_servers[server]

    def set_ntp_server_status(self, server, status):
        """Set status for an NTP server in the NTP server dict.

        The status can be "working", "not working" or "check in progress",
        and is defined by three constants defined in constants.py.

        :param str server: an NTP server
        :param int status: status of the NTP server
        """

        # the remove-server and set-server-status operations need to be atomic,
        # so that we avoid reintroducing removed server by setting their status
        with self._ntp_servers_lock:
            if server in self._ntp_servers:
                self._ntp_servers[server] = status

    @property
    def timezone_spoke(self):
        if not self._timezone_spoke:
            self._timezone_spoke = TimeZoneSpoke(self.data, self.storage, self.payload)
        return self._timezone_spoke

    @property
    def completed(self):
        return bool(self._timezone_module.proxy.Timezone)

    @property
    def mandatory(self):
        return True

    @property
    def status(self):
        kickstart_timezone = self._timezone_module.proxy.Timezone

        if kickstart_timezone:
            return _("%s timezone") % kickstart_timezone
        else:
            return _("Timezone is not set.")

    def _summary_text(self):
        """Return summary of current timezone & NTP configuration.

        :returns: current status
        :rtype: str
        """
        msg = ""
        # timezone
        kickstart_timezone = self._timezone_module.proxy.Timezone
        timezone_msg = _("not set")
        if kickstart_timezone:
            timezone_msg = kickstart_timezone

        msg += _("Timezone: %s\n") % timezone_msg

        # newline section separator
        msg += "\n"

        # NTP
        msg += _("NTP servers:")
        if self._ntp_servers:
            for status in format_ntp_status_list(self._ntp_servers):
                msg += "\n%s" % status
        else:
            msg += _("not configured")

        return msg

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        if self._timezone_module.proxy.Timezone:
            timezone_option = _("Change timezone")
        else:
            timezone_option = _("Set timezone")

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        self._container.add(TextWidget(timezone_option), callback=self._timezone_callback)
        self._container.add(TextWidget(_("Configure NTP servers")), callback=self._configure_ntp_server_callback)

        self.window.add_with_separator(self._container)

    def _timezone_callback(self, data):
        ScreenHandler.push_screen_modal(self.timezone_spoke)
        self.close()

    def _configure_ntp_server_callback(self, data):
        new_spoke = NTPServersSpoke(self.data, self.storage, self.payload, self)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def input(self, args, key):
        """ Handle the input - visit a sub spoke or go back to hub."""
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        # update the NTP server list in kickstart
        self._timezone_module.proxy.SetNTPServers(list(self.ntp_servers.keys()))
Ejemplo n.º 25
0
class ConfigureDeviceSpoke(NormalTUISpoke):
    """ Assign mount point to a block device and (optionally) reformat it. """
    category = SystemCategory

    def __init__(self, data, storage, payload, device, mount_data):
        super().__init__(data, storage, payload)
        self.title = N_("Configure device: %s") % mount_data[MOUNT_POINT_DEVICE]
        self._container = None

        self._supported_filesystems = [fmt.type for fmt in get_supported_filesystems()]
        self._mount_data = mount_data
        self._device = device

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        """Refresh window."""
        super().refresh(args)
        self._container = ListColumnContainer(1)
        self._add_mount_point_widget()
        self._add_format_widget()
        self._add_reformat_widget()

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(TextWidget(
            _("Choose from above to assign mount point and/or set format.")
        ))

    def input(self, args, key):
        """Grab the choice and update things."""
        if not self._container.process_user_input(key):
            return super().input(args, key)

        return InputState.PROCESSED_AND_REDRAW

    def apply(self):
        """Nothing to apply here."""
        pass

    def _add_mount_point_widget(self):
        """Add a widget for mount point assignment."""
        title = _("Mount point")
        fmt = get_format(self._mount_data[MOUNT_POINT_FORMAT])

        if fmt and fmt.mountable:
            # mount point can be set
            value = self._mount_data[MOUNT_POINT_PATH] or _("none")
            callback = self._assign_mount_point
        elif fmt and fmt.type is None:
            # mount point cannot be set for no format
            # (fmt.name = "Unknown" in this case which would look weird)
            value = _("none")
            callback = None
        else:
            # mount point cannot be set for format that is not mountable, just
            # show the format's name in square brackets instead
            value = fmt.name
            callback = None

        dialog = Dialog(title, conditions=[self._check_assign_mount_point])
        widget = EntryWidget(dialog.title, value)
        self._container.add(widget, callback, dialog)

    def _check_assign_mount_point(self, user_input, report_func):
        """Check the mount point assignment."""
        # a valid mount point must start with / or user set nothing
        if user_input == "" or user_input.startswith("/"):
            return True
        else:
            report_func(_("Invalid mount point given"))
            return False

    def _assign_mount_point(self, dialog):
        """Change the mount point assignment."""
        self._mount_data[MOUNT_POINT_PATH] = dialog.run()

        # Always reformat root.
        if self._mount_data[MOUNT_POINT_PATH] == "/":
            self._mount_data[MOUNT_POINT_REFORMAT] = True

    def _add_format_widget(self):
        """Add a widget for format."""
        dialog = Dialog(_("Format"), conditions=[self._check_format])
        widget = EntryWidget(dialog.title, self._mount_data[MOUNT_POINT_FORMAT] or _("none"))
        self._container.add(widget, self._set_format, dialog)

    def _check_format(self, user_input, report_func):
        """Check value of format."""
        user_input = user_input.lower()
        if user_input in self._supported_filesystems:
            return True
        else:
            msg = _("Invalid or unsupported format given")
            msg += "\n"
            msg += (_("Supported formats: %s") % ", ".join(self._supported_filesystems))
            report_func(msg)
            return False

    def _set_format(self, dialog):
        """Change value of format."""
        old_format = self._mount_data[MOUNT_POINT_FORMAT]
        new_format = dialog.run()

        # Reformat to a new format.
        if new_format != old_format:
            self._mount_data[MOUNT_POINT_FORMAT] = new_format
            self._mount_data[MOUNT_POINT_REFORMAT] = True

    def _add_reformat_widget(self):
        """Add a widget for reformat."""
        widget = CheckboxWidget(
            title=_("Reformat"),
            completed=self._mount_data[MOUNT_POINT_REFORMAT]
        )
        self._container.add(widget, self._switch_reformat)

    def _switch_reformat(self, data):
        """Change value of reformat."""
        device_format = self._device.format.type

        if device_format and device_format != self._mount_data[MOUNT_POINT_FORMAT]:
            reformat = True
        elif self._mount_data[MOUNT_POINT_PATH] == "/":
            reformat = True
        else:
            reformat = not self._mount_data[MOUNT_POINT_REFORMAT]

        self._mount_data[MOUNT_POINT_REFORMAT] = reformat
Ejemplo n.º 26
0
class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
    This spoke allows a user to select their installed language. Note that this
    does not affect the display of the installer, it only will affect the system
    post-install, because it's too much of a pain to make other languages work
    in text-mode.

    Also this doesn't allow for selection of multiple languages like in the GUI.

       .. inheritance-diagram:: LangSpoke
          :parts: 3
    """
    helpFile = "LangSupportSpoke.txt"
    category = LocalizationCategory

    def __init__(self, data, storage, payload):
        NormalTUISpoke.__init__(self, data, storage, payload)
        self.title = N_("Language settings")
        self.initialize_start()
        self._container = None

        self._langs = [
            localization.get_english_name(lang)
            for lang in localization.get_available_translations()
        ]
        self._langs_and_locales = dict(
            (localization.get_english_name(lang), lang)
            for lang in localization.get_available_translations())
        self._locales = dict((lang, localization.get_language_locales(lang))
                             for lang in self._langs_and_locales.values())

        self._l12_module = LOCALIZATION.get_proxy()

        self._selected = self._l12_module.Language
        self.initialize_done()

    @property
    def completed(self):
        return self._l12_module.Language

    @property
    def mandatory(self):
        return False

    @property
    def showable(self):
        # don't show the language support spoke in single language mode
        return not flags.singlelang

    @property
    def status(self):
        if self._l12_module.Language:
            return localization.get_english_name(self._selected)
        else:
            return _("Language is not set.")

    def refresh(self, args=None):
        """
        args is None if we want a list of languages; or, it is a list of all
        locales for a language.
        """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(3)

        if args:
            self.window.add(TextWidget(_("Available locales")))
            for locale in args:
                widget = TextWidget(localization.get_english_name(locale))
                self._container.add(widget, self._set_locales_callback, locale)
        else:
            self.window.add(TextWidget(_("Available languages")))
            for lang in self._langs:
                langs_and_locales = self._langs_and_locales[lang]
                locales = self._locales[langs_and_locales]
                self._container.add(TextWidget(lang),
                                    self._show_locales_callback, locales)

        self.window.add_with_separator(self._container)

    def _set_locales_callback(self, data):
        locale = data
        self._selected = locale
        self.apply()
        self.close()

    def _show_locales_callback(self, data):
        locales = data
        ScreenHandler.replace_screen(self, locales)

    def input(self, args, key):
        """ Handle user input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # TRANSLATORS: 'b' to go back
            if key.lower() == C_("TUI|Spoke Navigation|Language Support", "b"):
                ScreenHandler.replace_screen(self)
                return InputState.PROCESSED
            else:
                return super().input(args, key)

    def prompt(self, args=None):
        """ Customize default prompt. """
        prompt = NormalTUISpoke.prompt(self, args)
        prompt.set_message(_("Please select language support to install"))
        # TRANSLATORS: 'b' to go back
        prompt.add_option(C_("TUI|Spoke Navigation|Language Support", "b"),
                          _("to return to language list"))
        return prompt

    def apply(self):
        """ Store the selected lang support locales """
        self._l12_module.SetLanguage(self._selected)
Ejemplo n.º 27
0
class PartTypeSpoke(NormalTUISpoke):
    """ Partitioning options are presented here.

       .. inheritance-diagram:: PartTypeSpoke
          :parts: 3
    """
    category = SystemCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Partitioning Options")
        self._container = None
        self._part_type_list = sorted(PARTTYPES.keys())

        # remember the original values so that we can detect a change
        self._disk_init_proxy = STORAGE.get_proxy(DISK_INITIALIZATION)
        self._orig_init_mode = self._disk_init_proxy.InitializationMode
        self._manual_part_proxy = STORAGE.get_proxy(MANUAL_PARTITIONING)
        self._orig_mount_assign = self._manual_part_proxy.Enabled

        # Create the auto partitioning proxy
        self._auto_part_proxy = STORAGE.get_proxy(AUTO_PARTITIONING)

        # default to mount point assignment if it is already (partially)
        # configured
        self._do_mount_assign = self._orig_mount_assign
        if not self._do_mount_assign:
            self._init_mode = self._disk_init_proxy.InitializationMode
        else:
            self._init_mode = CLEAR_PARTITIONS_NONE

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        super().refresh(args)
        self._container = ListColumnContainer(1)

        for part_type in self._part_type_list:
            c = CheckboxWidget(title=_(part_type),
                               completed=(not self._do_mount_assign
                                          and PARTTYPES[part_type] == self._init_mode)
                               )
            self._container.add(c, self._select_partition_type_callback, part_type)

        c = CheckboxWidget(title=_("Manually assign mount points"),
                           completed=self._do_mount_assign)

        self._container.add(c, self._select_mount_assign)
        self.window.add_with_separator(self._container)

        message = _("Installation requires partitioning of your hard drive. "
                    "Select what space to use for the install target or "
                    "manually assign mount points.")

        self.window.add_with_separator(TextWidget(message))

    def _select_mount_assign(self, data=None):
        self._init_mode = CLEAR_PARTITIONS_NONE
        self._do_mount_assign = True
        self.apply()

    def _select_partition_type_callback(self, data):
        self._do_mount_assign = False
        self._init_mode = PARTTYPES[data]
        self.apply()

    def apply(self):
        # kind of a hack, but if we're actually getting to this spoke, there
        # is no doubt that we are doing autopartitioning, so set autopart to
        # True. In the case of ks installs which may not have defined any
        # partition options, autopart was never set to True, causing some
        # issues. (rhbz#1001061)
        if not self._do_mount_assign:
            self._auto_part_proxy.SetEnabled(True)
            self._manual_part_proxy.SetEnabled(False)
            self._disk_init_proxy.SetInitializationMode(self._init_mode)
            self._disk_init_proxy.SetInitializeLabelsEnabled(True)
        else:
            self._auto_part_proxy.SetEnabled(False)
            self._manual_part_proxy.SetEnabled(True)
            self._disk_init_proxy.SetInitializationMode(CLEAR_PARTITIONS_NONE)
            self._disk_init_proxy.SetInitializeLabelsEnabled(False)

    def _ensure_init_storage(self):
        """
        If a different clearpart type was chosen or mount point assignment was
        chosen instead, we need to reset/rescan storage to revert all changes
        done by the previous run of doKickstartStorage() and get everything into
        the initial state.
        """
        # the only safe options are:
        # 1) if nothing was set before (self._orig_clearpart_type is None) or
        if self._orig_init_mode == CLEAR_PARTITIONS_DEFAULT:
            return

        # 2) mount point assignment was done before and user just wants to tweak it
        if self._orig_mount_assign and self._do_mount_assign:
            return

        # else
        print(_("Reverting previous configuration. This may take a moment..."))
        reset_storage(self.storage, scan_all=True)
        self._manual_part_proxy.SetMountPoints([])

    def input(self, args, key):
        """Grab the choice and update things"""
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()
                self._ensure_init_storage()
                if self._do_mount_assign:
                    new_spoke = MountPointAssignSpoke(self.data, self.storage, self.payload)
                else:
                    new_spoke = PartitionSchemeSpoke(self.data, self.storage, self.payload)
                ScreenHandler.push_screen_modal(new_spoke)
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

        return InputState.PROCESSED_AND_REDRAW
Ejemplo n.º 28
0
class TimeZoneSpoke(NormalTUISpoke):
    """
       .. inheritance-diagram:: TimeZoneSpoke
          :parts: 3
    """
    category = LocalizationCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)

        self.title = N_("Timezone settings")
        self._container = None
        # it's stupid to call get_all_regions_and_timezones twice, but regions
        # needs to be unsorted in order to display in the same order as the GUI
        # so whatever
        self._regions = list(timezone.get_all_regions_and_timezones().keys())
        self._timezones = dict((k, sorted(v)) for k, v in timezone.get_all_regions_and_timezones().items())
        self._lower_regions = [r.lower() for r in self._regions]

        self._zones = ["%s/%s" % (region, z) for region in self._timezones for z in self._timezones[region]]
        # for lowercase lookup
        self._lower_zones = [z.lower().replace("_", " ") for region in self._timezones for z in self._timezones[region]]
        self._selection = ""

        self._timezone_module = TIMEZONE.get_observer()
        self._timezone_module.connect()

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        """args is None if we want a list of zones or "zone" to show all timezones in that zone."""
        super().refresh(args)

        self._container = ListColumnContainer(3, columns_width=24)

        if args and args in self._timezones:
            self.window.add(TextWidget(_("Available timezones in region %s") % args))
            for tz in self._timezones[args]:
                self._container.add(TextWidget(tz), self._select_timezone_callback, CallbackTimezoneArgs(args, tz))
        else:
            self.window.add(TextWidget(_("Available regions")))
            for region in self._regions:
                self._container.add(TextWidget(region), self._select_region_callback, region)

        self.window.add_with_separator(self._container)

    def _select_timezone_callback(self, data):
        self._selection = "%s/%s" % (data.region, data.timezone)
        self.apply()
        self.close()

    def _select_region_callback(self, data):
        region = data
        selected_timezones = self._timezones[region]
        if len(selected_timezones) == 1:
            self._selection = "%s/%s" % (region, selected_timezones[0])
            self.apply()
            self.close()
        else:
            ScreenHandler.replace_screen(self, region)

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            if key.lower().replace("_", " ") in self._lower_zones:
                index = self._lower_zones.index(key.lower().replace("_", " "))
                self._selection = self._zones[index]
                self.apply()
                return InputState.PROCESSED_AND_CLOSE
            elif key.lower() in self._lower_regions:
                index = self._lower_regions.index(key.lower())
                if len(self._timezones[self._regions[index]]) == 1:
                    self._selection = "%s/%s" % (self._regions[index],
                                                 self._timezones[self._regions[index]][0])
                    self.apply()
                    self.close()
                else:
                    ScreenHandler.replace_screen(self, self._regions[index])
                return InputState.PROCESSED
            # TRANSLATORS: 'b' to go back
            elif key.lower() == C_('TUI|Spoke Navigation|Time Settings', 'b'):
                ScreenHandler.replace_screen(self)
                return InputState.PROCESSED
            else:
                return key

    def prompt(self, args=None):
        """ Customize default prompt. """
        prompt = NormalTUISpoke.prompt(self, args)
        prompt.set_message(_("Please select the timezone. Use numbers or type names directly"))
        # TRANSLATORS: 'b' to go back
        prompt.add_option(C_('TUI|Spoke Navigation|Time Settings', 'b'), _("back to region list"))
        return prompt

    def apply(self):
        self._timezone_module.proxy.SetTimezone(self._selection)
        self._timezone_module.proxy.SetKickstarted(False)
Ejemplo n.º 29
0
class UserSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
       .. inheritance-diagram:: UserSpoke
          :parts: 3
    """
    helpFile = "UserSpoke.txt"
    category = UserSettingsCategory

    @classmethod
    def should_run(cls, environment, data):
        if FirstbootSpokeMixIn.should_run(environment, data):
            return True

        # the user spoke should run always in the anaconda and in firstboot only
        # when doing reconfig or if no user has been created in the installation
        if environment == FIRSTBOOT_ENVIRON and data and not data.user.userList:
            return True

        return False

    def __init__(self, data, storage, payload, instclass):
        FirstbootSpokeMixIn.__init__(self)
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.initialize_start()

        self.title = N_("User creation")
        self._container = None

        if self.data.user.userList:
            self._user_data = self.data.user.userList[0]
            self._create_user = True
        else:
            self._user_data = self.data.UserData()
            self._create_user = False

        self._use_password = self._user_data.isCrypted or self._user_data.password
        self._groups = ""
        self._is_admin = False
        self._policy = self.data.anaconda.pwpolicy.get_policy("user", fallback_to_default=True)

        self.errors = []

        self._users_module = USERS.get_observer()
        self._users_module.connect()

        self.initialize_done()

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)
        self._is_admin = "wheel" in self._user_data.groups
        self._groups = ", ".join(self._user_data.groups)

        self._container = ListColumnContainer(1)

        w = CheckboxWidget(title=_("Create user"), completed=self._create_user)
        self._container.add(w, self._set_create_user)

        if self._create_user:
            dialog = Dialog(title=_("Full name"), conditions=[self._check_fullname])
            self._container.add(EntryWidget(dialog.title, self._user_data.gecos), self._set_fullname, dialog)

            dialog = Dialog(title=_("User name"), conditions=[self._check_username])
            self._container.add(EntryWidget(dialog.title, self._user_data.name), self._set_username, dialog)

            w = CheckboxWidget(title=_("Use password"), completed=self._use_password)
            self._container.add(w, self._set_use_password)

            if self._use_password:
                password_dialog = PasswordDialog(title=_("Password"), policy=self._policy)
                if self._user_data.password:
                    entry = EntryWidget(password_dialog.title, _(PASSWORD_SET))
                else:
                    entry = EntryWidget(password_dialog.title)

                self._container.add(entry, self._set_password, password_dialog)

            msg = _("Administrator")
            w = CheckboxWidget(title=msg, completed=self._is_admin)
            self._container.add(w, self._set_administrator)

            dialog = Dialog(title=_("Groups"), conditions=[self._check_groups])
            self._container.add(EntryWidget(dialog.title, self._groups), self._set_groups, dialog)

        self.window.add_with_separator(self._container)

    @report_if_failed(message=FULLNAME_ERROR_MSG)
    def _check_fullname(self, user_input, report_func):
        return GECOS_VALID.match(user_input) is not None

    @report_check_func()
    def _check_username(self, user_input, report_func):
        return check_username(user_input)

    @report_check_func()
    def _check_groups(self, user_input, report_func):
        return check_grouplist(user_input)

    def _set_create_user(self, args):
        self._create_user = not self._create_user

    def _set_fullname(self, dialog):
        self._user_data.gecos = dialog.run()

    def _set_username(self, dialog):
        self._user_data.name = dialog.run()

    def _set_use_password(self, args):
        self._use_password = not self._use_password

    def _set_password(self, password_dialog):
        password = password_dialog.run()

        while password is None:
            password = password_dialog.run()

        self._user_data.password = password

    def _set_administrator(self, args):
        self._is_admin = not self._is_admin

    def _set_groups(self, dialog):
        self._groups = dialog.run()

    def show_all(self):
        NormalTUISpoke.show_all(self)
        # if we have any errors, display them
        while self.errors:
            print(self.errors.pop())

    @property
    def completed(self):
        """ Verify a user is created; verify pw is set if option checked. """
        if len(self.data.user.userList) > 0:
            if self._use_password and not bool(self._user_data.password or self._user_data.isCrypted):
                return False
            else:
                return True
        else:
            return False

    @property
    def showable(self):
        return not (self.completed and flags.automatedInstall
                    and self.data.user.seen and not self._policy.changesok)

    @property
    def mandatory(self):
        """ Only mandatory if the root pw hasn't been set in the UI
            eg. not mandatory if the root account was locked in a kickstart
        """
        return not self._users_module.proxy.IsRootPasswordSet and not self._users_module.proxy.IsRootAccountLocked

    @property
    def status(self):
        if len(self.data.user.userList) == 0:
            return _("No user will be created")
        elif self._use_password and not bool(self._user_data.password or self._user_data.isCrypted):
            return _("You must set a password")
        elif "wheel" in self.data.user.userList[0].groups:
            return _("Administrator %s will be created") % self.data.user.userList[0].name
        else:
            return _("User %s will be created") % self.data.user.userList[0].name

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW

        return super().input(args, key)

    def apply(self):
        if self._user_data.gecos and not self._user_data.name:
            username = guess_username(self._user_data.gecos)
            valid, msg = check_username(username)
            if not valid:
                self.errors.append(_("Invalid user name: %(name)s.\n%(error_message)s")
                                   % {"name": username, "error_message": msg})
            else:
                self._user_data.name = guess_username(self._user_data.gecos)

        self._user_data.groups = [g.strip() for g in self._groups.split(",") if g]

        # Add or remove the user from wheel group
        if self._is_admin and "wheel" not in self._user_data.groups:
            self._user_data.groups.append("wheel")
        elif not self._is_admin and "wheel" in self._user_data.groups:
            self._user_data.groups.remove("wheel")

        # Add or remove the user from userlist as needed
        if self._create_user and (self._user_data not in self.data.user.userList and self._user_data.name):
            self.data.user.userList.append(self._user_data)
        elif (not self._create_user) and (self._user_data in self.data.user.userList):
            self.data.user.userList.remove(self._user_data)

        # encrypt and store password only if user entered anything; this should
        # preserve passwords set via kickstart
        if self._use_password and self._user_data.password and len(self._user_data.password) > 0:
            self._user_data.password = self._user_data.password
            self._user_data.isCrypted = True
        # clear pw when user unselects to use pw
        else:
            self._user_data.password = ""
            self._user_data.isCrypted = False
Ejemplo n.º 30
0
class ConfigureNetworkSpoke(NormalTUISpoke):
    """ Spoke to set various configuration options for net devices. """
    category = "network"

    def __init__(self, data, storage, payload, instclass, network_data):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("Device configuration")

        self.network_data = network_data
        if self.network_data.bootProto == "dhcp":
            self.network_data.ip = "dhcp"
        if self.network_data.noipv6:
            self.network_data.ipv6 = "ignore"

        self.apply_configuration = False
        self._container = None

    def refresh(self, args=None):
        """ Refresh window. """
        super().refresh(args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=(_('IPv4 address or %s for DHCP') % '"dhcp"'),
                        conditions=[self._check_ipv4_or_dhcp])
        self._container.add(EntryWidget(dialog.title, self.network_data.ip),
                            self._set_ipv4_or_dhcp, dialog)

        dialog = Dialog(title=_("IPv4 netmask"),
                        conditions=[self._check_netmask])
        self._container.add(
            EntryWidget(dialog.title, self.network_data.netmask),
            self._set_netmask, dialog)

        dialog = Dialog(title=_("IPv4 gateway"), conditions=[self._check_ipv4])
        self._container.add(
            EntryWidget(dialog.title, self.network_data.gateway),
            self._set_ipv4_gateway, dialog)

        msg = (_(
            'IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, '
            '%(ignore)s to turn off') % {
                "auto": '"auto"',
                "dhcp": '"dhcp"',
                "ignore": '"ignore"'
            })
        dialog = Dialog(title=msg, conditions=[self._check_ipv6_config])
        self._container.add(EntryWidget(dialog.title, self.network_data.ipv6),
                            self._set_ipv6, dialog)

        dialog = Dialog(title=_("IPv6 default gateway"),
                        conditions=[self._check_ipv6])
        self._container.add(
            EntryWidget(dialog.title, self.network_data.ipv6gateway),
            self._set_ipv6_gateway, dialog)

        dialog = Dialog(title=_("Nameservers (comma separated)"),
                        conditions=[self._check_nameservers])
        self._container.add(
            EntryWidget(dialog.title, self.network_data.nameserver),
            self._set_nameservers, dialog)

        msg = _("Connect automatically after reboot")
        w = CheckboxWidget(title=msg, completed=self.network_data.onboot)
        self._container.add(w, self._set_onboot_handler)

        msg = _("Apply configuration in installer")
        w = CheckboxWidget(title=msg, completed=self.apply_configuration)
        self._container.add(w, self._set_apply_handler)

        self.window.add_with_separator(self._container)

        message = _("Configuring device %s.") % self.network_data.device
        self.window.add_with_separator(TextWidget(message))

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4_or_dhcp(self, user_input, report_func):
        return IPV4_OR_DHCP_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4(self, user_input, report_func):
        return IPV4_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=NETMASK_ERROR_MSG)
    def _check_netmask(self, user_input, report_func):
        return IPV4_NETMASK_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6(self, user_input, report_func):
        return network.check_ip_address(user_input, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6_config(self, user_input, report_func):
        if user_input in ["auto", "dhcp", "ignore"]:
            return True
        addr, _slash, prefix = user_input.partition("/")
        if prefix:
            try:
                if not 1 <= int(prefix) <= 128:
                    return False
            except ValueError:
                return False
        return network.check_ip_address(addr, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_nameservers(self, user_input, report_func):
        if user_input.strip():
            addresses = [str.strip(i) for i in user_input.split(",")]
            for ip in addresses:
                if not network.check_ip_address(ip):
                    return False
        return True

    def _set_ipv4_or_dhcp(self, dialog):
        self.network_data.ip = dialog.run()

    def _set_netmask(self, dialog):
        self.network_data.netmask = dialog.run()

    def _set_ipv4_gateway(self, dialog):
        self.network_data.gateway = dialog.run()

    def _set_ipv6(self, dialog):
        self.network_data.ipv6 = dialog.run()

    def _set_ipv6_gateway(self, dialog):
        self.network_data.ipv6gateway = dialog.run()

    def _set_nameservers(self, dialog):
        self.network_data.nameserver = dialog.run()

    def _set_apply_handler(self, args):
        self.apply_configuration = not self.apply_configuration

    def _set_onboot_handler(self, args):
        self.network_data.onboot = not self.network_data.onboot

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return super().input(args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply our changes. """
        # save this back to network data, this will be applied in upper layer
        pass
Ejemplo n.º 31
0
class RescueModeSpoke(NormalTUISpoke):
    """UI offering mounting existing installation roots in rescue mode."""

    # If it acts like a spoke and looks like a spoke, is it a spoke? Not
    # always. This is independent of any hub(s), so pass in some fake data
    def __init__(self, rescue):
        super().__init__(data=None, storage=None, payload=None)
        self.title = N_("Rescue")
        self._container = None
        self._rescue = rescue

    def refresh(self, args=None):
        super().refresh(args)

        msg = _("The rescue environment will now attempt "
                "to find your Linux installation and mount it under "
                "the directory : %s.  You can then make any changes "
                "required to your system.  Choose '1' to proceed with "
                "this step.\nYou can choose to mount your file "
                "systems read-only instead of read-write by choosing "
                "'2'.\nIf for some reason this process does not work "
                "choose '3' to skip directly to a shell.\n\n") % (
                    conf.target.system_root)
        self.window.add_with_separator(TextWidget(msg))

        self._container = ListColumnContainer(1)

        self._container.add(TextWidget(_("Continue")),
                            self._read_write_mount_callback)
        self._container.add(TextWidget(_("Read-only mount")),
                            self._read_only_mount_callback)
        self._container.add(TextWidget(_("Skip to shell")),
                            self._skip_to_shell_callback)
        self._container.add(TextWidget(_("Quit (Reboot)")),
                            self._quit_callback)

        self.window.add_with_separator(self._container)

    def _read_write_mount_callback(self, data):
        self._mount_and_prompt_for_shell()

    def _read_only_mount_callback(self, data):
        self._rescue.ro = True
        self._mount_and_prompt_for_shell()

    def _skip_to_shell_callback(self, data):
        self._show_result_and_prompt_for_shell()

    def _quit_callback(self, data):
        d = YesNoDialog(_(QUIT_MESSAGE))
        ScreenHandler.push_screen_modal(d)
        self.redraw()
        if d.answer:
            self._rescue.reboot = True
            self._rescue.finish()

    def _mount_and_prompt_for_shell(self):
        self._rescue.mount = True
        self._mount_root()
        self._show_result_and_prompt_for_shell()

    def prompt(self, args=None):
        """ Override the default TUI prompt."""
        if self._rescue.automated:
            if self._rescue.mount:
                self._mount_root()
            self._show_result_and_prompt_for_shell()
            return None
        return Prompt()

    def input(self, args, key):
        """Override any input so we can launch rescue mode."""
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return InputState.DISCARDED

    def _mount_root(self):
        # decrypt all luks devices
        self._unlock_devices()
        roots = self._rescue.find_roots()

        if not roots:
            return

        if len(roots) == 1:
            root = roots[0]
        else:
            # have to prompt user for which root to mount
            root_spoke = RootSelectionSpoke(roots)
            ScreenHandler.push_screen_modal(root_spoke)
            self.redraw()

            root = root_spoke.selection

        self._rescue.mount_root(root)

    def _show_result_and_prompt_for_shell(self):
        new_spoke = RescueStatusAndShellSpoke(self._rescue)
        ScreenHandler.push_screen_modal(new_spoke)
        self.close()

    def _unlock_devices(self):
        """Attempt to unlock all locked LUKS devices.

        Returns true if all devices were unlocked.
        """
        try_passphrase = None
        passphrase = None
        for device_name in self._rescue.get_locked_device_names():
            skip = False
            unlocked = False
            while not (skip or unlocked):
                if try_passphrase is None:
                    p = PasswordDialog(device_name)
                    ScreenHandler.push_screen_modal(p)
                    if p.answer:
                        passphrase = p.answer.strip()
                else:
                    passphrase = try_passphrase

                if passphrase is None:
                    # cancelled
                    skip = True
                else:
                    unlocked = self._rescue.unlock_device(
                        device_name, passphrase)
                    try_passphrase = passphrase if unlocked else None

        return not self._rescue.get_locked_device_names()

    def apply(self):
        """Move along home."""
        pass
Ejemplo n.º 32
0
class StorageSpoke(NormalTUISpoke):
    """Storage spoke where users proceed to customize storage features such
       as disk selection, partitioning, and fs type.

       .. inheritance-diagram:: StorageSpoke
          :parts: 3
    """
    helpFile = "StorageSpoke.txt"
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.title = N_("Installation Destination")
        self._ready = False
        self._container = None
        self.selected_disks = self.data.ignoredisk.onlyuse[:]
        self.select_all = False

        self.autopart = None
        self.clearPartType = None

        # This list gets set up once in initialize and should not be modified
        # except perhaps to add advanced devices. It will remain the full list
        # of disks that can be included in the install.
        self.disks = []
        self.errors = []
        self.warnings = []

        if self.data.zerombr.zerombr and arch.is_s390():
            # if zerombr is specified in a ks file and there are unformatted
            # dasds, automatically format them. pass in storage.devicetree here
            # instead of storage.disks since media_present is checked on disks;
            # a dasd needing dasdfmt will fail this media check though
            to_format = [d for d in getDisks(self.storage.devicetree)
                         if d.type == "dasd" and blockdev.s390.dasd_needs_format(d.busid)]
            if to_format:
                self.run_dasdfmt(to_format)

        if not flags.automatedInstall:
            # default to using autopart for interactive installs
            self.data.autopart.autopart = True

    @property
    def completed(self):
        retval = bool(self.storage.root_device and not self.errors)

        return retval

    @property
    def ready(self):
        # By default, the storage spoke is not ready.  We have to wait until
        # storageInitialize is done.
        return self._ready and not threadMgr.get(THREAD_STORAGE_WATCHER)

    @property
    def mandatory(self):
        return True

    @property
    def showable(self):
        return not flags.dirInstall

    @property
    def status(self):
        """ A short string describing the current status of storage setup. """
        msg = _("No disks selected")

        if flags.automatedInstall and not self.storage.root_device:
            msg = _("Kickstart insufficient")
        elif self.data.ignoredisk.onlyuse:
            msg = P_(("%d disk selected"),
                     ("%d disks selected"),
                     len(self.data.ignoredisk.onlyuse)) % len(self.data.ignoredisk.onlyuse)

            if self.errors:
                msg = _("Error checking storage configuration")
            elif self.warnings:
                msg = _("Warning checking storage configuration")
            # Maybe show what type of clearpart and which disks selected?
            elif self.data.autopart.autopart:
                msg = _("Automatic partitioning selected")
            else:
                msg = _("Custom partitioning selected")

        return msg

    def _update_disk_list(self, disk):
        """ Update self.selected_disks based on the selection."""

        name = disk.name

        # if the disk isn't already selected, select it.
        if name not in self.selected_disks:
            self.selected_disks.append(name)
        # If the disk is already selected, deselect it.
        elif name in self.selected_disks:
            self.selected_disks.remove(name)

    def _update_summary(self):
        """ Update the summary based on the UI. """
        count = 0
        capacity = 0
        free = Size(0)

        # pass in our disk list so hidden disks' free space is available
        free_space = self.storage.get_free_space(disks=self.disks)
        selected = [d for d in self.disks if d.name in self.selected_disks]

        for disk in selected:
            capacity += disk.size
            free += free_space[disk.name][0]
            count += 1

        summary = (P_(("%d disk selected; %s capacity; %s free ..."),
                      ("%d disks selected; %s capacity; %s free ..."),
                      count) % (count, str(Size(capacity)), free))

        if len(self.disks) == 0:
            summary = _("No disks detected.  Please shut down the computer, connect at least one disk, and restart to complete installation.")
        elif count == 0:
            summary = (_("No disks selected; please select at least one disk to install to."))

        # Append storage errors to the summary
        if self.errors:
            summary = summary + "\n" + "\n".join(self.errors)
        elif self.warnings:
            summary = summary + "\n" + "\n".join(self.warnings)

        return summary

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        # Join the initialization thread to block on it
        # This print is foul.  Need a better message display
        print(_(PAYLOAD_STATUS_PROBING_STORAGE))
        threadMgr.wait(THREAD_STORAGE_WATCHER)

        # synchronize our local data store with the global ksdata
        # Commment out because there is no way to select a disk right
        # now without putting it in ksdata.  Seems wrong?
        #self.selected_disks = self.data.ignoredisk.onlyuse[:]
        self.autopart = self.data.autopart.autopart

        self._container = ListColumnContainer(1, spacing=1)

        message = self._update_summary()

        # loop through the disks and present them.
        for disk in self.disks:
            disk_info = self._format_disk_info(disk)
            c = CheckboxWidget(title=disk_info, completed=(disk.name in self.selected_disks))
            self._container.add(c, self._update_disk_list_callback, disk)

        # if we have more than one disk, present an option to just
        # select all disks
        if len(self.disks) > 1:
            c = CheckboxWidget(title=_("Select all"), completed=self.select_all)
            self._container.add(c, self._select_all_disks_callback)

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(TextWidget(message))

    def _select_all_disks_callback(self, data):
        """ Mark all disks as selected for use in partitioning. """
        self.select_all = True
        for disk in self.disks:
            if disk.name not in self.selected_disks:
                self._update_disk_list(disk)

    def _update_disk_list_callback(self, data):
        disk = data
        self.select_all = False
        self._update_disk_list(disk)

    def _format_disk_info(self, disk):
        """ Some specialized disks are difficult to identify in the storage
            spoke, so add and return extra identifying information about them.

            Since this is going to be ugly to do within the confines of the
            CheckboxWidget, pre-format the display string right here.
        """
        # show this info for all disks
        format_str = "%s: %s (%s)" % (disk.model, disk.size, disk.name)

        disk_attrs = []
        # now check for/add info about special disks
        if (isinstance(disk, MultipathDevice) or isinstance(disk, iScsiDiskDevice) or isinstance(disk, FcoeDiskDevice)):
            if hasattr(disk, "wwid"):
                disk_attrs.append(disk.wwid)
        elif isinstance(disk, DASDDevice):
            if hasattr(disk, "busid"):
                disk_attrs.append(disk.busid)
        elif isinstance(disk, ZFCPDiskDevice):
            if hasattr(disk, "fcp_lun"):
                disk_attrs.append(disk.fcp_lun)
            if hasattr(disk, "wwpn"):
                disk_attrs.append(disk.wwpn)
            if hasattr(disk, "hba_id"):
                disk_attrs.append(disk.hba_id)

        # now append all additional attributes to our string
        for attr in disk_attrs:
            format_str += ", %s" % attr

        return format_str

    def input(self, args, key):
        """Grab the disk choice and update things"""
        self.errors = []
        if self._container.process_user_input(key):
            self.redraw()
            return InputState.PROCESSED
        else:
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                if self.selected_disks:
                    # check selected disks to see if we have any unformatted DASDs
                    # if we're on s390x, since they need to be formatted before we
                    # can use them.
                    if arch.is_s390():
                        _disks = [d for d in self.disks if d.name in self.selected_disks]
                        to_format = [d for d in _disks if d.type == "dasd" and
                                     blockdev.s390.dasd_needs_format(d.busid)]
                        if to_format:
                            self.run_dasdfmt(to_format)
                            self.redraw()
                            return InputState.PROCESSED

                    # make sure no containers were split up by the user's disk
                    # selection
                    self.errors.extend(checkDiskSelection(self.storage,
                                                          self.selected_disks))
                    if self.errors:
                        # The disk selection has to make sense before we can
                        # proceed.
                        self.redraw()
                        return InputState.PROCESSED

                    new_spoke = AutoPartSpoke(self.data, self.storage,
                                              self.payload, self.instclass)
                    ScreenHandler.push_screen_modal(new_spoke)
                    self.apply()
                    self.execute()
                    self.close()

                return InputState.PROCESSED
            else:
                return super(StorageSpoke, self).input(args, key)

    def run_dasdfmt(self, to_format):
        """
        This generates the list of DASDs requiring dasdfmt and runs dasdfmt
        against them.
        """
        # if the storage thread is running, wait on it to complete before taking
        # any further actions on devices; most likely to occur if user has
        # zerombr in their ks file
        threadMgr.wait(THREAD_STORAGE)

        # ask user to verify they want to format if zerombr not in ks file
        if not self.data.zerombr.zerombr:
            # prepare our msg strings; copied directly from dasdfmt.glade
            summary = _("The following unformatted DASDs have been detected on your system. You can choose to format them now with dasdfmt or cancel to leave them unformatted. Unformatted DASDs cannot be used during installation.\n\n")

            warntext = _("Warning: All storage changes made using the installer will be lost when you choose to format.\n\nProceed to run dasdfmt?\n")

            displaytext = summary + "\n".join("/dev/" + d.name for d in to_format) + "\n" + warntext

            # now show actual prompt; note -- in cmdline mode, auto-answer for
            # this is 'no', so unformatted DASDs will remain so unless zerombr
            # is added to the ks file
            question_window = YesNoDialog(displaytext)
            ScreenHandler.push_screen_modal(question_window)
            if not question_window.answer:
                # no? well fine then, back to the storage spoke with you;
                return None

        for disk in to_format:
            try:
                print(_("Formatting /dev/%s. This may take a moment.") % disk.name)
                blockdev.s390.dasd_format(disk.name)
            except blockdev.S390Error as err:
                # Log errors if formatting fails, but don't halt the installer
                log.error(str(err))
                continue

    def apply(self):
        self.autopart = self.data.autopart.autopart
        self.data.ignoredisk.onlyuse = self.selected_disks[:]
        self.data.clearpart.drives = self.selected_disks[:]

        if self.data.autopart.type is None:
            self.data.autopart.type = AUTOPART_TYPE_LVM

        if self.autopart:
            self.clearPartType = CLEARPART_TYPE_ALL
        else:
            self.clearPartType = CLEARPART_TYPE_NONE

        for disk in self.disks:
            if disk.name not in self.selected_disks and \
               disk in self.storage.devices:
                self.storage.devicetree.hide(disk)
            elif disk.name in self.selected_disks and \
                 disk not in self.storage.devices:
                self.storage.devicetree.unhide(disk)

        self.data.bootloader.location = "mbr"

        if self.data.bootloader.bootDrive and \
           self.data.bootloader.bootDrive not in self.selected_disks:
            self.data.bootloader.bootDrive = ""
            self.storage.bootloader.reset()

        self.storage.config.update(self.data)

        # If autopart is selected we want to remove whatever has been
        # created/scheduled to make room for autopart.
        # If custom is selected, we want to leave alone any storage layout the
        # user may have set up before now.
        self.storage.config.clear_non_existent = self.data.autopart.autopart

    def execute(self):
        print(_("Generating updated storage configuration"))
        try:
            doKickstartStorage(self.storage, self.data, self.instclass)
        except (StorageError, KickstartParseError) as e:
            log.error("storage configuration failed: %s", e)
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            self.data.bootloader.bootDrive = ""
            self.data.clearpart.type = CLEARPART_TYPE_ALL
            self.data.clearpart.initAll = False
            self.storage.config.update(self.data)
            self.storage.autopart_type = self.data.autopart.type
            self.storage.reset()
            # now set ksdata back to the user's specified config
            applyDiskSelection(self.storage, self.data, self.selected_disks)
        except BootLoaderError as e:
            log.error("BootLoader setup failed: %s", e)
            print(_("storage configuration failed: %s") % e)
            self.errors = [str(e)]
            self.data.bootloader.bootDrive = ""
        else:
            print(_("Checking storage configuration..."))
            report = storage_checker.check(self.storage)
            print("\n".join(report.all_errors))
            report.log(log)
            self.errors = report.errors
            self.warnings = report.warnings
        finally:
            resetCustomStorageData(self.data)
            self._ready = True

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        threadMgr.add(AnacondaThread(name=THREAD_STORAGE_WATCHER,
                                     target=self._initialize))

        self.selected_disks = self.data.ignoredisk.onlyuse[:]
        # Probably need something here to track which disks are selected?

    def _initialize(self):
        """
        Secondary initialize so wait for the storage thread to complete before
        populating our disk list
        """

        threadMgr.wait(THREAD_STORAGE)

        self.disks = sorted(getDisks(self.storage.devicetree),
                            key=lambda d: d.name)
        # if only one disk is available, go ahead and mark it as selected
        if len(self.disks) == 1:
            self._update_disk_list(self.disks[0])

        self._update_summary()
        self._ready = True

        # report that the storage spoke has been initialized
        self.initialize_done()
Ejemplo n.º 33
0
class SpecifyRepoSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Specify the repo URL here if closest mirror not selected. """
    category = SoftwareCategory

    HTTP = 1
    HTTPS = 2
    FTP = 3

    def __init__(self, data, storage, payload, protocol):
        NormalTUISpoke.__init__(self, data, storage, payload)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Specify Repo Options")
        self.protocol = protocol
        self._container = None
        self._url = self._get_url()

    def _get_url(self):
        """Get the URL of the current source."""
        source_proxy = self.payload.get_source_proxy()

        if source_proxy.Type == SOURCE_TYPE_URL:
            repo_configuration = RepoConfigurationData.from_structure(
                source_proxy.RepoConfiguration
            )

            return repo_configuration.url

        return ""

    def refresh(self, args=None):
        """ Refresh window. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(_("Repo URL"))
        self._container.add(EntryWidget(dialog.title, self._url), self._set_repo_url, dialog)

        self.window.add_with_separator(self._container)

    def _set_repo_url(self, dialog):
        self._url = dialog.run()

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return NormalTUISpoke.input(self, args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply all of our changes. """
        if self.protocol == SpecifyRepoSpoke.HTTP and not self._url.startswith("http://"):
            url = "http://" + self._url
        elif self.protocol == SpecifyRepoSpoke.HTTPS and not self._url.startswith("https://"):
            url = "https://" + self._url
        elif self.protocol == SpecifyRepoSpoke.FTP and not self._url.startswith("ftp://"):
            url = "ftp://" + self._url
        else:
            # protocol either unknown or entry already starts with a protocol
            # specification
            url = self._url
        self.set_source_url(url)
Ejemplo n.º 34
0
class SelectDeviceSpoke(NormalTUISpoke):
    """ Select device containing the install source ISO file. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, instclass):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("Select device containing the ISO file")
        self._container = None
        self._mountable_devices = self._get_mountable_devices()
        self._device = None

    @property
    def indirect(self):
        return True

    def _sanitize_model(self, model):
        return model.replace("_", " ")

    def _get_mountable_devices(self):
        disks = []
        fstring = "%(model)s %(path)s (%(size)s MB) %(format)s %(label)s"
        for dev in potentialHdisoSources(self.storage.devicetree):
            # path model size format type uuid of format
            dev_info = {"model": self._sanitize_model(dev.disk.model),
                        "path": dev.path,
                        "size": dev.size,
                        "format": dev.format.name or "",
                        "label": dev.format.label or dev.format.uuid or ""
                        }
            disks.append([dev, fstring % dev_info])
        return disks

    def refresh(self, args=None):
        super().refresh(args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        # check if the storage refresh thread is running
        if threadMgr.get(THREAD_STORAGE_WATCHER):
            # storage refresh is running - just report it
            # so that the user can refresh until it is done
            # TODO: refresh once the thread is done ?
            message = _(PAYLOAD_STATUS_PROBING_STORAGE)
            self.window.add_with_separator(TextWidget(message))

        # check if there are any mountable devices
        if self._mountable_devices:
            for d in self._mountable_devices:
                self._container.add(TextWidget(d[1]), callback=self._select_mountable_device, data=d[0])

            self.window.add_with_separator(self._container)

        else:
            message = _("No mountable devices found")
            self.window.add_with_separator(TextWidget(message))

    def _select_mountable_device(self, data):
        self._device = data
        new_spoke = SelectISOSpoke(self.data,
                                   self.storage, self.payload,
                                   self.instclass, self._device)
        ScreenHandler.push_screen_modal(new_spoke)
        self.close()

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # either the input was not a number or
            # we don't have the disk for the given number
            return super().input(args, key)

    # Override Spoke.apply
    def apply(self):
        pass
Ejemplo n.º 35
0
class SelectDeviceSpoke(NormalTUISpoke):
    """ Select device containing the install source ISO file. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Select device containing the ISO file")
        self._container = None
        self._device_tree = STORAGE.get_proxy(DEVICE_TREE)
        self._mountable_devices = self._get_mountable_devices()
        self._device = None

    @property
    def indirect(self):
        return True

    def _get_mountable_devices(self):
        disks = []

        for device_name in find_potential_hdiso_sources():
            device_info = get_hdiso_source_info(self._device_tree, device_name)
            device_desc = get_hdiso_source_description(device_info)
            disks.append([device_name, device_desc])

        return disks

    def refresh(self, args=None):
        super().refresh(args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        # check if the storage refresh thread is running
        if threadMgr.get(THREAD_STORAGE_WATCHER):
            # storage refresh is running - just report it
            # so that the user can refresh until it is done
            # TODO: refresh once the thread is done ?
            message = _(PAYLOAD_STATUS_PROBING_STORAGE)
            self.window.add_with_separator(TextWidget(message))

        # check if there are any mountable devices
        if self._mountable_devices:
            for d in self._mountable_devices:
                self._container.add(TextWidget(d[1]),
                                    callback=self._select_mountable_device,
                                    data=d[0])

            self.window.add_with_separator(self._container)

        else:
            message = _("No mountable devices found")
            self.window.add_with_separator(TextWidget(message))

    def _select_mountable_device(self, data):
        self._device = data
        new_spoke = SelectISOSpoke(self.data, self.storage, self.payload, self._device)
        ScreenHandler.push_screen_modal(new_spoke)
        self.close()

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # either the input was not a number or
            # we don't have the disk for the given number
            return super().input(args, key)

    # Override Spoke.apply
    def apply(self):
        pass
Ejemplo n.º 36
0
class SourceSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Spoke used to customize the install source repo.

       .. inheritance-diagram:: SourceSpoke
          :parts: 3
    """
    helpFile = "SourceSpoke.txt"
    category = SoftwareCategory

    SET_NETWORK_INSTALL_MODE = "network_install"

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Installation source")
        self._container = None
        self._ready = False
        self._error = False
        self._cdrom = None
        self._hmc = False

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        threadMgr.add(AnacondaThread(name=THREAD_SOURCE_WATCHER,
                                     target=self._initialize))
        payloadMgr.addListener(payloadMgr.STATE_ERROR, self._payload_error)

    def _initialize(self):
        """ Private initialize. """
        threadMgr.wait(THREAD_PAYLOAD)
        # If we've previously set up to use a CD/DVD method, the media has
        # already been mounted by payload.setup.  We can't try to mount it
        # again.  So just use what we already know to create the selector.
        # Otherwise, check to see if there's anything available.
        if self.data.method.method == "cdrom":
            self._cdrom = self.payload.install_device
        elif not flags.automatedInstall:
            self._cdrom = opticalInstallMedia(self.storage.devicetree)

        # Enable the SE/HMC option.
        if flags.hmc:
            self._hmc = True

        self._ready = True

        # report that the source spoke has been initialized
        self.initialize_done()

    def _payload_error(self):
        self._error = True

    def _repo_status(self):
        """ Return a string describing repo url or lack of one. """
        method = self.data.method
        if method.method == "url":
            return method.url or method.mirrorlist or method.metalink
        elif method.method == "nfs":
            return _("NFS server %s") % method.server
        elif method.method == "cdrom":
            return _("Local media")
        elif method.method == "hmc":
            return _("Local media via SE/HMC")
        elif method.method == "harddrive":
            if not method.dir:
                return _("Error setting up software source")
            return os.path.basename(method.dir)
        elif self.payload.baseRepo:
            return _("Closest mirror")
        else:
            return _("Nothing selected")

    @property
    def showable(self):
        return isinstance(self.payload, PackagePayload)

    @property
    def status(self):
        if self._error:
            return _("Error setting up software source")
        elif not self.ready:
            return _("Processing...")
        else:
            return self._repo_status()

    @property
    def completed(self):
        if flags.automatedInstall and self.ready and not self.payload.baseRepo:
            return False
        else:
            return not self._error and self.ready and (self.data.method.method or self.payload.baseRepo)

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        threadMgr.wait(THREAD_PAYLOAD)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        if self.data.method.method == "harddrive" and \
           get_mount_device(DRACUT_ISODIR) == get_mount_device(DRACUT_REPODIR):
            message = _("The installation source is in use by the installer and cannot be changed.")
            self.window.add_with_separator(TextWidget(message))
            return

        if args == self.SET_NETWORK_INSTALL_MODE:
            if self.payload.mirrors_available:
                self._container.add(TextWidget(_("Closest mirror")), self._set_network_close_mirror)
            self._container.add(TextWidget("http://"), self._set_network_url, SpecifyRepoSpoke.HTTP)
            self._container.add(TextWidget("https://"), self._set_network_url, SpecifyRepoSpoke.HTTPS)
            self._container.add(TextWidget("ftp://"), self._set_network_url, SpecifyRepoSpoke.FTP)
            self._container.add(TextWidget("nfs"), self._set_network_nfs)
        else:
            self.window.add(TextWidget(_("Choose an installation source type.")))
            self._container.add(TextWidget(_("CD/DVD")), self._set_cd_install_source)
            self._container.add(TextWidget(_("local ISO file")), self._set_iso_install_source)
            self._container.add(TextWidget(_("Network")), self._set_network_install_source)

            if self._hmc:
                self._container.add(TextWidget(_("SE/HMC")), self._set_hmc_install_source)

        self.window.add_with_separator(self._container)

    # Set installation source callbacks

    def _set_cd_install_source(self, data):
        self.set_source_cdrom()
        self.payload.install_device = self._cdrom
        self.apply()
        self.close()

    def _set_hmc_install_source(self, data):
        self.set_source_hmc()
        self.apply()
        self.close()

    def _set_iso_install_source(self, data):
        new_spoke = SelectDeviceSpoke(self.data,
                                      self.storage, self.payload,
                                      self.instclass)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_install_source(self, data):
        ScreenHandler.replace_screen(self, self.SET_NETWORK_INSTALL_MODE)

    # Set network source callbacks

    def _set_network_close_mirror(self, data):
        self.set_source_closest_mirror()
        self.apply()
        self.close()

    def _set_network_url(self, data):
        new_spoke = SpecifyRepoSpoke(self.data, self.storage,
                                     self.payload, self.instclass, data)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_nfs(self, data):
        self.set_source_nfs()
        new_spoke = SpecifyNFSRepoSpoke(self.data, self.storage,
                                        self.payload, self.instclass, self._error)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def input(self, args, key):
        """ Handle the input; this decides the repo source. """
        if not self._container.process_user_input(key):
            return super().input(args, key)

        return InputState.PROCESSED

    @property
    def ready(self):
        """ Check if the spoke is ready. """
        return (self._ready and
                not threadMgr.get(THREAD_PAYLOAD) and
                not threadMgr.get(THREAD_CHECK_SOFTWARE))

    def apply(self):
        """ Execute the selections made. """
        # If askmethod was provided on the command line, entering the source
        # spoke wipes that out.
        if flags.askmethod:
            flags.askmethod = False

        # if we had any errors, e.g. from a previous attempt to set the source,
        # clear them at this point
        self._error = False

        payloadMgr.restartThread(self.storage, self.data, self.payload, self.instclass,
                                 checkmount=False)
Ejemplo n.º 37
0
class SourceSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Spoke used to customize the install source repo.

       .. inheritance-diagram:: SourceSpoke
          :parts: 3
    """
    helpFile = "SourceSpoke.txt"
    category = SoftwareCategory

    SET_NETWORK_INSTALL_MODE = "network_install"

    @classmethod
    def should_run(cls, environment, data):
        """Don't run for any non-package payload."""
        if not NormalTUISpoke.should_run(environment, data):
            return False

        return context.payload_type == PAYLOAD_TYPE_DNF

    def __init__(self, data, storage, payload):
        NormalTUISpoke.__init__(self, data, storage, payload)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Installation source")
        self._container = None
        self._ready = False
        self._error = False
        self._hmc = False

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        threadMgr.add(AnacondaThread(name=THREAD_SOURCE_WATCHER,
                                     target=self._initialize))
        payloadMgr.add_listener(PayloadState.ERROR, self._payload_error)

    def _initialize(self):
        """ Private initialize. """
        threadMgr.wait(THREAD_PAYLOAD)

        # Enable the SE/HMC option.
        if self.payload.source_type == SOURCE_TYPE_HMC:
            self._hmc = True

        self._ready = True

        # report that the source spoke has been initialized
        self.initialize_done()

    def _payload_error(self):
        self._error = True

    @property
    def status(self):
        if self._error:
            return _("Error setting up software source")
        elif not self.ready:
            return _("Processing...")
        elif not self.payload.is_complete():
            return _("Nothing selected")
        else:
            source_proxy = self.payload.get_source_proxy()
            return source_proxy.Description

    @property
    def completed(self):
        if flags.automatedInstall and self.ready and not self.payload.base_repo:
            return False

        return not self._error and self.ready and self.payload.is_complete()

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)
        threadMgr.wait(THREAD_PAYLOAD)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        if args == self.SET_NETWORK_INSTALL_MODE:
            if conf.payload.enable_closest_mirror:
                self._container.add(TextWidget(_("Closest mirror")),
                                    self._set_network_close_mirror)

            self._container.add(TextWidget("http://"),
                                self._set_network_url,
                                SpecifyRepoSpoke.HTTP)
            self._container.add(TextWidget("https://"),
                                self._set_network_url,
                                SpecifyRepoSpoke.HTTPS)
            self._container.add(TextWidget("ftp://"),
                                self._set_network_url,
                                SpecifyRepoSpoke.FTP)
            self._container.add(TextWidget("nfs"),
                                self._set_network_nfs)
        else:
            self.window.add(TextWidget(_("Choose an installation source type.")))
            self._container.add(TextWidget(_("CD/DVD")), self._set_cd_install_source)
            self._container.add(TextWidget(_("local ISO file")), self._set_iso_install_source)
            self._container.add(TextWidget(_("Network")), self._set_network_install_source)

            if self._hmc:
                self._container.add(TextWidget(_("SE/HMC")), self._set_hmc_install_source)

        self.window.add_with_separator(self._container)

    # Set installation source callbacks

    def _set_cd_install_source(self, data):
        self.set_source_cdrom()
        self.apply()
        self.close()

    def _set_hmc_install_source(self, data):
        self.set_source_hmc()
        self.apply()
        self.close()

    def _set_iso_install_source(self, data):
        new_spoke = SelectDeviceSpoke(self.data, self.storage, self.payload)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_install_source(self, data):
        ScreenHandler.replace_screen(self, self.SET_NETWORK_INSTALL_MODE)

    # Set network source callbacks

    def _set_network_close_mirror(self, data):
        self.set_source_closest_mirror()
        self.apply()
        self.close()

    def _set_network_url(self, data):
        new_spoke = SpecifyRepoSpoke(self.data, self.storage, self.payload, data)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_nfs(self, data):
        new_spoke = SpecifyNFSRepoSpoke(self.data, self.storage, self.payload, self._error)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def input(self, args, key):
        """ Handle the input; this decides the repo source. """
        if not self._container.process_user_input(key):
            return super().input(args, key)

        return InputState.PROCESSED

    @property
    def ready(self):
        """ Check if the spoke is ready. """
        return (self._ready and
                not threadMgr.get(THREAD_PAYLOAD) and
                not threadMgr.get(THREAD_CHECK_SOFTWARE))

    def apply(self):
        """ Execute the selections made. """
        # if we had any errors, e.g. from a previous attempt to set the source,
        # clear them at this point
        self._error = False

        payloadMgr.restart_thread(self.payload, checkmount=False)
Ejemplo n.º 38
0
class MountPointAssignSpoke(NormalTUISpoke):
    """ Assign mount points to block devices. """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Assign mount points")
        self._container = None
        self._mds = None

        self._gather_mount_data_info()

    def _is_dev_usable(self, dev):
        maybe = not dev.protected and dev.size != Size(0)
        if maybe and self.data.ignoredisk.onlyuse:
            # all device's disks have to be in ignoredisk.onlyuse
            maybe = set(self.data.ignoredisk.onlyuse).issuperset(
                {d.name
                 for d in dev.disks})

        return maybe

    def _gather_mount_data_info(self):
        self._mds = OrderedDict()

        for device in filter(self._is_dev_usable,
                             self.storage.devicetree.leaves):
            fmt = device.format.type

            for ks_md in self.data.mount.dataList():
                if device is self.storage.devicetree.resolve_device(
                        ks_md.device):
                    # already have a configuration for the device in ksdata,
                    # let's just copy it
                    mdrec = MountDataRecorder(device=ks_md.device,
                                              mount_point=ks_md.mount_point,
                                              format=ks_md.format,
                                              reformat=ks_md.reformat)
                    # and make sure the new version is put back
                    self.data.mount.remove_mount_data(ks_md)
                    mdrec.modified = True
                    break
            else:
                if device.format.mountable and device.format.mountpoint:
                    mpoint = device.format.mountpoint
                else:
                    mpoint = None

                mdrec = MountDataRecorder(device=device.path,
                                          mount_point=mpoint,
                                          format=fmt,
                                          reformat=False)

            mdrec.orig_format = fmt
            self._mds[device.name] = mdrec

    @property
    def indirect(self):
        return True

    def prompt(self, args=None):
        prompt = super(MountPointAssignSpoke, self).prompt(args)
        # TRANSLATORS: 's' to rescan devices
        prompt.add_option(C_('TUI|Spoke Navigation|Partitioning', 's'),
                          _("rescan devices"))
        return prompt

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(2)

        for md in self._mds.values():
            device = self.storage.devicetree.resolve_device(md.device)
            devspec = "%s (%s)" % (md.device, device.size)
            if md.format:
                devspec += "\n %s" % md.format
                if md.reformat:
                    devspec += "*"
                if md.mount_point:
                    devspec += ", %s" % md.mount_point
            w = TextWidget(devspec)
            self._container.add(w, self._configure_device, device)

        self.window.add_with_separator(self._container)

        message = _(
            "Choose device from above to assign mount point and set format.\n"
            +
            "Formats marked with * are new formats meaning ALL DATA on the original format WILL BE LOST!"
        )
        self.window.add_with_separator(TextWidget(message))

    def _configure_device(self, device):
        md = self._mds[device.name]
        new_spoke = ConfigureDeviceSpoke(self.data, self.storage, self.payload,
                                         self.instclass, md)
        ScreenHandler.push_screen(new_spoke)

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            # TRANSLATORS: 's' to rescan devices
            if key.lower() == C_('TUI|Spoke Navigation|Partitioning', 's'):
                text = _(
                    "Warning: This will revert all changes done so far.\nDo you want to proceed?\n"
                )
                question_window = YesNoDialog(text)
                ScreenHandler.push_screen_modal(question_window)
                if question_window.answer:
                    # unset self.data.ignoredisk.onlyuse temporarily so that
                    # storage_initialize() processes all devices
                    ignoredisk = self.data.ignoredisk.onlyuse
                    self.data.ignoredisk.onlyuse = []

                    print(_("Scanning disks. This may take a moment..."))
                    storage_initialize(
                        self.storage, self.data,
                        self.storage.devicetree.protected_dev_names)

                    self.data.ignoredisk.onlyuse = ignoredisk
                    self.data.mount.clear_mount_data()
                    self._gather_mount_data_info()
                self.redraw()
                return InputState.PROCESSED
            # TRANSLATORS: 'c' to continue
            elif key.lower() == C_('TUI|Spoke Navigation', 'c'):
                self.apply()

            return super(MountPointAssignSpoke, self).input(args, key)

        return InputState.PROCESSED

    def apply(self):
        """ Apply our selections. """
        for mount_data in self._mds.values():
            if mount_data.modified and (mount_data.reformat
                                        or mount_data.mount_point):
                self.data.mount.add_mount_data(mount_data)
Ejemplo n.º 39
0
class NetworkSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """ Spoke used to configure network settings.

       .. inheritance-diagram:: NetworkSpoke
          :parts: 3
    """
    helpFile = "NetworkSpoke.txt"
    category = SystemCategory
    configurable_device_types = [
        NM.DeviceType.ETHERNET,
        NM.DeviceType.INFINIBAND,
    ]

    def __init__(self, data, storage, payload):
        NormalTUISpoke.__init__(self, data, storage, payload)
        self.title = N_("Network configuration")
        self._network_module = NETWORK.get_proxy()

        self.nm_client = network.get_nm_client()
        if not self.nm_client and conf.system.provides_system_bus:
            self.nm_client = NM.Client.new(None)

        self._container = None
        self.hostname = self._network_module.Hostname
        self.editable_configurations = []
        self.errors = []
        self._apply = False

    @classmethod
    def should_run(cls, environment, data):
        """Should the spoke run?"""
        if not FirstbootSpokeMixIn.should_run(environment, data):
            return False

        return conf.system.can_configure_network

    def initialize(self):
        self.initialize_start()
        NormalTUISpoke.initialize(self)
        self._update_editable_configurations()
        self._network_module.DeviceConfigurationChanged.connect(
            self._device_configurations_changed)
        self.initialize_done()

    def _device_configurations_changed(self, device_configurations):
        log.debug("device configurations changed: %s", device_configurations)
        self._update_editable_configurations()

    def _update_editable_configurations(self):
        device_configurations = NetworkDeviceConfiguration.from_structure_list(
            self._network_module.GetDeviceConfigurations())
        self.editable_configurations = [
            dc for dc in device_configurations
            if dc.device_type in self.configurable_device_types
        ]

    @property
    def completed(self):
        """ Check whether this spoke is complete or not."""
        # If we can't configure network, don't require it
        return (not conf.system.can_configure_network
                or self._network_module.GetActivatedInterfaces())

    @property
    def mandatory(self):
        # the network spoke should be mandatory only if it is running
        # during the installation and if the installation source requires network
        return ANACONDA_ENVIRON in flags.environs and self.payload.needs_network

    @property
    def status(self):
        """ Short msg telling what devices are active. """
        return network.status_message(self.nm_client)

    def _summary_text(self):
        """Devices cofiguration shown to user."""
        msg = ""
        activated_devs = self._network_module.GetActivatedInterfaces()
        for device_configuration in self.editable_configurations:
            name = device_configuration.device_name
            if name in activated_devs:
                msg += self._activated_device_msg(name)
            else:
                msg += _("Wired (%(interface_name)s) disconnected\n") \
                       % {"interface_name": name}
        return msg

    def _activated_device_msg(self, devname):
        msg = _("Wired (%(interface_name)s) connected\n") \
              % {"interface_name": devname}

        device = self.nm_client.get_device_by_iface(devname)
        if device:
            addr_str = dnss_str = gateway_str = netmask_str = ""
            ipv4config = device.get_ip4_config()
            if ipv4config:
                addresses = ipv4config.get_addresses()
                if addresses:
                    a0 = addresses[0]
                    addr_str = a0.get_address()
                    prefix = a0.get_prefix()
                    netmask_str = network.prefix_to_netmask(prefix)
                gateway_str = ipv4config.get_gateway() or ''
                dnss_str = ",".join(ipv4config.get_nameservers())
            msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
                {"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
            msg += _(" DNS: %s\n") % dnss_str

            ipv6config = device.get_ip6_config()
            if ipv6config:
                for address in ipv6config.get_addresses():
                    addr_str = address.get_address()
                    prefix = address.get_prefix()
                    # Do not display link-local addresses
                    if not addr_str.startswith("fe80:"):
                        msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
                            {"addr": addr_str, "prefix": prefix}
        return msg

    def refresh(self, args=None):
        """ Refresh screen. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        if not self.nm_client:
            self.window.add_with_separator(
                TextWidget(_("Network configuration is not available.")))
            return

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        hostname = _("Host Name: %s\n") % self._network_module.Hostname
        self.window.add_with_separator(TextWidget(hostname))
        current_hostname = _("Current host name: %s\n"
                             ) % self._network_module.GetCurrentHostname()
        self.window.add_with_separator(TextWidget(current_hostname))

        # if we have any errors, display them
        while len(self.errors) > 0:
            self.window.add_with_separator(TextWidget(self.errors.pop()))

        dialog = Dialog(_("Host Name"))
        self._container.add(TextWidget(_("Set host name")),
                            callback=self._set_hostname_callback,
                            data=dialog)

        for device_configuration in self.editable_configurations:
            iface = device_configuration.device_name
            text = (_("Configure device %s") % iface)
            self._container.add(TextWidget(text),
                                callback=self._ensure_connection_and_configure,
                                data=iface)

        self.window.add_with_separator(self._container)

    def _set_hostname_callback(self, dialog):
        self.hostname = dialog.run()
        self.redraw()
        self.apply()

    def _ensure_connection_and_configure(self, iface):
        for device_configuration in self.editable_configurations:
            if device_configuration.device_name == iface:
                connection_uuid = device_configuration.connection_uuid
                if connection_uuid:
                    self._configure_connection(iface, connection_uuid)
                else:
                    device_type = self.nm_client.get_device_by_iface(
                        iface).get_device_type()
                    connection = get_default_connection(iface, device_type)
                    connection_uuid = connection.get_uuid()
                    log.debug("adding default connection %s for %s",
                              connection_uuid, iface)
                    data = (iface, connection_uuid)
                    self.nm_client.add_connection2(
                        connection.to_dbus(
                            NM.ConnectionSerializationFlags.ALL),
                        (NM.SettingsAddConnection2Flags.TO_DISK
                         | NM.SettingsAddConnection2Flags.BLOCK_AUTOCONNECT),
                        None, False, None, self._default_connection_added_cb,
                        data)
                return
        log.error("device configuration for %s not found", iface)

    def _default_connection_added_cb(self, client, result, data):
        iface, connection_uuid = data
        try:
            _connection, result = client.add_connection2_finish(result)
        except Exception as e:  # pylint: disable=broad-except
            msg = "adding default connection {} from {} failed: {}".format(
                connection_uuid, iface, str(e))
            log.error(msg)
            self.errors.append(msg)
            self.redraw()
        else:
            log.debug("added default connection %s for %s: %s",
                      connection_uuid, iface, result)
            self._configure_connection(iface, connection_uuid)

    def _configure_connection(self, iface, connection_uuid):
        connection = self.nm_client.get_connection_by_uuid(connection_uuid)

        new_spoke = ConfigureDeviceSpoke(self.data, self.storage, self.payload,
                                         self._network_module, iface,
                                         connection)
        ScreenHandler.push_screen_modal(new_spoke)

        if new_spoke.errors:
            self.errors.extend(new_spoke.errors)
            self.redraw()
            return

        if new_spoke.apply_configuration:
            self._apply = True
            device = self.nm_client.get_device_by_iface(iface)
            log.debug("activating connection %s with device %s",
                      connection_uuid, iface)
            self.nm_client.activate_connection_async(connection, device, None,
                                                     None)

        self._network_module.LogConfigurationState(
            "Settings of {} updated in TUI.".format(iface))

        self.redraw()
        self.apply()

    def input(self, args, key):
        """ Handle the input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        """Apply all of our settings."""
        # Inform network module that device configurations might have been changed
        # and we want to generate kickstart from device configurations
        # (persistent NM / config files configuration), instead of using original kickstart.
        self._network_module.NetworkDeviceConfigurationChanged()

        (valid, error) = network.is_valid_hostname(self.hostname, local=True)
        if not self.hostname or valid:
            self._network_module.SetHostname(self.hostname)
        else:
            self.errors.append(_("Host name is not valid: %s") % error)
            self.hostname = self._network_module.Hostname

        if self._apply:
            self._apply = False
            if ANACONDA_ENVIRON in flags.environs:
                from pyanaconda.payload.manager import payloadMgr
                payloadMgr.restart_thread(self.payload, checkmount=False)
Ejemplo n.º 40
0
class ConfigureDeviceSpoke(NormalTUISpoke):
    """ Assign mount point to a block device and (optionally) reformat it. """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass, mount_data):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self._container = None
        self._mount_data = mount_data
        self.title = N_("Configure device: %s") % mount_data.device

        self._supported_filesystems = [
            fmt.type for fmt in get_supported_filesystems()
        ]

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        mount_point_title = _("Mount point")
        reformat_title = _("Reformat")
        none_msg = _("none")

        fmt = get_format(self._mount_data.format)
        if fmt and fmt.mountable:
            dialog = Dialog(mount_point_title,
                            conditions=[self._check_assign_mount_point])
            value = self._mount_data.mount_point or none_msg
            self._container.add(EntryWidget(dialog.title, value),
                                self._assign_mount_point, dialog)
        elif fmt and fmt.type is None:
            # mount point cannot be set for no format
            # (fmt.name = "Uknown" in this case which would look weird)
            self._container.add(EntryWidget(mount_point_title, none_msg),
                                lambda x: self.redraw())
        else:
            # mount point cannot be set for format that is not mountable, just
            # show the format's name in square brackets instead
            self._container.add(EntryWidget(mount_point_title, fmt.name),
                                lambda x: self.redraw())

        dialog = Dialog(_("Format"), conditions=[self._check_format])
        value = self._mount_data.format or none_msg
        self._container.add(EntryWidget(dialog.title, value), self._set_format,
                            dialog)

        if ((self._mount_data.orig_format
             and self._mount_data.orig_format != self._mount_data.format)
                or self._mount_data.mount_point == "/"):
            # changing format implies reformat and so does "/" mount point
            self._container.add(
                CheckboxWidget(title=reformat_title,
                               completed=self._mount_data.reformat))
        else:
            self._container.add(
                CheckboxWidget(title=reformat_title,
                               completed=self._mount_data.reformat),
                self._switch_reformat)

        self.window.add_with_separator(self._container)
        self.window.add_with_separator(
            TextWidget(
                _("Choose from above to assign mount point and/or set format.")
            ))

    def _check_format(self, user_input, report_func):
        user_input = user_input.lower()
        if user_input in self._supported_filesystems:
            return True
        else:
            msg = _("Invalid or unsupported format given")
            msg += "\n"
            msg += (_("Supported formats: %s") %
                    ", ".join(self._supported_filesystems))
            report_func(msg)
            return False

    def _check_assign_mount_point(self, user_input, report_func):
        # a valid mount point must start with / or user set nothing
        if user_input == "" or user_input.startswith("/"):
            return True
        else:
            report_func(_("Invalid mount point given"))
            return False

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            return super(ConfigureDeviceSpoke, self).input(args, key)

        self.redraw()
        return InputState.PROCESSED

    def apply(self):
        # nothing to do here, the callbacks below directly modify the data
        pass

    def _switch_reformat(self, args):
        self._mount_data.modified = True
        self._mount_data.reformat = not self._mount_data.reformat

    def _set_format(self, dialog):
        self._mount_data.modified = True
        value = dialog.run()

        if value != self._mount_data.format:
            self._mount_data.reformat = True
            self._mount_data.format = value

    def _assign_mount_point(self, dialog):
        self._mount_data.modified = True
        value = dialog.run()

        if value:
            self._mount_data.mount_point = value
        else:
            self._mount_data.mount_point = None
        if self._mount_data.mount_point == "/":
            self._mount_data.reformat = True
Ejemplo n.º 41
0
class ConfigureNetworkSpoke(NormalTUISpoke):
    """ Spoke to set various configuration options for net devices. """
    category = "network"

    def __init__(self, data, storage, payload, instclass, network_data):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("Device configuration")

        self.network_data = network_data
        if self.network_data.bootProto == "dhcp":
            self.network_data.ip = "dhcp"
        if self.network_data.noipv6:
            self.network_data.ipv6 = "ignore"

        self.apply_configuration = False
        self._container = None

    def refresh(self, args=None):
        """ Refresh window. """
        super().refresh(args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=(_('IPv4 address or %s for DHCP') % '"dhcp"'),
                        conditions=[self._check_ipv4_or_dhcp])
        self._container.add(EntryWidget(dialog.title, self.network_data.ip), self._set_ipv4_or_dhcp, dialog)

        dialog = Dialog(title=_("IPv4 netmask"), conditions=[self._check_netmask])
        self._container.add(EntryWidget(dialog.title, self.network_data.netmask), self._set_netmask, dialog)

        dialog = Dialog(title=_("IPv4 gateway"), conditions=[self._check_ipv4])
        self._container.add(EntryWidget(dialog.title, self.network_data.gateway), self._set_ipv4_gateway, dialog)

        msg = (_('IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, '
                 '%(ignore)s to turn off')
               % {"auto": '"auto"', "dhcp": '"dhcp"', "ignore": '"ignore"'})
        dialog = Dialog(title=msg, conditions=[self._check_ipv6_config])
        self._container.add(EntryWidget(dialog.title, self.network_data.ipv6), self._set_ipv6, dialog)

        dialog = Dialog(title=_("IPv6 default gateway"), conditions=[self._check_ipv6])
        self._container.add(EntryWidget(dialog.title, self.network_data.ipv6gateway), self._set_ipv6_gateway, dialog)

        dialog = Dialog(title=_("Nameservers (comma separated)"), conditions=[self._check_nameservers])
        self._container.add(EntryWidget(dialog.title, self.network_data.nameserver), self._set_nameservers, dialog)

        msg = _("Connect automatically after reboot")
        w = CheckboxWidget(title=msg, completed=self.network_data.onboot)
        self._container.add(w, self._set_onboot_handler)

        msg = _("Apply configuration in installer")
        w = CheckboxWidget(title=msg, completed=self.apply_configuration)
        self._container.add(w, self._set_apply_handler)

        self.window.add_with_separator(self._container)

        message = _("Configuring device %s.") % self.network_data.device
        self.window.add_with_separator(TextWidget(message))

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4_or_dhcp(self, user_input, report_func):
        return IPV4_OR_DHCP_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4(self, user_input, report_func):
        return IPV4_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=NETMASK_ERROR_MSG)
    def _check_netmask(self, user_input, report_func):
        return IPV4_NETMASK_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6(self, user_input, report_func):
        return network.check_ip_address(user_input, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6_config(self, user_input, report_func):
        if user_input in ["auto", "dhcp", "ignore"]:
            return True
        addr, _slash, prefix = user_input.partition("/")
        if prefix:
            try:
                if not 1 <= int(prefix) <= 128:
                    return False
            except ValueError:
                return False
        return network.check_ip_address(addr, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_nameservers(self, user_input, report_func):
        if user_input.strip():
            addresses = [str.strip(i) for i in user_input.split(",")]
            for ip in addresses:
                if not network.check_ip_address(ip):
                    return False
        return True

    def _set_ipv4_or_dhcp(self, dialog):
        self.network_data.ip = dialog.run()

    def _set_netmask(self, dialog):
        self.network_data.netmask = dialog.run()

    def _set_ipv4_gateway(self, dialog):
        self.network_data.gateway = dialog.run()

    def _set_ipv6(self, dialog):
        self.network_data.ipv6 = dialog.run()

    def _set_ipv6_gateway(self, dialog):
        self.network_data.ipv6gateway = dialog.run()

    def _set_nameservers(self, dialog):
        self.network_data.nameserver = dialog.run()

    def _set_apply_handler(self, args):
        self.apply_configuration = not self.apply_configuration

    def _set_onboot_handler(self, args):
        self.network_data.onboot = not self.network_data.onboot

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return super().input(args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply our changes. """
        # save this back to network data, this will be applied in upper layer
        pass
Ejemplo n.º 42
0
class TimeSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    helpFile = "DateTimeSpoke.txt"
    category = LocalizationCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Time settings")
        self._timezone_spoke = None
        self._container = None
        # we use an ordered dict to keep the NTP server insertion order
        self._ntp_servers = OrderedDict()
        self._ntp_servers_lock = RLock()

    @property
    def indirect(self):
        return False

    def initialize(self):
        self.initialize_start()
        # We get the initial NTP servers (if any):
        # - from kickstart when running inside of Anaconda
        #   during the installation
        # - from config files when running in Initial Setup
        #   after the installation
        ntp_servers = []

        if constants.ANACONDA_ENVIRON in flags.environs:
            ntp_servers = self.data.timezone.ntpservers
        elif constants.FIRSTBOOT_ENVIRON in flags.environs:
            ntp_servers = ntp.get_servers_from_config()[
                1]  # returns a (NPT pools, NTP servers) tupple
        else:
            log.error(
                "tui time spoke: unsupported environment configuration %s,"
                "can't decide where to get initial NTP servers",
                flags.environs)

        # check if the NTP servers appear to be working or not
        if ntp_servers:
            for server in ntp_servers:
                self._ntp_servers[server] = constants.NTP_SERVER_QUERY

            # check if the newly added NTP servers work fine
            self._check_ntp_servers_async(self._ntp_servers.keys())

        # we assume that the NTP spoke is initialized enough even if some NTP
        # server check threads might still be running
        self.initialize_done()

    def _check_ntp_servers_async(self, servers):
        """Asynchronously check if given NTP servers appear to be working.

        :param list servers: list of servers to check
        """
        for server in servers:
            threadMgr.add(
                AnacondaThread(prefix=constants.THREAD_NTP_SERVER_CHECK,
                               target=self._check_ntp_server,
                               args=(server, )))

    def _check_ntp_server(self, server):
        """Check if an NTP server appears to be working.

        :param str server: NTP server address
        :returns: True if the server appears to be working, False if not
        :rtype: bool
        """
        log.debug("checking NTP server %s", server)
        result = ntp.ntp_server_working(server)
        if result:
            log.debug("NTP server %s appears to be working", server)
            self.set_ntp_server_status(server, constants.NTP_SERVER_OK)
        else:
            log.debug("NTP server %s appears not to be working", server)
            self.set_ntp_server_status(server, constants.NTP_SERVER_NOK)

    @property
    def ntp_servers(self):
        """Return a list of NTP servers known to the Time spoke.

        :returns: a list of NTP servers
        :rtype: list of strings
        """
        return self._ntp_servers

    def add_ntp_server(self, server):
        """Add NTP server address to our internal NTP server tracking dictionary.

        :param str server: NTP server address to add
        """
        # the add & remove operations should (at least at the moment) be never
        # called from different threads at the same time, but lets just use
        # a lock there when we are at it
        with self._ntp_servers_lock:
            if server not in self._ntp_servers:
                self._ntp_servers[server] = constants.NTP_SERVER_QUERY
                self._check_ntp_servers_async([server])

    def remove_ntp_server(self, server):
        """Remove NTP server address from our internal NTP server tracking dictionary.

        :param str server: NTP server address to remove
        """
        # the remove-server and set-server-status operations need to be atomic,
        # so that we avoid reintroducing removed servers by setting their status
        with self._ntp_servers_lock:
            if server in self._ntp_servers:
                del self._ntp_servers[server]

    def set_ntp_server_status(self, server, status):
        """Set status for an NTP server in the NTP server dict.

        The status can be "working", "not working" or "check in progress",
        and is defined by three constants defined in constants.py.

        :param str server: an NTP server
        :param int status: status of the NTP server
        """

        # the remove-server and set-server-status operations need to be atomic,
        # so that we avoid reintroducing removed server by setting their status
        with self._ntp_servers_lock:
            if server in self._ntp_servers:
                self._ntp_servers[server] = status

    @property
    def timezone_spoke(self):
        if not self._timezone_spoke:
            self._timezone_spoke = TimeZoneSpoke(self.data, self.storage,
                                                 self.payload, self.instclass)
        return self._timezone_spoke

    @property
    def completed(self):
        return bool(self.data.timezone.timezone)

    @property
    def mandatory(self):
        return True

    @property
    def status(self):
        if self.data.timezone.timezone:
            return _("%s timezone") % self.data.timezone.timezone
        else:
            return _("Timezone is not set.")

    def _summary_text(self):
        """Return summary of current timezone & NTP configuration.

        :returns: current status
        :rtype: str
        """
        msg = ""
        # timezone
        timezone_msg = _("not set")
        if self.data.timezone.timezone:
            timezone_msg = self.data.timezone.timezone

        msg += _("Timezone: %s\n") % timezone_msg

        # newline section separator
        msg += "\n"

        # NTP
        msg += _("NTP servers:")
        if self._ntp_servers:
            for status in format_ntp_status_list(self._ntp_servers):
                msg += "\n%s" % status
        else:
            msg += _("not configured")

        return msg

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        if self.data.timezone.timezone:
            timezone_option = _("Change timezone")
        else:
            timezone_option = _("Set timezone")

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        self._container.add(TextWidget(timezone_option),
                            callback=self._timezone_callback)
        self._container.add(TextWidget(_("Configure NTP servers")),
                            callback=self._configure_ntp_server_callback)

        self.window.add_with_separator(self._container)

    def _timezone_callback(self, data):
        ScreenHandler.push_screen_modal(self.timezone_spoke)
        self.close()

    def _configure_ntp_server_callback(self, data):
        new_spoke = NTPServersSpoke(self.data, self.storage, self.payload,
                                    self.instclass, self)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def input(self, args, key):
        """ Handle the input - visit a sub spoke or go back to hub."""
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super(TimeSpoke, self).input(args, key)

    def apply(self):
        # update the NTP server list in kickstart
        self.data.timezone.ntpservers = list(self.ntp_servers.keys())
Ejemplo n.º 43
0
class UserSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
       .. inheritance-diagram:: UserSpoke
          :parts: 3
    """
    helpFile = "UserSpoke.txt"
    category = UserSettingsCategory

    @classmethod
    def should_run(cls, environment, data):
        # the user spoke should run always in the anaconda and in firstboot only
        # when doing reconfig or if no user has been created in the installation
        if environment == ANACONDA_ENVIRON:
            return True
        elif environment == FIRSTBOOT_ENVIRON and data is None:
            # cannot decide, stay in the game and let another call with data
            # available (will come) decide
            return True
        elif environment == FIRSTBOOT_ENVIRON and data and \
                (data.firstboot.firstboot == FIRSTBOOT_RECONFIG or
                 len(data.user.userList) == 0):
            return True
        else:
            return False

    def __init__(self, data, storage, payload, instclass):
        FirstbootSpokeMixIn.__init__(self)
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.initialize_start()

        self.title = N_("User creation")
        self._container = None

        if self.data.user.userList:
            self._user_data = self.data.user.userList[0]
            self._create_user = True
        else:
            self._user_data = self.data.UserData()
            self._create_user = False

        self._use_password = self._user_data.isCrypted or self._user_data.password
        self._groups = ""
        self._is_admin = False
        self._policy = self.data.anaconda.pwpolicy.get_policy(
            "user", fallback_to_default=True)

        self.errors = []

        self.initialize_done()

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)
        self._is_admin = "wheel" in self._user_data.groups
        self._groups = ", ".join(self._user_data.groups)

        self._container = ListColumnContainer(1)

        w = CheckboxWidget(title=_("Create user"), completed=self._create_user)
        self._container.add(w, self._set_create_user)

        if self._create_user:
            dialog = Dialog(title=_("Full name"),
                            conditions=[self._check_fullname])
            self._container.add(
                EntryWidget(dialog.title, self._user_data.gecos),
                self._set_fullname, dialog)

            dialog = Dialog(title=_("User name"),
                            conditions=[self._check_username])
            self._container.add(
                EntryWidget(dialog.title, self._user_data.name),
                self._set_username, dialog)

            w = CheckboxWidget(title=_("Use password"),
                               completed=self._use_password)
            self._container.add(w, self._set_use_password)

            if self._use_password:
                password_dialog = PasswordDialog(title=_("Password"),
                                                 policy=self._policy)
                if self._user_data.password:
                    entry = EntryWidget(password_dialog.title, _(PASSWORD_SET))
                else:
                    entry = EntryWidget(password_dialog.title)

                self._container.add(entry, self._set_password, password_dialog)

            msg = _("Administrator")
            w = CheckboxWidget(title=msg, completed=self._is_admin)
            self._container.add(w, self._set_administrator)

            dialog = Dialog(title=_("Groups"), conditions=[self._check_groups])
            self._container.add(EntryWidget(dialog.title, self._groups),
                                self._set_groups, dialog)

        self.window.add_with_separator(self._container)

    @report_if_failed(message=FULLNAME_ERROR_MSG)
    def _check_fullname(self, user_input, report_func):
        return GECOS_VALID.match(user_input) is not None

    @report_check_func()
    def _check_username(self, user_input, report_func):
        return check_username(user_input)

    @report_check_func()
    def _check_groups(self, user_input, report_func):
        return check_grouplist(user_input)

    def _set_create_user(self, args):
        self._create_user = not self._create_user

    def _set_fullname(self, dialog):
        self._user_data.gecos = dialog.run()

    def _set_username(self, dialog):
        self._user_data.name = dialog.run()

    def _set_use_password(self, args):
        self._use_password = not self._use_password

    def _set_password(self, password_dialog):
        password = password_dialog.run()

        while password is None:
            password = password_dialog.run()

        self._user_data.password = password

    def _set_administrator(self, args):
        self._is_admin = not self._is_admin

    def _set_groups(self, dialog):
        self._groups = dialog.run()

    def show_all(self):
        NormalTUISpoke.show_all(self)
        # if we have any errors, display them
        while self.errors:
            print(self.errors.pop())

    @property
    def completed(self):
        """ Verify a user is created; verify pw is set if option checked. """
        if len(self.data.user.userList) > 0:
            if self._use_password and not bool(self._user_data.password
                                               or self._user_data.isCrypted):
                return False
            else:
                return True
        else:
            return False

    @property
    def showable(self):
        return not (self.completed and flags.automatedInstall
                    and self.data.user.seen and not self._policy.changesok)

    @property
    def mandatory(self):
        """ Only mandatory if the root pw hasn't been set in the UI
            eg. not mandatory if the root account was locked in a kickstart
        """
        return not self.data.rootpw.password and not self.data.rootpw.lock

    @property
    def status(self):
        if len(self.data.user.userList) == 0:
            return _("No user will be created")
        elif self._use_password and not bool(self._user_data.password
                                             or self._user_data.isCrypted):
            return _("You must set a password")
        elif "wheel" in self.data.user.userList[0].groups:
            return _("Administrator %s will be created"
                     ) % self.data.user.userList[0].name
        else:
            return _(
                "User %s will be created") % self.data.user.userList[0].name

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            self.redraw()
            return InputState.PROCESSED

        return super(UserSpoke, self).input(args, key)

    def apply(self):
        if self._user_data.gecos and not self._user_data.name:
            username = guess_username(self._user_data.gecos)
            valid, msg = check_username(username)
            if not valid:
                self.errors.append(
                    _("Invalid user name: %(name)s.\n%(error_message)s") % {
                        "name": username,
                        "error_message": msg
                    })
            else:
                self._user_data.name = guess_username(self._user_data.gecos)

        self._user_data.groups = [
            g.strip() for g in self._groups.split(",") if g
        ]

        # Add or remove the user from wheel group
        if self._is_admin and "wheel" not in self._user_data.groups:
            self._user_data.groups.append("wheel")
        elif not self._is_admin and "wheel" in self._user_data.groups:
            self._user_data.groups.remove("wheel")

        # Add or remove the user from userlist as needed
        if self._create_user and (self._user_data
                                  not in self.data.user.userList
                                  and self._user_data.name):
            self.data.user.userList.append(self._user_data)
        elif (not self._create_user) and (self._user_data
                                          in self.data.user.userList):
            self.data.user.userList.remove(self._user_data)

        # encrypt and store password only if user entered anything; this should
        # preserve passwords set via kickstart
        if self._use_password and self._user_data.password and len(
                self._user_data.password) > 0:
            self._user_data.password = self._user_data.password
            self._user_data.isCrypted = True
            self._user_data.password_kickstarted = False
        # clear pw when user unselects to use pw
        else:
            self._user_data.password = ""
            self._user_data.isCrypted = False
            self._user_data.password_kickstarted = False
Ejemplo n.º 44
0
class UserSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
       .. inheritance-diagram:: UserSpoke
          :parts: 3
    """
    category = UserSettingsCategory

    @staticmethod
    def get_screen_id():
        """Return a unique id of this UI screen."""
        return "user-configuration"

    @classmethod
    def should_run(cls, environment, data):
        """Should the spoke run?"""
        if not is_module_available(USERS):
            return False

        if FirstbootSpokeMixIn.should_run(environment, data):
            return True

        # the user spoke should run always in the anaconda and in firstboot only
        # when doing reconfig or if no user has been created in the installation
        users_module = USERS.get_proxy()
        user_list = get_user_list(users_module)
        if environment == FIRSTBOOT_ENVIRON and data and not user_list:
            return True

        return False

    def __init__(self, data, storage, payload):
        FirstbootSpokeMixIn.__init__(self)
        NormalTUISpoke.__init__(self, data, storage, payload)

        self.initialize_start()

        # connect to the Users DBus module
        self._users_module = USERS.get_proxy()

        self.title = N_("User creation")
        self._container = None

        # was user creation requested by the Users DBus module
        # - at the moment this basically means user creation was
        #   requested via kickstart
        # - note that this does not currently update when user
        #   list is changed via DBus
        self._user_requested = False
        self._user_cleared = False

        # should a user be created ?
        self._create_user = False

        self._user_list = get_user_list(self._users_module, add_default=True)
        # if user has a name, it's an actual user that has been requested,
        # rather than a default user added by us
        if self.user.name:
            self._user_requested = True
            self._create_user = True

        self._use_password = self.user.is_crypted or self.user.password
        self._groups = ""
        self._is_admin = False
        self.errors = []

        self._users_module = USERS.get_proxy()

        self.initialize_done()

    @property
    def user(self):
        """The user that is manipulated by the User spoke.

        This user is always the first one in the user list.

        :return: a UserData instance
        """
        return self._user_list[0]

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        # refresh the user list
        self._user_list = get_user_list(self._users_module,
                                        add_default=True,
                                        add_if_not_empty=self._user_cleared)

        self._is_admin = self.user.has_admin_priviledges()
        self._groups = ", ".join(self.user.groups)

        self._container = ListColumnContainer(1)

        w = CheckboxWidget(title=_("Create user"), completed=self._create_user)
        self._container.add(w, self._set_create_user)

        if self._create_user:
            dialog = Dialog(title=_("Full name"),
                            conditions=[self._check_fullname])
            self._container.add(EntryWidget(dialog.title, self.user.gecos),
                                self._set_fullname, dialog)

            dialog = Dialog(title=_("User name"),
                            conditions=[self._check_username])
            self._container.add(EntryWidget(dialog.title, self.user.name),
                                self._set_username, dialog)

            w = CheckboxWidget(title=_("Use password"),
                               completed=self._use_password)
            self._container.add(w, self._set_use_password)

            if self._use_password:
                password_dialog = PasswordDialog(
                    title=_("Password"), policy_name=PASSWORD_POLICY_USER)
                if self.user.password:
                    entry = EntryWidget(password_dialog.title, _(PASSWORD_SET))
                else:
                    entry = EntryWidget(password_dialog.title)

                self._container.add(entry, self._set_password, password_dialog)

            msg = _("Administrator")
            w = CheckboxWidget(title=msg, completed=self._is_admin)
            self._container.add(w, self._set_administrator)

            dialog = Dialog(title=_("Groups"), conditions=[self._check_groups])
            self._container.add(EntryWidget(dialog.title, self._groups),
                                self._set_groups, dialog)

        self.window.add_with_separator(self._container)

    @report_if_failed(message=FULLNAME_ERROR_MSG)
    def _check_fullname(self, user_input, report_func):
        return GECOS_VALID.match(user_input) is not None

    @report_check_func()
    def _check_username(self, user_input, report_func):
        return check_username(user_input)

    @report_check_func()
    def _check_groups(self, user_input, report_func):
        return check_grouplist(user_input)

    def _set_create_user(self, args):
        self._create_user = not self._create_user

    def _set_fullname(self, dialog):
        self.user.gecos = dialog.run()

    def _set_username(self, dialog):
        self.user.name = dialog.run()

    def _set_use_password(self, args):
        self._use_password = not self._use_password

    def _set_password(self, password_dialog):
        password = password_dialog.run()

        while password is None:
            password = password_dialog.run()

        self.user.password = password

    def _set_administrator(self, args):
        self._is_admin = not self._is_admin

    def _set_groups(self, dialog):
        self._groups = dialog.run()

    def show_all(self):
        NormalTUISpoke.show_all(self)
        # if we have any errors, display them
        while self.errors:
            print(self.errors.pop())

    @property
    def completed(self):
        """ Verify a user is created; verify pw is set if option checked. """
        user_list = get_user_list(self._users_module)
        if user_list:
            if self._use_password and not bool(self.user.password
                                               or self.user.is_crypted):
                return False
            else:
                return True
        else:
            return False

    @property
    def showable(self):
        return not (self.completed and flags.automatedInstall
                    and self._user_requested and not conf.ui.can_change_users)

    @property
    def mandatory(self):
        """The spoke is mandatory only if some input is missing.

        Possible reasons to be mandatory:
        - No admin user has been created
        - Password has been requested but not entered
        """
        return (not self._users_module.CheckAdminUserExists()
                or (self._use_password
                    and not bool(self.user.password or self.user.is_crypted)))

    @property
    def status(self):
        user_list = get_user_list(self._users_module)
        if not user_list:
            return _("No user will be created")
        elif self._use_password and not bool(self.user.password
                                             or self.user.is_crypted):
            return _("You must set a password")
        elif user_list[0].has_admin_priviledges():
            return _("Administrator %s will be created") % user_list[0].name
        else:
            return _("User %s will be created") % user_list[0].name

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW

        return super().input(args, key)

    def apply(self):
        if self.user.gecos and not self.user.name:
            username = guess_username(self.user.gecos)
            valid, msg = check_username(username)
            if not valid:
                self.errors.append(
                    _("Invalid user name: %(name)s.\n%(error_message)s") % {
                        "name": username,
                        "error_message": msg
                    })
            else:
                self.user.name = guess_username(self.user.gecos)

        self.user.groups = [g.strip() for g in self._groups.split(",") if g]

        # Add or remove user admin status
        self.user.set_admin_priviledges(self._is_admin)

        # encrypt and store password only if user entered anything; this should
        # preserve passwords set via kickstart
        if self._use_password and self.user.password and len(
                self.user.password) > 0:
            self.user.password = self.user.password
            self.user.is_crypted = True
        # clear pw when user unselects to use pw
        else:
            self.user.password = ""
            self.user.is_crypted = False

        # Turning user creation off clears any already configured user,
        # regardless of origin (kickstart, user, DBus).
        if not self._create_user and self.user.name:
            self.user.name = ""
            self._user_cleared = True
        # An the other hand, if we have a user with name set,
        # it is valid and should be used if the spoke is re-visited.
        if self.user.name:
            self._user_cleared = False
        # Set the user list while removing any unset users, where unset
        # means the user has nema == "".
        set_user_list(self._users_module, self._user_list, remove_unset=True)
class HelloWorldEditSpoke(NormalTUISpoke):
    """Example class demonstrating usage of editing in TUI"""

    category = HelloWorldCategory

    def __init__(self, data, storage, payload, instclass):
        """
        :see: simpleline.render.screen.UIScreen
        :param data: data object passed to every spoke to load/store data
                     from/to it
        :type data: pykickstart.base.BaseHandler
        :param storage: object storing storage-related information
                        (disks, partitioning, bootloader, etc.)
        :type storage: blivet.Blivet
        :param payload: object storing packaging-related information
        :type payload: pyanaconda.packaging.Payload
        :param instclass: distribution-specific information
        :type instclass: pyanaconda.installclass.BaseInstallClass

        """

        NormalTUISpoke.__init__(self, data, storage, payload, instclass)

        self.title = N_("Hello World Edit")
        self._container = None
        # values for user to set
        self._checked = False
        self._unconditional_input = ""
        self._conditional_input = ""

    def refresh(self, args=None):
        """
        The refresh method that is called every time the spoke is displayed.
        It should update the UI elements according to the contents of
        self.data.

        :see: pyanaconda.ui.common.UIObject.refresh
        :see: simpleline.render.screen.UIScreen.refresh
        :param args: optional argument that may be used when the screen is
                     scheduled
        :type args: anything

        """

        super().refresh(args)
        self._container = ListColumnContainer(columns=1)

        # add ListColumnContainer to window (main window container)
        # this will automatically add numbering and will call callbacks when required
        self.window.add(self._container)

        self._container.add(CheckboxWidget(title="Simple checkbox", completed=self._checked),
                            callback=self._checkbox_called)
        self._container.add(EntryWidget(title="Unconditional input",
                                        value=self._unconditional_input),
                            callback=self._get_unconditional_input)

        # show conditional input only if the checkbox is checked
        if self._checked:
            self._container.add(EntryWidget(title="Conditional input",
                                            value="Password set" if self._conditional_input
                                                  else ""),
                                callback=self._get_conditional_input)

        self._window.add_separator()

    def _checkbox_called(self, data):
        """Callback when user wants to switch checkbox.

        :param data: can be passed when adding callback in container (not used here)
        :type data: anything
        """

        self._checked = not self._checked

    def _get_unconditional_input(self, data):
        """Callback when user wants to set unconditional input.

        :param data: can be passed when adding callback in container (not used here)
        :type data: anything
        """

        dialog = Dialog("Unconditional input", conditions=[self._check_user_input])

        self._unconditional_input = dialog.run()

    def _get_conditional_input(self, data):
        """Callback when user wants to set conditional input.

        :param data: can be passed when adding callback in container (not used here)
        :type data: anything
        """

        # password policy for setting root password
        password_policy = self.data.anaconda.pwpolicy.get_policy("root", fallback_to_default=True)
        dialog = PasswordDialog("Unconditional input", policy=password_policy)

        self._conditional_input = dialog.run()

    def _check_user_input(self, user_input, report_func):
        """Check if user has wrote a valid value.

        :param user_input: user input for validation
        :type user_input: str

        :param report_func: function for reporting errors on user input
        :type report_func: func with one param
        """

        if re.match(r'^\w+$', user_input):
            return True
        else:
            report_func("You must set a one word")
            return False

    def input(self, args, key):
        """
        The input method that is called by the main loop on user's input.

        :param args: optional argument that may be used when the screen is
                     scheduled
        :type args: anything
        :param key: user's input
        :type key: unicode
        :return: if the input should not be handled here, return it, otherwise
                 return InputState.PROCESSED or InputState.DISCARDED if the input was
                 processed successfully or not respectively
        :rtype: enum InputState

        """

        if self._container.process_user_input(key):
            # redraw or close must be called before PROCESSED
            self.redraw()
            return InputState.PROCESSED
        else:
            return super().input(args=args, key=key)

    @property
    def completed(self):
        # completed if user entered something non-empty to the Conditioned input
        return bool(self._conditional_input)

    @property
    def status(self):
        return "Hidden input %s" % ("entered" if self._conditional_input
                                    else "not entered")

    def apply(self):

        # nothing to do here
        pass
Ejemplo n.º 46
0
class AskVNCSpoke(NormalTUISpoke):
    """
       .. inheritance-diagram:: AskVNCSpoke
          :parts: 3
    """
    title = N_("VNC")

    # This spoke is kinda standalone, not meant to be used with a hub
    # We pass in some fake data just to make our parents happy
    def __init__(self,
                 data,
                 storage=None,
                 payload=None,
                 instclass=None,
                 message=""):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.input_required = True
        self.initialize_start()
        self._container = None

        # The TUI hasn't been initialized with the message handlers yet. Add an
        # exception message handler so that the TUI exits if anything goes wrong
        # at this stage.
        loop = App.get_event_loop()
        loop.register_signal_handler(ExceptionSignal,
                                     exception_msg_handler_and_exit)
        self._message = message
        self._usevnc = False
        self.initialize_done()

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self.window.add_with_separator(TextWidget(self._message))

        self._container = ListColumnContainer(1, spacing=1)

        # choices are
        # USE VNC
        self._container.add(TextWidget(_(USEVNC)), self._use_vnc_callback)
        # USE TEXT
        self._container.add(TextWidget(_(USETEXT)), self._use_text_callback)

        self.window.add_with_separator(self._container)

    def _use_vnc_callback(self, data):
        self._usevnc = True
        new_spoke = VNCPassSpoke(self.data, self.storage, self.payload,
                                 self.instclass)
        ScreenHandler.push_screen_modal(new_spoke)

    def _use_text_callback(self, data):
        self._usevnc = False

    def input(self, args, key):
        """Override input so that we can launch the VNC password spoke"""
        if self._container.process_user_input(key):
            self.apply()
            self.close()
            return InputState.PROCESSED
        else:
            # TRANSLATORS: 'q' to quit
            if key.lower() == C_('TUI|Spoke Navigation', 'q'):
                d = YesNoDialog(_(u"Do you really want to quit?"))
                ScreenHandler.push_screen_modal(d)
                if d.answer:
                    ipmi_abort(scripts=self.data.scripts)
                    if can_touch_runtime_system("Quit and Reboot"):
                        execWithRedirect("systemctl", ["--no-wall", "reboot"])
                    else:
                        sys.exit(1)
            else:
                return super(AskVNCSpoke, self).input(args, key)

    def apply(self):
        self.data.vnc.enabled = self._usevnc
Ejemplo n.º 47
0
class NetworkSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """ Spoke used to configure network settings.

       .. inheritance-diagram:: NetworkSpoke
          :parts: 3
    """
    helpFile = "NetworkSpoke.txt"
    category = SystemCategory
    configurable_device_types = [
        NM.DeviceType.ETHERNET,
        NM.DeviceType.INFINIBAND,
    ]

    def __init__(self, data, storage, payload):
        NormalTUISpoke.__init__(self, data, storage, payload)
        self.title = N_("Network configuration")
        self._network_module = NETWORK.get_observer()
        self._network_module.connect()

        self.nm_client = network.get_nm_client()
        if not self.nm_client and conf.system.provides_system_bus:
            self.nm_client = NM.Client.new(None)

        self._container = None
        self.hostname = self._network_module.proxy.Hostname
        self.editable_configurations = []
        self.errors = []
        self._apply = False

    @classmethod
    def should_run(cls, environment, data):
        return conf.system.can_configure_network

    def initialize(self):
        self.initialize_start()
        NormalTUISpoke.initialize(self)
        self._update_editable_configurations()
        self._network_module.proxy.DeviceConfigurationChanged.connect(self._device_configurations_changed)
        self.initialize_done()

    def _device_configurations_changed(self, device_configurations):
        log.debug("device configurations changed: %s", device_configurations)
        self._update_editable_configurations()

    def _update_editable_configurations(self):
        device_configurations = self._network_module.proxy.GetDeviceConfigurations()
        self.editable_configurations = [NetworkDeviceConfiguration.from_structure(dc)
                                        for dc in device_configurations
                                        if dc['device-type'] in self.configurable_device_types]

    @property
    def completed(self):
        """ Check whether this spoke is complete or not."""
        # If we can't configure network, don't require it
        return (not conf.system.can_configure_network
                or self._network_module.proxy.GetActivatedInterfaces())

    @property
    def mandatory(self):
        # the network spoke should be mandatory only if it is running
        # during the installation and if the installation source requires network
        return ANACONDA_ENVIRON in flags.environs and self.payload.needs_network

    @property
    def status(self):
        """ Short msg telling what devices are active. """
        return network.status_message(self.nm_client)

    def _summary_text(self):
        """Devices cofiguration shown to user."""
        msg = ""
        activated_devs = self._network_module.proxy.GetActivatedInterfaces()
        for device_configuration in self.editable_configurations:
            name = device_configuration.device_name
            if name in activated_devs:
                msg += self._activated_device_msg(name)
            else:
                msg += _("Wired (%(interface_name)s) disconnected\n") \
                       % {"interface_name": name}
        return msg

    def _activated_device_msg(self, devname):
        msg = _("Wired (%(interface_name)s) connected\n") \
              % {"interface_name": devname}

        device = self.nm_client.get_device_by_iface(devname)
        if device:
            ipv4config = device.get_ip4_config()
            if ipv4config:
                addresses = ipv4config.get_addresses()
                if addresses:
                    a0 = addresses[0]
                    addr_str = a0.get_address()
                    prefix = a0.get_prefix()
                    netmask_str = network.prefix_to_netmask(prefix)
                gateway_str = ipv4config.get_gateway() or ''
                dnss_str = ",".join(ipv4config.get_nameservers())
            else:
                addr_str = dnss_str = gateway_str = netmask_str = ""
            msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
                {"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
            msg += _(" DNS: %s\n") % dnss_str

            ipv6config = device.get_ip6_config()
            if ipv6config:
                for address in ipv6config.get_addresses():
                    addr_str = address.get_address()
                    prefix = address.get_prefix()
                    # Do not display link-local addresses
                    if not addr_str.startswith("fe80:"):
                        msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
                            {"addr": addr_str, "prefix": prefix}
        return msg

    def refresh(self, args=None):
        """ Refresh screen. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        if not self.nm_client:
            self.window.add_with_separator(TextWidget(_("Network configuration is not available.")))
            return

        summary = self._summary_text()
        self.window.add_with_separator(TextWidget(summary))

        hostname = _("Host Name: %s\n") % self._network_module.proxy.Hostname
        self.window.add_with_separator(TextWidget(hostname))
        current_hostname = _("Current host name: %s\n") % self._network_module.proxy.GetCurrentHostname()
        self.window.add_with_separator(TextWidget(current_hostname))

        # if we have any errors, display them
        while len(self.errors) > 0:
            self.window.add_with_separator(TextWidget(self.errors.pop()))

        dialog = Dialog(_("Host Name"))
        self._container.add(TextWidget(_("Set host name")), callback=self._set_hostname_callback, data=dialog)

        for device_configuration in self.editable_configurations:
            iface = device_configuration.device_name
            text = (_("Configure device %s") % iface)
            self._container.add(TextWidget(text), callback=self._ensure_connection_and_configure,
                                data=iface)

        self.window.add_with_separator(self._container)

    def _set_hostname_callback(self, dialog):
        self.hostname = dialog.run()
        self.redraw()
        self.apply()

    def _ensure_connection_and_configure(self, iface):
        for device_configuration in self.editable_configurations:
            if device_configuration.device_name == iface:
                connection_uuid = device_configuration.connection_uuid
                if connection_uuid:
                    self._configure_connection(iface, connection_uuid)
                else:
                    device_type = self.nm_client.get_device_by_iface(iface).get_device_type()
                    connection = get_default_connection(iface, device_type)
                    connection_uuid = connection.get_uuid()
                    log.debug("adding default connection %s for %s", connection_uuid, iface)
                    persistent = False
                    data = (iface, connection_uuid)
                    self.nm_client.add_connection_async(connection, persistent, None,
                                                        self._default_connection_added_cb, data)
                return
        log.error("device configuration for %s not found", iface)

    def _default_connection_added_cb(self, client, result, data):
        client.add_connection_finish(result)
        iface, connection_uuid = data
        log.debug("added default connection %s for %s", connection_uuid, iface)
        self._configure_connection(iface, connection_uuid)

    def _configure_connection(self, iface, connection_uuid):
        connection = self.nm_client.get_connection_by_uuid(connection_uuid)

        new_spoke = ConfigureDeviceSpoke(self.data, self.storage, self.payload,
                                         self._network_module, iface, connection)
        ScreenHandler.push_screen_modal(new_spoke)

        if new_spoke.errors:
            self.errors.extend(new_spoke.errors)
            self.redraw()
            return

        if new_spoke.apply_configuration:
            self._apply = True
            device = self.nm_client.get_device_by_iface(iface)
            log.debug("activating connection %s with device %s",
                      connection_uuid, iface)
            self.nm_client.activate_connection_async(connection, device, None, None)

        self._network_module.proxy.LogConfigurationState(
            "Settings of {} updated in TUI.".format(iface)
        )

        self.redraw()
        self.apply()

    def input(self, args, key):
        """ Handle the input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            return super().input(args, key)

    def apply(self):
        """Apply all of our settings."""
        # Inform network module that device configurations might have been changed
        # and we want to generate kickstart from device configurations
        # (persistent NM / ifcfg configuration), instead of using original kickstart.
        self._network_module.proxy.NetworkDeviceConfigurationChanged()

        (valid, error) = network.is_valid_hostname(self.hostname)
        if valid:
            self._network_module.proxy.SetHostname(self.hostname)
        else:
            self.errors.append(_("Host name is not valid: %s") % error)
            self.hostname = self._network_module.proxy.Hostname

        if self._apply:
            self._apply = False
            if ANACONDA_ENVIRON in flags.environs:
                from pyanaconda.payload.manager import payloadMgr
                payloadMgr.restart_thread(self.storage, self.data, self.payload, checkmount=False)
Ejemplo n.º 48
0
class SpecifyNFSRepoSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Specify server and mount opts here if NFS selected. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, error):
        NormalTUISpoke.__init__(self, data, storage, payload)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Specify Repo Options")
        self._container = None
        self._error = error

        options, host, path = self._get_nfs()
        self._nfs_opts = options
        self._nfs_server = "{}:{}".format(host, path) if host else ""

    def _get_nfs(self):
        """Get the NFS options, host and path of the current source."""
        source_proxy = self.payload.get_source_proxy()

        if source_proxy.Type == SOURCE_TYPE_NFS:
            return parse_nfs_url(source_proxy.URL)

        return "", "", ""

    def refresh(self, args=None):
        """ Refresh window. """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=_("SERVER:/PATH"), conditions=[self._check_nfs_server])
        self._container.add(EntryWidget(dialog.title, self._nfs_server),
                            self._set_nfs_server, dialog)

        dialog = Dialog(title=_("NFS mount options"))
        self._container.add(EntryWidget(dialog.title, self._nfs_opts), self._set_nfs_opts, dialog)

        self.window.add_with_separator(self._container)

    def _set_nfs_server(self, dialog):
        self._nfs_server = dialog.run()

    def _check_nfs_server(self, user_input, report_func):
        if ":" not in user_input or len(user_input.split(":")) != 2:
            report_func(_("Server must be specified as SERVER:/PATH"))
            return False

        return True

    def _set_nfs_opts(self, dialog):
        self._nfs_opts = dialog.run()

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW
        else:
            return NormalTUISpoke.input(self, args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """ Apply our changes. """
        if self._nfs_server == "" or ':' not in self._nfs_server:
            return False

        if self._nfs_server.startswith("nfs://"):
            self._nfs_server = self._nfs_server[6:]

        try:
            (server, directory) = self._nfs_server.split(":", 2)
        except ValueError as err:
            log.error("ValueError: %s", err)
            self._error = True
            return

        opts = self._nfs_opts or ""
        self.set_source_nfs(server, directory, opts)
Ejemplo n.º 49
0
class ConfigureDeviceSpoke(NormalTUISpoke):
    """ Spoke to set various configuration options for net devices. """
    category = "network"

    def __init__(self, data, storage, payload, network_module, iface, connection):
        super().__init__(data, storage, payload)
        self.title = N_("Device configuration")

        self._network_module = network_module
        self._container = None
        self._connection = connection
        self._iface = iface
        self._connection_uuid = connection.get_uuid()
        self.errors = []
        self.apply_configuration = False

        self._data = WiredTUIConfigurationData()
        self._data.set_from_connection(self._connection)
        # ONBOOT workaround - changing autoconnect connection value would
        # activate the device
        self._data.onboot = self._get_onboot(self._connection_uuid)

        log.debug("Configure iface %s: connection %s -> %s", self._iface, self._connection_uuid,
                  self._data)

    def _get_onboot(self, connection_uuid):
        return self._network_module.proxy.GetConnectionOnbootValue(connection_uuid)

    def _set_onboot(self, connection_uuid, onboot):
        return self._network_module.proxy.SetConnectionOnbootValue(connection_uuid, onboot)

    def refresh(self, args=None):
        """ Refresh window. """
        super().refresh(args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=(_('IPv4 address or %s for DHCP') % '"dhcp"'),
                        conditions=[self._check_ipv4_or_dhcp])
        self._container.add(EntryWidget(dialog.title, self._data.ip), self._set_ipv4_or_dhcp, dialog)

        dialog = Dialog(title=_("IPv4 netmask"), conditions=[self._check_netmask])
        self._container.add(EntryWidget(dialog.title, self._data.netmask), self._set_netmask, dialog)

        dialog = Dialog(title=_("IPv4 gateway"), conditions=[self._check_ipv4])
        self._container.add(EntryWidget(dialog.title, self._data.gateway), self._set_ipv4_gateway, dialog)

        msg = (_('IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, '
                 '%(ignore)s to turn off')
               % {"auto": '"auto"', "dhcp": '"dhcp"', "ignore": '"ignore"'})
        dialog = Dialog(title=msg, conditions=[self._check_ipv6_config])
        self._container.add(EntryWidget(dialog.title, self._data.ipv6), self._set_ipv6, dialog)

        dialog = Dialog(title=_("IPv6 default gateway"), conditions=[self._check_ipv6])
        self._container.add(EntryWidget(dialog.title, self._data.ipv6gateway), self._set_ipv6_gateway, dialog)

        dialog = Dialog(title=_("Nameservers (comma separated)"), conditions=[self._check_nameservers])
        self._container.add(EntryWidget(dialog.title, self._data.nameserver), self._set_nameservers, dialog)

        msg = _("Connect automatically after reboot")
        w = CheckboxWidget(title=msg, completed=self._data.onboot)
        self._container.add(w, self._set_onboot_handler)

        msg = _("Apply configuration in installer")
        w = CheckboxWidget(title=msg, completed=self.apply_configuration)
        self._container.add(w, self._set_apply_handler)

        self.window.add_with_separator(self._container)

        message = _("Configuring device %s.") % self._iface
        self.window.add_with_separator(TextWidget(message))

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4_or_dhcp(self, user_input, report_func):
        return IPV4_OR_DHCP_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4(self, user_input, report_func):
        return IPV4_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=NETMASK_ERROR_MSG)
    def _check_netmask(self, user_input, report_func):
        return IPV4_NETMASK_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6(self, user_input, report_func):
        return network.check_ip_address(user_input, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6_config(self, user_input, report_func):
        if user_input in ["auto", "dhcp", "ignore"]:
            return True
        addr, _slash, prefix = user_input.partition("/")
        if prefix:
            try:
                if not 1 <= int(prefix) <= 128:
                    return False
            except ValueError:
                return False
        return network.check_ip_address(addr, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_nameservers(self, user_input, report_func):
        if user_input.strip():
            addresses = [str.strip(i) for i in user_input.split(",")]
            for ip in addresses:
                if not network.check_ip_address(ip):
                    return False
        return True

    def _set_ipv4_or_dhcp(self, dialog):
        self._data.ip = dialog.run()

    def _set_netmask(self, dialog):
        self._data.netmask = dialog.run()

    def _set_ipv4_gateway(self, dialog):
        self._data.gateway = dialog.run()

    def _set_ipv6(self, dialog):
        self._data.ipv6 = dialog.run()

    def _set_ipv6_gateway(self, dialog):
        self._data.ipv6gateway = dialog.run()

    def _set_nameservers(self, dialog):
        self._data.nameserver = dialog.run()

    def _set_apply_handler(self, args):
        self.apply_configuration = not self.apply_configuration

    def _set_onboot_handler(self, args):
        self._data.onboot = not self._data.onboot

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_REDRAW
        else:
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                if self._data.ip != "dhcp" and not self._data.netmask:
                    self.errors.append(_("Configuration not saved: netmask missing in static configuration"))
                else:
                    self.apply()
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """Apply changes to NM connection."""
        log.debug("updating connection %s:\n%s", self._connection_uuid,
                  self._connection.to_dbus(NM.ConnectionSerializationFlags.ALL))
        self._data.update_connection(self._connection)

        # ONBOOT workaround
        s_con = self._connection.get_setting_connection()
        s_con.set_property(NM.SETTING_CONNECTION_AUTOCONNECT, False)

        self._connection.commit_changes(True, None)
        log.debug("updated connection %s:\n%s", self._connection_uuid,
                  self._connection.to_dbus(NM.ConnectionSerializationFlags.ALL))

        # ONBOOT workaround
        self._set_onboot(self._connection_uuid, self._data.onboot)
Ejemplo n.º 50
0
class SelectISOSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Select an ISO to use as install source. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, device):
        NormalTUISpoke.__init__(self, data, storage, payload)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Select an ISO to use as install source")
        self._container = None
        self._device = device
        self._isos = self._collect_iso_files()

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        if self._isos:
            self._container = ListColumnContainer(1, columns_width=78, spacing=1)

            for iso in self._isos:
                self._container.add(TextWidget(iso), callback=self._select_iso_callback, data=iso)

            self.window.add_with_separator(self._container)
        else:
            message = _("No *.iso files found in device root folder")
            self.window.add_with_separator(TextWidget(message))

    def _select_iso_callback(self, data):
        self._current_iso_path = data
        self.apply()
        self.close()

    def input(self, args, key):
        if self._container is not None and self._container.process_user_input(key):
            return InputState.PROCESSED
        elif key.lower() == Prompt.CONTINUE:
            self.apply()
            return InputState.PROCESSED_AND_CLOSE
        else:
            return super().input(args, key)

    @property
    def indirect(self):
        return True

    def _collect_iso_files(self):
        """Collect *.iso files."""
        try:
            self._mount_device()
            return self._getISOs()
        finally:
            self._unmount_device()

    def _mount_device(self):
        """ Mount the device so we can search it for ISOs. """
        # FIXME: Use a unique mount point.
        device_path = get_device_path(self._device)
        mounts = payload_utils.get_mount_paths(device_path)

        # We have to check both ISO_DIR and the DRACUT_ISODIR because we
        # still reference both, even though /mnt/install is a symlink to
        # /run/install.  Finding mount points doesn't handle the symlink
        if ISO_DIR not in mounts and DRACUT_ISODIR not in mounts:
            # We're not mounted to either location, so do the mount
            payload_utils.mount_device(self._device, ISO_DIR)

    def _unmount_device(self):
        # FIXME: Unmount a specific mount point.
        payload_utils.unmount_device(self._device, mount_point=None)

    def _getISOs(self):
        """List all *.iso files in the root folder
        of the currently selected device.

        TODO: advanced ISO file selection
        :returns: a list of *.iso file paths
        :rtype: list
        """
        isos = []
        for filename in os.listdir(ISO_DIR):
            if fnmatch.fnmatch(filename.lower(), "*.iso"):
                isos.append(filename)
        return isos

    def apply(self):
        """ Apply all of our changes. """
        if self._current_iso_path:
            self.set_source_hdd_iso(self._device, self._current_iso_path)
Ejemplo n.º 51
0
class SourceSpoke(NormalTUISpoke, SourceSwitchHandler):
    """ Spoke used to customize the install source repo.

       .. inheritance-diagram:: SourceSpoke
          :parts: 3
    """
    helpFile = "SourceSpoke.txt"
    category = SoftwareCategory

    SET_NETWORK_INSTALL_MODE = "network_install"

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        SourceSwitchHandler.__init__(self)
        self.title = N_("Installation source")
        self._container = None
        self._ready = False
        self._error = False
        self._cdrom = None
        self._hmc = False

    def initialize(self):
        NormalTUISpoke.initialize(self)
        self.initialize_start()

        threadMgr.add(
            AnacondaThread(name=THREAD_SOURCE_WATCHER,
                           target=self._initialize))
        payloadMgr.addListener(payloadMgr.STATE_ERROR, self._payload_error)

    def _initialize(self):
        """ Private initialize. """
        threadMgr.wait(THREAD_PAYLOAD)
        # If we've previously set up to use a CD/DVD method, the media has
        # already been mounted by payload.setup.  We can't try to mount it
        # again.  So just use what we already know to create the selector.
        # Otherwise, check to see if there's anything available.
        if self.data.method.method == "cdrom":
            self._cdrom = self.payload.install_device
        elif not flags.automatedInstall:
            self._cdrom = opticalInstallMedia(self.storage.devicetree)

        # Enable the SE/HMC option.
        if flags.hmc:
            self._hmc = True

        self._ready = True

        # report that the source spoke has been initialized
        self.initialize_done()

    def _payload_error(self):
        self._error = True

    def _repo_status(self):
        """ Return a string describing repo url or lack of one. """
        method = self.data.method
        if method.method == "url":
            return method.url or method.mirrorlist or method.metalink
        elif method.method == "nfs":
            return _("NFS server %s") % method.server
        elif method.method == "cdrom":
            return _("Local media")
        elif method.method == "hmc":
            return _("Local media via SE/HMC")
        elif method.method == "harddrive":
            if not method.dir:
                return _("Error setting up software source")
            return os.path.basename(method.dir)
        elif self.payload.baseRepo:
            return _("Closest mirror")
        else:
            return _("Nothing selected")

    @property
    def showable(self):
        return isinstance(self.payload, PackagePayload)

    @property
    def status(self):
        if self._error:
            return _("Error setting up software source")
        elif not self.ready:
            return _("Processing...")
        else:
            return self._repo_status()

    @property
    def completed(self):
        if flags.automatedInstall and self.ready and not self.payload.baseRepo:
            return False
        else:
            return not self._error and self.ready and (
                self.data.method.method or self.payload.baseRepo)

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        threadMgr.wait(THREAD_PAYLOAD)

        self._container = ListColumnContainer(1, columns_width=78, spacing=1)

        if self.data.method.method == "harddrive" and \
           get_mount_device(DRACUT_ISODIR) == get_mount_device(DRACUT_REPODIR):
            message = _(
                "The installation source is in use by the installer and cannot be changed."
            )
            self.window.add_with_separator(TextWidget(message))
            return

        if args == self.SET_NETWORK_INSTALL_MODE:
            if self.payload.mirrors_available:
                self._container.add(TextWidget(_("Closest mirror")),
                                    self._set_network_close_mirror)
            self._container.add(TextWidget("http://"), self._set_network_url,
                                SpecifyRepoSpoke.HTTP)
            self._container.add(TextWidget("https://"), self._set_network_url,
                                SpecifyRepoSpoke.HTTPS)
            self._container.add(TextWidget("ftp://"), self._set_network_url,
                                SpecifyRepoSpoke.FTP)
            self._container.add(TextWidget("nfs"), self._set_network_nfs)
        else:
            self.window.add(
                TextWidget(_("Choose an installation source type.")))
            self._container.add(TextWidget(_("CD/DVD")),
                                self._set_cd_install_source)
            self._container.add(TextWidget(_("local ISO file")),
                                self._set_iso_install_source)
            self._container.add(TextWidget(_("Network")),
                                self._set_network_install_source)

            if self._hmc:
                self._container.add(TextWidget(_("SE/HMC")),
                                    self._set_hmc_install_source)

        self.window.add_with_separator(self._container)

    # Set installation source callbacks

    def _set_cd_install_source(self, data):
        self.set_source_cdrom()
        self.payload.install_device = self._cdrom
        self.apply()
        self.close()

    def _set_hmc_install_source(self, data):
        self.set_source_hmc()
        self.apply()
        self.close()

    def _set_iso_install_source(self, data):
        new_spoke = SelectDeviceSpoke(self.data, self.storage, self.payload,
                                      self.instclass)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_install_source(self, data):
        ScreenHandler.replace_screen(self, self.SET_NETWORK_INSTALL_MODE)

    # Set network source callbacks

    def _set_network_close_mirror(self, data):
        self.set_source_closest_mirror()
        self.apply()
        self.close()

    def _set_network_url(self, data):
        new_spoke = SpecifyRepoSpoke(self.data, self.storage, self.payload,
                                     self.instclass, data)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def _set_network_nfs(self, data):
        self.set_source_nfs()
        new_spoke = SpecifyNFSRepoSpoke(self.data, self.storage, self.payload,
                                        self.instclass, self._error)
        ScreenHandler.push_screen_modal(new_spoke)
        self.apply()
        self.close()

    def input(self, args, key):
        """ Handle the input; this decides the repo source. """
        if not self._container.process_user_input(key):
            return super().input(args, key)

        return InputState.PROCESSED

    @property
    def ready(self):
        """ Check if the spoke is ready. """
        return (self._ready and not threadMgr.get(THREAD_PAYLOAD)
                and not threadMgr.get(THREAD_CHECK_SOFTWARE))

    def apply(self):
        """ Execute the selections made. """
        # If askmethod was provided on the command line, entering the source
        # spoke wipes that out.
        if flags.askmethod:
            flags.askmethod = False

        # if we had any errors, e.g. from a previous attempt to set the source,
        # clear them at this point
        self._error = False

        payloadMgr.restartThread(self.storage,
                                 self.data,
                                 self.payload,
                                 self.instclass,
                                 checkmount=False)
Ejemplo n.º 52
0
class LangSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
    This spoke allows a user to select their installed language. Note that this
    does not affect the display of the installer, it only will affect the system
    post-install, because it's too much of a pain to make other languages work
    in text-mode.

    Also this doesn't allow for selection of multiple languages like in the GUI.

       .. inheritance-diagram:: LangSpoke
          :parts: 3
    """
    helpFile = "LangSupportSpoke.txt"
    category = LocalizationCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Language settings")
        self.initialize_start()
        self._container = None

        self._langs = [localization.get_english_name(lang)
                       for lang in localization.get_available_translations()]
        self._langs_and_locales = dict((localization.get_english_name(lang), lang)
                                       for lang in localization.get_available_translations())
        self._locales = dict((lang, localization.get_language_locales(lang))
                             for lang in self._langs_and_locales.values())

        self._l12_module = LOCALIZATION.get_observer()
        self._l12_module.connect()

        self._selected = self._l12_module.proxy.Language
        self.initialize_done()

    @property
    def completed(self):
        return self._l12_module.proxy.Language

    @property
    def mandatory(self):
        return False

    @property
    def showable(self):
        # don't show the language support spoke in single language mode
        return not flags.singlelang

    @property
    def status(self):
        if self._l12_module.proxy.Language:
            return localization.get_english_name(self._selected)
        else:
            return _("Language is not set.")

    def refresh(self, args=None):
        """
        args is None if we want a list of languages; or, it is a list of all
        locales for a language.
        """
        NormalTUISpoke.refresh(self, args)

        self._container = ListColumnContainer(3)

        if args:
            self.window.add(TextWidget(_("Available locales")))
            for locale in args:
                widget = TextWidget(localization.get_english_name(locale))
                self._container.add(widget, self._set_locales_callback, locale)
        else:
            self.window.add(TextWidget(_("Available languages")))
            for lang in self._langs:
                langs_and_locales = self._langs_and_locales[lang]
                locales = self._locales[langs_and_locales]
                self._container.add(TextWidget(lang), self._show_locales_callback, locales)

        self.window.add_with_separator(self._container)

    def _set_locales_callback(self, data):
        locale = data
        self._selected = locale
        self.apply()
        self.close()

    def _show_locales_callback(self, data):
        locales = data
        ScreenHandler.replace_screen(self, locales)

    def input(self, args, key):
        """ Handle user input. """
        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # TRANSLATORS: 'b' to go back
            if key.lower() == C_("TUI|Spoke Navigation|Language Support", "b"):
                ScreenHandler.replace_screen(self)
                return InputState.PROCESSED
            else:
                return super().input(args, key)

    def prompt(self, args=None):
        """ Customize default prompt. """
        prompt = NormalTUISpoke.prompt(self, args)
        prompt.set_message(_("Please select language support to install"))
        # TRANSLATORS: 'b' to go back
        prompt.add_option(C_("TUI|Spoke Navigation|Language Support", "b"), _("to return to language list"))
        return prompt

    def apply(self):
        """ Store the selected lang support locales """
        self._l12_module.proxy.SetLanguage(self._selected)
Ejemplo n.º 53
0
class KdumpSpoke(NormalTUISpoke):
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        super().__init__(data, storage, payload, instclass)
        self.title = N_("Kdump")
        self._addon_data = self.data.addons.com_redhat_kdump

        self._lower, self._upper, self._step = getMemoryBounds()
        # Allow a string of digits optionally followed by 'M'
        self._reserve_check_re = re.compile(r'^(\d+M?)$')

        self._container = None

    @classmethod
    def should_run(cls, environment, data):
        # the KdumpSpoke should run only if requested
        return flags.cmdline.getbool("kdump_addon", default=False)

    def apply(self):
        pass

    @property
    def completed(self):
        return True

    @property
    def status(self):
        if self._addon_data.enabled:
            state = _("Kdump is enabled")
        else:
            state = _("Kdump is disabled")
        return state

    def refresh(self, args=None):
        super().refresh(args)

        self._container = ListColumnContainer(1)
        self.window.add(self._container)

        self._create_enable_checkbox()

        if self._addon_data.enabled:
            self._create_fadump_checkbox()
            self._create_reserve_amount_text_widget()

        self.window.add_separator()

    def _create_enable_checkbox(self):
        enable_kdump_checkbox = CheckboxWidget(title=_("Enable kdump"),
                                               completed=self._addon_data.enabled)
        self._container.add(enable_kdump_checkbox, self._set_enabled)

    def _create_fadump_checkbox(self):
        if not os.path.exists(FADUMP_CAPABLE_FILE):
            return

        enable_fadump_checkbox = CheckboxWidget(title=_("Enable dump mode fadump"),
                                                completed=self._addon_data.enablefadump)
        self._container.add(enable_fadump_checkbox, self._set_fadump_enable)

    def _create_reserve_amount_text_widget(self):
        title = _("Reserve amount (%d - %d MB)" % (self._lower, self._upper))
        reserve_amount_entry = EntryWidget(title=title, value=self._addon_data.reserveMB)
        self._container.add(reserve_amount_entry, self._get_reserve_amount)

    def _set_enabled(self, data):
        self._addon_data.enabled = not self._addon_data.enabled

    def _set_fadump_enable(self, data):
        self._addon_data.enablefadump = not self._addon_data.enablefadump

    def _get_reserve_amount(self, data):
        text = "Reserve amount (%d - %d MB)" % (self._lower, self._upper)
        dialog = Dialog(title=text, conditions=[self._check_reserve_valid])

        self._addon_data.reserveMB = dialog.run()

    def _check_reserve_valid(self, key, report_func):
        if self._reserve_check_re.match(key):
            if key[-1] == 'M':
                key = key[:-1]
            key = int(key)
            if self._upper >= key >= self._lower:
                return True
        return False

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.redraw()
            return InputState.PROCESSED
        else:
            return super().input(args, key)
Ejemplo n.º 54
0
class ConfigureDeviceSpoke(NormalTUISpoke):
    """ Spoke to set various configuration options for net devices. """
    category = "network"

    def __init__(self, data, storage, payload, network_module, iface,
                 connection):
        super().__init__(data, storage, payload)
        self.title = N_("Device configuration")

        self._network_module = network_module
        self._container = None
        self._connection = connection
        self._iface = iface
        self._connection_uuid = connection.get_uuid()
        self.errors = []
        self.apply_configuration = False

        self._data = WiredTUIConfigurationData()
        self._data.set_from_connection(self._connection)

        log.debug("Configure iface %s: connection %s -> %s", self._iface,
                  self._connection_uuid, self._data)

    def refresh(self, args=None):
        """ Refresh window. """
        super().refresh(args)

        self._container = ListColumnContainer(1)

        dialog = Dialog(title=(_('IPv4 address or %s for DHCP') % '"dhcp"'),
                        conditions=[self._check_ipv4_or_dhcp])
        self._container.add(EntryWidget(dialog.title, self._data.ip),
                            self._set_ipv4_or_dhcp, dialog)

        dialog = Dialog(title=_("IPv4 netmask"),
                        conditions=[self._check_netmask])
        self._container.add(EntryWidget(dialog.title, self._data.netmask),
                            self._set_netmask, dialog)

        dialog = Dialog(title=_("IPv4 gateway"), conditions=[self._check_ipv4])
        self._container.add(EntryWidget(dialog.title, self._data.gateway),
                            self._set_ipv4_gateway, dialog)

        msg = (_(
            'IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, '
            '%(ignore)s to turn off') % {
                "auto": '"auto"',
                "dhcp": '"dhcp"',
                "ignore": '"ignore"'
            })
        dialog = Dialog(title=msg, conditions=[self._check_ipv6_config])
        self._container.add(EntryWidget(dialog.title, self._data.ipv6),
                            self._set_ipv6, dialog)

        dialog = Dialog(title=_("IPv6 default gateway"),
                        conditions=[self._check_ipv6])
        self._container.add(EntryWidget(dialog.title, self._data.ipv6gateway),
                            self._set_ipv6_gateway, dialog)

        dialog = Dialog(title=_("Nameservers (comma separated)"),
                        conditions=[self._check_nameservers])
        self._container.add(EntryWidget(dialog.title, self._data.nameserver),
                            self._set_nameservers, dialog)

        msg = _("Connect automatically after reboot")
        w = CheckboxWidget(title=msg, completed=self._data.onboot)
        self._container.add(w, self._set_onboot_handler)

        msg = _("Apply configuration in installer")
        w = CheckboxWidget(title=msg, completed=self.apply_configuration)
        self._container.add(w, self._set_apply_handler)

        self.window.add_with_separator(self._container)

        message = _("Configuring device %s.") % self._iface
        self.window.add_with_separator(TextWidget(message))

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4_or_dhcp(self, user_input, report_func):
        return IPV4_OR_DHCP_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv4(self, user_input, report_func):
        return IPV4_PATTERN_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=NETMASK_ERROR_MSG)
    def _check_netmask(self, user_input, report_func):
        return IPV4_NETMASK_WITH_ANCHORS.match(user_input) is not None

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6(self, user_input, report_func):
        return network.check_ip_address(user_input, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_ipv6_config(self, user_input, report_func):
        if user_input in ["auto", "dhcp", "ignore"]:
            return True
        addr, _slash, prefix = user_input.partition("/")
        if prefix:
            try:
                if not 1 <= int(prefix) <= 128:
                    return False
            except ValueError:
                return False
        return network.check_ip_address(addr, version=6)

    @report_if_failed(message=IP_ERROR_MSG)
    def _check_nameservers(self, user_input, report_func):
        if user_input.strip():
            addresses = [str.strip(i) for i in user_input.split(",")]
            for ip in addresses:
                if not network.check_ip_address(ip):
                    return False
        return True

    def _set_ipv4_or_dhcp(self, dialog):
        self._data.ip = dialog.run()

    def _set_netmask(self, dialog):
        self._data.netmask = dialog.run()

    def _set_ipv4_gateway(self, dialog):
        self._data.gateway = dialog.run()

    def _set_ipv6(self, dialog):
        self._data.ipv6 = dialog.run()

    def _set_ipv6_gateway(self, dialog):
        self._data.ipv6gateway = dialog.run()

    def _set_nameservers(self, dialog):
        self._data.nameserver = dialog.run()

    def _set_apply_handler(self, args):
        self.apply_configuration = not self.apply_configuration

    def _set_onboot_handler(self, args):
        self._data.onboot = not self._data.onboot

    def input(self, args, key):
        if self._container.process_user_input(key):
            return InputState.PROCESSED_AND_REDRAW
        else:
            if key.lower() == Prompt.CONTINUE:
                if self._data.ip != "dhcp" and not self._data.netmask:
                    self.errors.append(
                        _("Configuration not saved: netmask missing in static configuration"
                          ))
                else:
                    self.apply()
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

    @property
    def indirect(self):
        return True

    def apply(self):
        """Apply changes to NM connection."""
        log.debug(
            "updating connection %s:\n%s", self._connection_uuid,
            self._connection.to_dbus(NM.ConnectionSerializationFlags.ALL))

        updated_connection = NM.SimpleConnection.new_clone(self._connection)
        self._data.update_connection(updated_connection)

        # Commit the changes
        self._connection.update2(
            updated_connection.to_dbus(NM.ConnectionSerializationFlags.ALL),
            NM.SettingsUpdate2Flags.TO_DISK
            | NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT, None, None,
            self._connection_updated_cb, self._connection_uuid)

    def _connection_updated_cb(self, connection, result, connection_uuid):
        connection.update2_finish(result)
        log.debug("updated connection %s:\n%s", connection_uuid,
                  connection.to_dbus(NM.ConnectionSerializationFlags.ALL))
Ejemplo n.º 55
0
class AutoPartSpoke(NormalTUISpoke):
    """ Autopartitioning options are presented here.

       .. inheritance-diagram:: AutoPartSpoke
          :parts: 3
    """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Autopartitioning Options")
        self._container = None
        self.clearPartType = self.data.clearpart.type
        self.parttypelist = sorted(PARTTYPES.keys())

    @property
    def indirect(self):
        return True

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)
        # synchronize our local data store with the global ksdata
        self.clearPartType = self.data.clearpart.type
        self._container = ListColumnContainer(1)

        # I dislike "is None", but bool(0) returns false :(
        if self.clearPartType is None:
            # Default to clearing everything.
            self.clearPartType = CLEARPART_TYPE_ALL

        for part_type in self.parttypelist:
            c = CheckboxWidget(title=_(part_type), completed=(PARTTYPES[part_type] == self.clearPartType))
            self._container.add(c, self._select_partition_type_callback, part_type)

        self.window.add_with_separator(self._container)

        message = _("Installation requires partitioning of your hard drive. "
                    "Select what space to use for the install target.")

        self.window.add_with_separator(TextWidget(message))

    def _select_partition_type_callback(self, data):
        self.clearPartType = PARTTYPES[data]
        self.apply()

    def apply(self):
        # kind of a hack, but if we're actually getting to this spoke, there
        # is no doubt that we are doing autopartitioning, so set autopart to
        # True. In the case of ks installs which may not have defined any
        # partition options, autopart was never set to True, causing some
        # issues. (rhbz#1001061)
        self.data.autopart.autopart = True
        self.data.clearpart.type = self.clearPartType
        self.data.clearpart.initAll = True

    def input(self, args, key):
        """Grab the choice and update things"""
        if not self._container.process_user_input(key):
            # TRANSLATORS: 'c' to continue
            if key.lower() == C_('TUI|Spoke Navigation', 'c'):
                new_spoke = PartitionSchemeSpoke(self.data, self.storage,
                                                 self.payload, self.instclass)
                ScreenHandler.push_screen_modal(new_spoke)
                self.apply()
                self.close()
                return InputState.PROCESSED
            else:
                return super(AutoPartSpoke, self).input(args, key)

        self.redraw()
        return InputState.PROCESSED