Ejemplo n.º 1
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.º 2
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.º 3
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.º 4
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.º 5
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.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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))
Ejemplo n.º 10
0
    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 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()
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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.")
        ))
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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))
Ejemplo n.º 16
0
    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))
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
    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)
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
    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))
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
    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))
Ejemplo n.º 26
0
    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))
Ejemplo n.º 27
0
    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))
Ejemplo n.º 28
0
    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)

        if args is None:
            msg = self._refresh_environments()
        else:
            msg = self._refresh_addons(args)

        self.window.add_with_separator(TextWidget(msg))
        self.window.add_with_separator(self._container)
Ejemplo n.º 29
0
    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)
Ejemplo n.º 30
0
    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)
Ejemplo n.º 31
0
class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke):
    """
    Since this class inherits from the FirstbootOnlySpokeMixIn, it will
    only appear in the Initial Setup (successor of the Firstboot tool).

    :see: pyanaconda.ui.tui.TUISpoke
    :see: pyanaconda.ui.common.FirstbootSpokeMixIn
    :see: pyanaconda.ui.tui.tuiobject.TUIObject
    :see: pyaanconda.ui.tui.simpleline.Widget

    """

    ### class attributes defined by API ###

    # category this spoke belongs to
    category = SystemCategory

    def __init__(self, data, storage, payload):
        """
        :see: pyanaconda.ui.tui.base.UIScreen
        :see: pyanaconda.ui.tui.base.App
        :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

        """

        NormalTUISpoke.__init__(self, data, storage, payload)

        self.initialize_start()

        # title of the spoke
        self.title = N_("Qubes OS")

        self._container = None

        self.qubes_data = self.data.addons.org_qubes_os_initial_setup

        for attr in self.qubes_data.bool_options:
            setattr(self, '_' + attr, getattr(self.qubes_data, attr))

        self.initialize_done()

    def initialize(self):
        """
        The initialize method that is called after the instance is created.
        The difference between __init__ and this method is that this may take
        a long time and thus could be called in a separated thread.

        :see: pyanaconda.ui.common.UIObject.initialize

        """

        NormalTUISpoke.initialize(self)

    def _add_checkbox(self, name, title):
        w = CheckboxWidget(title=title, completed=getattr(self, name))
        self._container.add(w, self._set_checkbox, name)

    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: pyanaconda.ui.tui.base.UIScreen.refresh
        :param args: optional argument that may be used when the screen is
                     scheduled (passed to App.switch_screen* methods)
        :type args: anything
        :return: whether this screen requests input or not
        :rtype: bool

        """
        super(QubesOsSpoke, self).refresh()
        self._container = ListColumnContainer(1)

        w = CheckboxWidget(title=_('Create default system qubes '
                                   '(sys-net, sys-firewall, default DispVM)'),
                           completed=self._system_vms)
        self._container.add(w, self._set_checkbox, '_system_vms')
        w = CheckboxWidget(title=_('Create default application qubes '
                                   '(personal, work, untrusted, vault)'),
                           completed=self._default_vms)
        self._container.add(w, self._set_checkbox, '_default_vms')
        if self.qubes_data.whonix_available:
            w = CheckboxWidget(title=_(
                'Create Whonix Gateway and Workstation qubes '
                '(sys-whonix, anon-whonix)'),
                               completed=self._whonix_vms)
            self._container.add(w, self._set_checkbox, '_whonix_vms')
        if self._whonix_vms:
            w = CheckboxWidget(title=_(
                'Enable system and template updates over the Tor anonymity '
                'network using Whonix'),
                               completed=self._whonix_default)
            self._container.add(w, self._set_checkbox, '_whonix_default')
        if self.qubes_data.usbvm_available:
            w = CheckboxWidget(title=_(
                'Create USB qube holding all USB controllers (sys-usb)'),
                               completed=self._usbvm)
            self._container.add(w, self._set_checkbox, '_usbvm')
        if self._usbvm:
            w = CheckboxWidget(title=_(
                'Use sys-net qube for both networking and USB devices'),
                               completed=self._usbvm_with_netvm)
            self._container.add(w, self._set_checkbox, '_usbvm_with_netvm')

        self.window.add_with_separator(self._container)

    def _set_checkbox(self, name):
        setattr(self, name, not getattr(self, name))

    def apply(self):
        """
        The apply method that is called when the spoke is left. It should
        update the contents of self.data with values set in the spoke.

        """

        for attr in self.qubes_data.bool_options:
            setattr(self.qubes_data, attr, getattr(self, '_' + attr))

        self.qubes_data.seen = True

    def execute(self):
        """
        The excecute method that is called when the spoke is left. It is
        supposed to do all changes to the runtime environment according to
        the values set in the spoke.

        """

        # nothing to do here
        pass

    @property
    def completed(self):
        """
        The completed property that tells whether all mandatory items on the
        spoke are set, or not. The spoke will be marked on the hub as completed
        or uncompleted acording to the returned value.

        :rtype: bool

        """

        return self.qubes_data.seen

    @property
    def status(self):
        """
        The status property that is a brief string describing the state of the
        spoke. It should describe whether all values are set and if possible
        also the values themselves. The returned value will appear on the hub
        below the spoke's title.

        :rtype: str

        """

        return ""

    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 (passed to App.switch_screen* methods)
        :type args: anything
        :param key: user's input
        :type key: unicode
        :return: if the input should not be handled here, return it, otherwise
                 return INPUT_PROCESSED or INPUT_DISCARDED if the input was
                 processed succesfully or not respectively
        :rtype: bool|unicode

        """

        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW

        return super().input(args, key)
Ejemplo n.º 32
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

    @staticmethod
    def get_screen_id():
        """Return a unique id of this UI screen."""
        return "language-configuration"

    @classmethod
    def should_run(cls, environment, data):
        """Should the spoke run?"""
        if not is_module_available(LOCALIZATION):
            return False

        return FirstbootSpokeMixIn.should_run(environment, data)

    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 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:

            if key.lower() == PROMPT_BACK_KEY:
                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"))
        prompt.add_option(PROMPT_BACK_KEY, _(PROMPT_BACK_DESCRIPTION))
        return prompt

    def apply(self):
        """ Store the selected lang support locales """
        self._l12_module.SetLanguage(self._selected)
Ejemplo n.º 33
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):
        super().__init__(data, storage, payload, instclass)

        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.selected_disks = self._disk_select_observer.proxy.SelectedDisks

        self.title = N_("Installation Destination")
        self._ready = False
        self._container = None
        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 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. """
        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.data.autopart.autopart:
            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. """
        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):
        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.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.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:
                    # Is DASD formatting supported?
                    if DasdFormatting.is_supported():
                        # Wait for storage.
                        threadMgr.wait(THREAD_STORAGE)

                        # Get selected disks.
                        disks = getDisksByNames(self.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
                            applyDiskSelection(self.storage, self.data,
                                               self.selected_disks)

                            # Run the dialog.
                            self.run_dasdfmt_dialog(dasd_formatting)
                            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().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 apply(self):
        self.autopart = self.data.autopart.autopart

        self._disk_select_observer.proxy.SetSelectedDisks(self.selected_disks)
        self._disk_init_observer.proxy.SetDrivesToClear(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()

        # 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._disk_init_observer.proxy.SetInitializationMode(
                CLEAR_PARTITIONS_ALL)
            self._disk_init_observer.proxy.SetInitializeLabelsEnabled(False)
            self.storage.config.update()
            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._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 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.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])
Ejemplo n.º 34
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.º 35
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

    @classmethod
    def should_run(cls, environment, data):
        """Don't run for any non-package payload."""
        return context.payload.type == PAYLOAD_TYPE_DNF

    def __init__(self, data, storage, payload):
        super().__init__(data, storage, payload)
        self.title = N_("Software selection")
        self._container = None
        self.errors = []
        self._tx_id = None

        # Get the packages configuration.
        self._selection = self.payload.get_packages_data()

        # are we taking values (package list) from a kickstart file?
        self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted

        # Register event listeners to update our status on payload events
        payloadMgr.add_listener(PayloadState.STARTED, self._payload_start)
        payloadMgr.add_listener(PayloadState.ERROR, self._payload_error)

    def initialize(self):
        """Initialize the spoke."""
        super().initialize()
        self.initialize_start()

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

    def _initialize(self):
        """Initialize the spoke in a separate thread."""
        threadMgr.wait(THREAD_PAYLOAD)

        if not self._kickstarted:
            # Set the environment.
            self.set_default_environment()

            # Apply the initial selection.
            self.apply()

        # Check the initial software selection.
        self.execute()

        # Wait for the software selection thread that might be started by execute().
        # 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 set_default_environment(self):
        # If an environment was specified in the configuration, use that.
        # Otherwise, select the first environment.
        if self.payload.environments:
            environments = self.payload.environments

            if conf.payload.default_environment in environments:
                self._selection.environment = conf.payload.default_environment
            else:
                self._selection.environment = environments[0]

    def _payload_start(self):
        self.errors = []

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

    def _translate_env_name_to_id(self, environment):
        """ Return the id of the selected environment or None. """
        if not environment:
            # None means environment is not set, no need to try translate that to an id
            return None
        try:
            return self.payload.environment_id(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.environment_addons:
            for addons_list in self.payload.environment_addons[environment_id]:
                addons.extend(addons_list)

        return addons

    @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.base_repo:
            return _("Installation source not set up")
        if not self.txid_valid:
            return _("Source changed - please verify")
        if not self._selection.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.environment_description(
            self._selection.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.base_repo and self.payload.proxy.PackagesKickstarted
        else:
            return processing_done and self.payload.base_repo and self._selection.environment

    def setup(self, args):
        """Set up the spoke right before it is used."""
        super().setup(args)

        # Join the initialization thread to block on it
        threadMgr.wait(THREAD_SOFTWARE_WATCHER)

        # Get the packages configuration.
        self._selection = self.payload.get_packages_data()

        return True

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

        threadMgr.wait(THREAD_PAYLOAD)
        self._container = None

        if not self.payload.base_repo:
            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)

        if args is None:
            msg = self._refresh_environments()
        else:
            msg = self._refresh_addons(args)

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

    def _refresh_environments(self):
        environments = self.payload.environments

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

        return _("Base environment")

    def _refresh_addons(self, available_addons):
        for addon_id in available_addons:
            name = self.payload.group_description(addon_id)[0]
            selected = addon_id in self._selection.groups
            widget = CheckboxWidget(title="%s" % name, completed=selected)
            self._container.add(widget,
                                callback=self._set_addons_callback,
                                data=addon_id)

        if available_addons:
            return _("Additional software for selected environment")
        else:
            return _("No additional software to select.")

    def _set_environment_callback(self, data):
        self._selection.environment = data

    def _set_addons_callback(self, data):
        if data not in self._selection.groups:
            self._selection.groups.append(data)
        else:
            self._selection.groups.remove(data)

    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 not self._selection.environment:
                    self.close()

                # The environment was selected, switch screen
                elif args is None:
                    # Get addons for the selected environment
                    environment = self._selection.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.execute()
                    self.close()
            else:
                return super().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 the changes."""
        self._kickstarted = False

        # Clear packages data.
        self._selection.packages = []
        self._selection.excluded_packages = []

        # Clear groups data.
        self._selection.excluded_groups = []
        self._selection.groups_package_types = {}

        # Select valid groups.
        # FIXME: Remove invalid groups from selected groups.

        log.debug("Setting new software selection: %s", self._selection)
        self.payload.set_packages_data(self._selection)

    def execute(self):
        """Execute the changes."""
        threadMgr.add(
            AnacondaThread(name=THREAD_CHECK_SOFTWARE,
                           target=self._check_software_selection))

    def _check_software_selection(self):
        """Check the software selection."""
        try:
            self.payload.check_software_selection()
        except DependencyError as e:
            self.errors = [str(e)]
            self._tx_id = None
            log.warning("Transaction error %s", str(e))
        else:
            self._tx_id = self.payload.tx_id

    @property
    def txid_valid(self):
        """ Whether we have a valid dnf tx id. """
        return self._tx_id == self.payload.tx_id
Ejemplo n.º 36
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.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 = payload_utils.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
            payload_utils.mount_device(self._device, ISO_DIR)

    def _unmount_device(self):
        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:
            # 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.º 37
0
class SelectDeviceSpoke(NormalTUISpoke):
    """ Select device containing the install source ISO file. """
    category = SoftwareCategory

    def __init__(self, data, storage, payload, instclass):
        NormalTUISpoke.__init__(self, data, storage, payload, instclass)
        self.title = N_("Select device containing the ISO file")
        self._container = None
        self._currentISOFile = 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):
        NormalTUISpoke.refresh(self, args)

        # 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:
            self._container = ListColumnContainer(1,
                                                  columns_width=78,
                                                  spacing=1)

            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(SelectDeviceSpoke, self).input(args, key)

    # Override Spoke.apply
    def apply(self):
        pass
Ejemplo n.º 38
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

        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.º 39
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.º 40
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"

    @staticmethod
    def get_screen_id():
        """Return a unique id of this UI screen."""
        return "software-source-configuration"

    @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.º 41
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.º 42
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.º 43
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):
        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 = self._network_module.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.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 / ifcfg configuration), instead of using original kickstart.
        self._network_module.NetworkDeviceConfigurationChanged()

        (valid, error) = network.is_valid_hostname(self.hostname, local=True)
        if 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.º 44
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):
        NormalTUISpoke.__init__(self, data, storage, payload)
        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.add_listener(PayloadState.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 = find_optical_install_media()

        # Enable the SE/HMC option.
        if self.payload.is_hmc_enabled:
            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.base_repo:
            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.base_repo:
            return False
        else:
            return (not self._error
                    and self.ready
                    and (self.data.method.method or self.payload.base_repo))

    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 \
           payload_utils.get_mount_device_path(DRACUT_ISODIR) == \
                payload_utils.get_mount_device_path(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)
        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):
        self.set_source_nfs()
        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.º 45
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."""
        passphrase = None

        for device_name in self._rescue.get_locked_device_names():
            while True:
                if passphrase is None:
                    dialog = PasswordDialog(device_name)
                    ScreenHandler.push_screen_modal(dialog)
                    if not dialog.answer:
                        break

                    passphrase = dialog.answer.strip()

                if self._rescue.unlock_device(device_name, passphrase):
                    break

                passphrase = None

    def apply(self):
        """Move along home."""
        pass
Ejemplo n.º 46
0
class PartTypeSpoke(NormalTUISpoke):
    """ Partitioning options are presented here.

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

    def __init__(self, data, storage, payload, storage_module, partitioning):
        super().__init__(data, storage, payload)
        self.title = N_("Partitioning Options")
        self._container = None

        # Choose the initialization mode.
        self._disk_init_proxy = STORAGE.get_proxy(DISK_INITIALIZATION)
        self._orig_init_mode = self._disk_init_proxy.InitializationMode
        self._init_mode = self._orig_init_mode
        self._init_mode_list = sorted(INIT_MODES.keys())

        if self._init_mode == CLEAR_PARTITIONS_DEFAULT:
            self._init_mode = CLEAR_PARTITIONS_ALL

        # Choose the partitioning method.
        self._storage_module = storage_module
        self._partitioning = partitioning
        self._orig_part_method = self._partitioning.PartitioningMethod
        self._part_method = self._orig_part_method

        if self._part_method == PARTITIONING_METHOD_MANUAL:
            self._init_mode = CLEAR_PARTITIONS_NONE

    @property
    def indirect(self):
        return True

    @property
    def partitioning(self):
        return self._partitioning

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

        for init_mode in self._init_mode_list:
            c = CheckboxWidget(title=_(init_mode), completed=(
                self._part_method == PARTITIONING_METHOD_AUTOMATIC
                and self._init_mode == INIT_MODES[init_mode]
            ))
            self._container.add(c, self._select_partition_type_callback, init_mode)

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

        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._part_method = PARTITIONING_METHOD_MANUAL
        self._init_mode = CLEAR_PARTITIONS_NONE

    def _select_partition_type_callback(self, data):
        self._part_method = PARTITIONING_METHOD_AUTOMATIC
        self._init_mode = INIT_MODES[data]

    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._disk_init_proxy.SetInitializationMode(self._init_mode)
        self._disk_init_proxy.SetInitializeLabelsEnabled(
            self._part_method == PARTITIONING_METHOD_AUTOMATIC
        )

        if self._orig_part_method != self._part_method:
            self._partitioning = create_partitioning(self._part_method)

    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_part_method == self._part_method == PARTITIONING_METHOD_MANUAL:
            return

        # else
        print(_("Reverting previous configuration. This may take a moment..."))
        reset_storage(scan_all=True)

    def input(self, args, key):
        """Grab the choice and update things"""
        if not self._container.process_user_input(key):
            if key.lower() == Prompt.CONTINUE:
                self.apply()
                self._ensure_init_storage()
                if self._part_method == PARTITIONING_METHOD_MANUAL:
                    new_spoke = MountPointAssignSpoke(
                        self.data, self.storage, self.payload, self._partitioning
                    )
                else:
                    new_spoke = PartitionSchemeSpoke(
                        self.data, self.storage, self.payload, self._partitioning
                    )
                ScreenHandler.push_screen_modal(new_spoke)
                return InputState.PROCESSED_AND_CLOSE
            else:
                return super().input(args, key)

        return InputState.PROCESSED_AND_REDRAW
Ejemplo n.º 47
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.º 48
0
class PartitionSchemeSpoke(NormalTUISpoke):
    """ Spoke to select what partitioning scheme to use on disk(s). """
    category = SystemCategory

    def __init__(self, data, storage, payload, partitioning):
        super().__init__(data, storage, payload)
        self.title = N_("Partition Scheme Options")
        self._container = None
        self._part_schemes = OrderedDict()
        self._partitioning = partitioning
        self._request = PartitioningRequest.from_structure(
            self._partitioning.Request
        )

        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]

        selected_choice = self._request.partitioning_scheme

        for item in supported_choices:
            self._part_schemes[item[0]] = item[1]
            if item[1] == selected_choice:
                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
        self._request.partitioning_scheme = data

    def input(self, args, key):
        """ Grab the choice and update things. """
        if not self._container.process_user_input(key):
            if key.lower() == Prompt.CONTINUE:
                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._partitioning.SetRequest(
            PartitioningRequest.to_structure(self._request)
        )
Ejemplo n.º 49
0
class NTPServersSpoke(NormalTUISpoke):
    category = LocalizationCategory

    def __init__(self, data, storage, payload, instclass, time_spoke):
        NormalTUISpoke.__init__(self, 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):
        NormalTUISpoke.refresh(self, 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(NTPServersSpoke, self).input(args, key)

    def apply(self):
        pass
Ejemplo n.º 50
0
class MountPointAssignSpoke(NormalTUISpoke):
    """ Assign mount points to block devices. """
    category = SystemCategory

    def __init__(self, data, storage, payload, partitioning):
        super().__init__(data, storage, payload)
        self.title = N_("Assign mount points")
        self._container = None
        self._partitioning = partitioning
        self._device_tree = STORAGE.get_proxy(self._partitioning.GetDeviceTree())
        self._requests = self._gather_requests()

    @property
    def indirect(self):
        return True

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

        for request in self._requests:
            widget = TextWidget(self._get_request_description(request))
            self._container.add(widget, self._configure_request, request)

        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)
        prompt.add_option(PROMPT_SCAN_KEY, _(PROMPT_SCAN_DESCRIPTION))
        return prompt

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

        if key.lower() == PROMPT_SCAN_KEY:
            self._rescan_devices()
            return InputState.PROCESSED_AND_REDRAW

        elif key.lower() == Prompt.CONTINUE:
            self.apply()

        return super().input(args, key)

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

        for request in self._requests:
            if request.reformat or request.mount_point:
                if not request.mount_point:
                    request.mount_point = "none"

                mount_points.append(request)

        self._partitioning.SetRequests(
            MountPointRequest.to_structure_list(mount_points)
        )

    def _gather_requests(self):
        """Gather info about mount points."""
        return MountPointRequest.from_structure_list(
            self._partitioning.GatherRequests()
        )

    def _get_request_description(self, request):
        """Get description of the given mount info."""
        # Get the device data.
        device_name = self._device_tree.ResolveDevice(request.device_spec)
        device_data = DeviceData.from_structure(
            self._device_tree.GetDeviceData(device_name)
        )

        # Generate the description.
        description = "{} ({})".format(request.device_spec, Size(device_data.size))

        if request.format_type:
            description += "\n {}".format(request.format_type)

            if request.reformat:
                description += "*"

            if request.mount_point:
                description += ", {}".format(request.mount_point)

        return description

    def _configure_request(self, request):
        """Configure the given mount request."""
        spoke = ConfigureDeviceSpoke(
            self.data, self.storage, self.payload, self._device_tree, request
        )
        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(scan_all=True)

        # Forget the mount point requests.
        self._partitioning.SetRequests([])
        self._requests = self._gather_requests()
Ejemplo n.º 51
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
        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._policy = self.data.anaconda.pwpolicy.get_policy(
            "user", fallback_to_default=True)

        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=self._policy)
                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 self._policy.changesok)

    @property
    def mandatory(self):
        """Only mandatory if no admin user has been requested."""
        return not self._users_module.CheckAdminUserExists()

    @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)
Ejemplo n.º 52
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

    @classmethod
    def should_run(cls, environment, data):
        """Don't run the storage spoke on dir installations."""
        if not NormalTUISpoke.should_run(environment, data):
            return False

        return not conf.target.is_directory

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

        self._storage_module = STORAGE.get_proxy()
        self._device_tree = STORAGE.get_proxy(DEVICE_TREE)
        self._bootloader_module = STORAGE.get_proxy(BOOTLOADER)
        self._disk_init_module = STORAGE.get_proxy(DISK_INITIALIZATION)
        self._disk_select_module = STORAGE.get_proxy(DISK_SELECTION)

        self._available_disks = []
        self._selected_disks = []

        # Is the partitioning already configured?
        self._is_preconfigured = bool(self._storage_module.CreatedPartitioning)

        # Find a partitioning to use.
        self._partitioning = find_partitioning()

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

    @property
    def completed(self):
        return self.ready and not self.errors and self._device_tree.GetRootDevice()

    @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) \
            and not threadMgr.get(THREAD_STORAGE_WATCHER)

    @property
    def mandatory(self):
        return True

    @property
    def status(self):
        """ A short string describing the current status of storage setup. """
        if not self.ready:
            return _("Processing...")
        elif flags.automatedInstall and not self._device_tree.GetRootDevice():
            return _("Kickstart insufficient")
        elif not self._disk_select_module.SelectedDisks:
            return _("No disks selected")
        if self.errors:
            return _("Error checking storage configuration")
        elif self.warnings:
            return _("Warning checking storage configuration")
        elif self._partitioning.PartitioningMethod == PARTITIONING_METHOD_AUTOMATIC:
            return _("Automatic partitioning selected")
        else:
            return _("Custom partitioning selected")

    def _update_disk_list(self, name):
        """ Update self.selected_disks based on the selection."""
        # 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(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 setup(self, args=None):
        """Set up the spoke right before it is used."""
        super().setup(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)

        self._available_disks = self._disk_select_module.GetUsableDisks()
        self._selected_disks = self._disk_select_module.SelectedDisks

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

        # Get the available partitioning.
        object_path = self._storage_module.CreatedPartitioning[-1]
        self._partitioning = STORAGE.get_proxy(object_path)

        return True

    def refresh(self, args=None):
        """Prepare the content of the screen."""
        super().refresh(args)

        # Create a new container.
        self._container = ListColumnContainer(1, spacing=1)

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

        # 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(self._update_summary()))

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

    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.
        """
        data = DeviceData.from_structure(
            self._device_tree.GetDeviceData(disk)
        )

        # show this info for all disks
        format_str = "{}: {} ({})".format(
            data.attrs.get("model", "DISK"),
            Size(data.size),
            data.name
        )

        # now append all additional attributes to our string
        disk_attrs = filter(None, map(data.attrs.get, (
            "wwn", "bus-id", "fcp-lun", "wwpn", "hba-id"
        )))

        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:
            if key.lower() == Prompt.CONTINUE:
                if self._selected_disks:
                    # Is DASD formatting supported?
                    if DasdFormatting.is_supported():
                        # Wait for storage.
                        threadMgr.wait(THREAD_STORAGE)

                        # Allow to format DASDs.
                        self._disk_init_module.SetFormatUnrecognizedEnabled(True)
                        self._disk_init_module.SetFormatLDLEnabled(True)

                        # 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._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
                    report = ValidationReport.from_structure(
                        self._disk_select_module.ValidateSelectedDisks(self._selected_disks)
                    )
                    self.errors.extend(report.get_messages())

                    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,
                                              self._storage_module, self._partitioning)
                    ScreenHandler.push_screen_modal(new_spoke)
                    self._partitioning = new_spoke.partitioning
                    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()
        dasd_formatting.report.disconnect(self._show_dasdfmt_report)

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

    def run_passphrase_dialog(self):
        """Ask user for a default passphrase."""
        if not self._is_passphrase_required():
            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_name=PASSWORD_POLICY_LUKS,
            process_func=lambda x: x
        )

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

        self._set_required_passphrase(passphrase)

    def _is_passphrase_required(self):
        """Is the default passphrase required?"""
        return self._partitioning.PartitioningMethod in (
            PARTITIONING_METHOD_AUTOMATIC,
            PARTITIONING_METHOD_CUSTOM
        ) and self._partitioning.RequiresPassphrase()

    def _set_required_passphrase(self, passphrase):
        """Set the required passphrase."""
        self._partitioning.SetPassphrase(passphrase)

    def apply(self):
        self._bootloader_module.SetPreferredLocation(BOOTLOADER_LOCATION_MBR)
        apply_disk_selection(self._selected_disks, reset_boot_drive=True)

    def execute(self):
        report = apply_partitioning(self._partitioning, self._show_execute_message)

        log.debug("Partitioning has been applied: %s", report)
        self.errors = list(report.error_messages)
        self.warnings = list(report.warning_messages)

        print("\n".join(report.get_messages()))
        self._ready = True

    def _show_execute_message(self, msg):
        print(msg)
        log.debug(msg)

    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))

    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.
        disks = self._disk_select_module.GetUsableDisks()
        DasdFormatting.run_automatically(disks)

        # Update the selected disks.
        select_default_disks()

        # Automatically apply the preconfigured partitioning.
        if flags.automatedInstall and self._is_preconfigured:
            self.execute()

        # Storage is ready.
        self._ready = True

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

    def closed(self):
        """The spoke has been closed."""
        super().closed()

        # Run the setup method again on entry.
        self.screen_ready = False
Ejemplo n.º 53
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):
        super().__init__(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):
        super().refresh(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().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.º 54
0
class ConfigureDeviceSpoke(NormalTUISpoke):
    """ Assign mount point to a block device and (optionally) reformat it. """
    category = SystemCategory

    def __init__(self, data, storage, payload, device_tree, request):
        super().__init__(data, storage, payload)
        self.title = N_("Configure device: %s") % request.device_spec
        self._container = None
        self._device_tree = device_tree
        self._request = request
        self._supported_filesystems = set(device_tree.GetSupportedFileSystems())

    @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 = DeviceFormatData.from_structure(
            self._device_tree.GetFormatTypeData(self._request.format_type)
        )

        if fmt.mountable:
            # mount point can be set
            value = self._request.mount_point or _("none")
            callback = self._assign_mount_point
        elif not fmt.type:
            # 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.description
            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._request.mount_point = dialog.run()

        # Always reformat root.
        if self._request.mount_point == "/":
            self._request.reformat = True

    def _add_format_widget(self):
        """Add a widget for format."""
        dialog = Dialog(_("Format"), conditions=[self._check_format])
        widget = EntryWidget(dialog.title, self._request.format_type 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._request.format_type
        new_format = dialog.run()

        # Reformat to a new format.
        if new_format != old_format:
            self._request.format_type = new_format
            self._request.reformat = True

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

    def _switch_reformat(self, data):
        """Change value of reformat."""
        device_name = self._device_tree.ResolveDevice(
            self._request.device_spec
        )
        format_data = DeviceFormatData.from_structure(
            self._device_tree.GetFormatData(device_name)
        )
        device_format = format_data.type

        if device_format and device_format != self._request.format_type:
            reformat = True
        elif self._request.mount_point == "/":
            reformat = True
        else:
            reformat = not self._request.reformat

        self._request.reformat = reformat
Ejemplo n.º 55
0
class MountPointAssignSpoke(NormalTUISpoke):
    """ Assign mount points to block devices. """
    category = SystemCategory

    def __init__(self, data, storage, payload, instclass):
        super().__init__(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, selected_disks):
        maybe = not dev.protected and dev.size != Size(0)
        if maybe and selected_disks:
            # all device's disks have to be in selected disks
            maybe = set(selected_disks).issuperset({d.name for d in dev.disks})

        return maybe

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

        disk_select_proxy = STORAGE.get_proxy(DISK_SELECTION)
        selected_disks = disk_select_proxy.SelectedDisks

        for device in self.storage.devicetree.leaves:
            if not self._is_dev_usable(device, selected_disks):
                continue

            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().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):
        super().refresh(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 selected disks temporarily so that
                    # storage_initialize() processes all devices
                    disk_select_proxy = STORAGE.get_proxy(DISK_SELECTION)
                    selected_disks = disk_select_proxy.SelectedDisks
                    disk_select_proxy.SetSelectedDisks([])

                    print(_("Scanning disks. This may take a moment..."))
                    storage_initialize(self.storage, self.data,
                                       self.storage.protected_dev_names)

                    disk_select_proxy.SetSelectedDisks(selected_disks)
                    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().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.º 56
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)

        # Forget the mount point requests.
        self._manual_part_proxy.SetRequests([])

    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.º 57
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.º 58
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.º 59
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.º 60
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:
            # 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))

        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))