diff --git a/anaconda.py b/anaconda.py
index 9e95b8af6a3..eaa714992b2 100755
--- a/anaconda.py
+++ b/anaconda.py
@@ -594,14 +594,6 @@ def _earlyExceptionHandler(ty, value, traceback):
threadMgr.add(AnacondaThread(name=constants.THREAD_WAIT_FOR_CONNECTING_NM,
target=wait_for_connecting_NM_thread))
- # initialize the screen access manager before launching the UI
- from pyanaconda import screen_access
- screen_access.initSAM()
- # try to open any existing config file
- # (might be created by pre-anaconda helper tools, injected during image
- # generation, etc.)
- screen_access.sam.open_config_file()
-
# now start the interface
display.setup_display(anaconda, opts)
if anaconda.gui_startup_failed:
diff --git a/data/anaconda.conf b/data/anaconda.conf
index 612ae9f844c..2cc02d51c08 100644
--- a/data/anaconda.conf
+++ b/data/anaconda.conf
@@ -140,6 +140,9 @@ default_help_pages =
# Is the partitioning with blivet-gui supported?
blivet_gui_supported = True
+# A list of spokes to hide in UI.
+# FIXME: Use other identification then names of the spokes.
+hidden_spokes =
[License]
# A path to EULA (if any)
diff --git a/data/liveinst/liveinst b/data/liveinst/liveinst
index 7a632fa3ae0..29fa95dcfc8 100755
--- a/data/liveinst/liveinst
+++ b/data/liveinst/liveinst
@@ -74,7 +74,7 @@ fi
if [ -f /etc/os-release ]; then
variantid=$( grep VARIANT_ID /etc/os-release | tail -1 | cut -d= -f2)
if [ "$variantid" = "workstation" ]; then
- export ANACONDA_PRODUCTVARIANT="Workstation"
+ export ANACONDA_PRODUCTVARIANT="Workstation Live"
fi
fi
diff --git a/data/product.d/fedora-silverblue.conf b/data/product.d/fedora-silverblue.conf
index 15401c2bbc1..c0bf3454ceb 100644
--- a/data/product.d/fedora-silverblue.conf
+++ b/data/product.d/fedora-silverblue.conf
@@ -6,4 +6,4 @@ variant_name = Silverblue
[Base Product]
product_name = Fedora
-variant_name = Workstation
+variant_name = Workstation Live
diff --git a/data/product.d/fedora-workstation-live.conf b/data/product.d/fedora-workstation-live.conf
new file mode 100644
index 00000000000..bfd7581275b
--- /dev/null
+++ b/data/product.d/fedora-workstation-live.conf
@@ -0,0 +1,15 @@
+# Anaconda configuration file for Fedora Workstation Live.
+
+[Product]
+product_name = Fedora
+variant_name = Workstation Live
+
+[Base Product]
+product_name = Fedora
+variant_name = Workstation
+
+[User Interface]
+hidden_spokes =
+ NetworkSpoke
+ PasswordSpoke
+ UserSpoke
diff --git a/pyanaconda/core/configuration/system.py b/pyanaconda/core/configuration/system.py
index 77fae1f9880..3dc008b1c02 100644
--- a/pyanaconda/core/configuration/system.py
+++ b/pyanaconda/core/configuration/system.py
@@ -167,11 +167,6 @@ def provides_resolver_config(self):
"""Can we copy /etc/resolv.conf to the target system?"""
return self._is_boot_iso
- @property
- def provides_user_interaction_config(self):
- """Can we read /etc/sysconfig/anaconda?"""
- return self._is_boot_iso or self._is_live_os
-
@property
def provides_web_browser(self):
"""Can we redirect users to web pages?"""
diff --git a/pyanaconda/core/configuration/ui.py b/pyanaconda/core/configuration/ui.py
index 04b2ceff98a..b009d2d877e 100644
--- a/pyanaconda/core/configuration/ui.py
+++ b/pyanaconda/core/configuration/ui.py
@@ -45,3 +45,11 @@ def default_help_pages(self):
def blivet_gui_supported(self):
"""Is the partitioning with blivet-gui supported?"""
return self._get_option("blivet_gui_supported", bool)
+
+ @property
+ def hidden_spokes(self):
+ """A list of spokes to hide in UI.
+
+ :return: a list of strings
+ """
+ return self._get_option("hidden_spokes", str).split()
diff --git a/pyanaconda/installation.py b/pyanaconda/installation.py
index 7bee9f0dead..9ea204fb318 100644
--- a/pyanaconda/installation.py
+++ b/pyanaconda/installation.py
@@ -40,7 +40,6 @@
from pyanaconda.core import util
from pyanaconda import timezone
from pyanaconda import network
-from pyanaconda import screen_access
from pyanaconda.core.i18n import N_
from pyanaconda.threading import threadMgr
from pyanaconda.ui.lib.entropy import wait_for_entropy
@@ -113,11 +112,6 @@ def doConfiguration(storage, payload, ksdata):
task_proxy = SERVICES.get_proxy(dbus_task)
os_config.append(Task(task_proxy.Name, sync_run_task, (task_proxy,)))
- # The user interaction config file needs to be synchronized with
- # current state of the Services module.
- os_config.append(Task("Synchronize user interaction config file state",
- screen_access.sam.update_config_file_state))
-
os_config.append(Task("Configure keyboard", ksdata.keyboard.execute))
os_config.append(Task("Configure timezone", ksdata.timezone.execute))
@@ -212,18 +206,6 @@ def doConfiguration(storage, payload, ksdata):
# write anaconda related configs & kickstarts
write_configs.append(Task("Store kickstarts", _writeKS, (ksdata,)))
- # Write out the user interaction config file.
- #
- # But make sure it's not written out in the image and directory installation mode,
- # as that might result in spokes being inadvertently hidden when the actual installation
- # starts from the generate image or directory contents.
- if conf.target.is_image:
- log.info("Not writing out user interaction config file due to image install mode.")
- elif conf.target.is_directory:
- log.info("Not writing out user interaction config file due to directory install mode.")
- else:
- write_configs.append(Task("Store user interaction config", screen_access.sam.write_out_config_file))
-
# only add write_configs to the main queue if we actually store some kickstarts/configs
if write_configs.task_count:
configuration_queue.append(write_configs)
diff --git a/pyanaconda/modules/services/installation.py b/pyanaconda/modules/services/installation.py
index 953d02fa174..9e08f34736f 100644
--- a/pyanaconda/modules/services/installation.py
+++ b/pyanaconda/modules/services/installation.py
@@ -16,16 +16,20 @@
# Red Hat, Inc.
#
import os
+from configparser import ConfigParser
from pyanaconda.core import util
-
+from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.services.constants import SetupOnBootAction
+from pyanaconda.startup_utils import get_anaconda_version_string
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
-__all__ = ["ConfigureInitialSetupTask", "ConfigureServicesTask"]
+
+__all__ = ["ConfigureInitialSetupTask", "ConfigurePostInstallationToolsTask",
+ "ConfigureServicesTask"]
class ConfigureInitialSetupTask(Task):
@@ -37,7 +41,7 @@ def __init__(self, sysroot, setup_on_boot):
"""Create a new Initial Setup configuration task.
:param str sysroot: a path to the root of the target system
- :param enum setup_on_boot: setup-on-boot mode for Initial Setup
+ :param Enum setup_on_boot: setup-on-boot mode for Initial Setup
Modes are defined by the SetupOnBoot enum as distinct integers.
@@ -90,6 +94,57 @@ def run(self):
self._disable_service()
+class ConfigurePostInstallationToolsTask(Task):
+ """Installation task for configuration of post-installation tools."""
+
+ def __init__(self, sysroot, tools_enabled):
+ """Create a new task.
+
+ :param str sysroot: a path to the root of the target system
+ :param bool tools_enabled: are the post-installation tools enabled?
+ """
+ super().__init__()
+ self._sysroot = sysroot
+ self._tools_enabled = tools_enabled
+
+ @property
+ def name(self):
+ return "Configure post-installation tools"
+
+ def run(self):
+ """Run the task. """
+ if conf.target.is_image:
+ log.info("Not writing out user interaction config file due to image install mode.")
+ elif conf.target.is_directory:
+ log.info("Not writing out user interaction config file due to directory install mode.")
+ else:
+ parser = self._get_config()
+ self._write_config(parser)
+
+ def _get_config(self):
+ """Generate the user interaction configuration."""
+ parser = ConfigParser(delimiters=("=", ), comment_prefixes=("#", ))
+ parser.add_section("General")
+ parser["General"]["post_install_tools_disabled"] = "1" if not self._tools_enabled else "0"
+ return parser
+
+ def _write_config(self, parser):
+ """Write the user interaction config file."""
+ path = os.path.join(self._sysroot, "etc/sysconfig/anaconda")
+ log.info("Writing out user interaction config at %s", path)
+
+ try:
+ with open(path, "wt") as f:
+ f.write(
+ "# This file has been generated by the Anaconda Installer "
+ "{}\n\n".format(get_anaconda_version_string())
+ )
+
+ parser.write(f)
+ except OSError:
+ log.exception("Can't write user interaction config file.")
+
+
class ConfigureServicesTask(Task):
"""Installation task for service configuration.
diff --git a/pyanaconda/modules/services/services.py b/pyanaconda/modules/services/services.py
index 630ec398cdc..b15a802e55d 100644
--- a/pyanaconda/modules/services/services.py
+++ b/pyanaconda/modules/services/services.py
@@ -28,8 +28,7 @@
from pyanaconda.modules.services.kickstart import ServicesKickstartSpecification
from pyanaconda.modules.services.services_interface import ServicesInterface
from pyanaconda.modules.services.installation import ConfigureInitialSetupTask, \
- ConfigureServicesTask
-
+ ConfigureServicesTask, ConfigurePostInstallationToolsTask
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
@@ -241,13 +240,19 @@ def install_with_tasks(self, sysroot):
:returns: list of object paths of installation tasks
"""
tasks = [
- ConfigureInitialSetupTask(sysroot=sysroot, setup_on_boot=self.setup_on_boot),
+ ConfigureInitialSetupTask(
+ sysroot=sysroot,
+ setup_on_boot=self.setup_on_boot
+ ),
+ ConfigurePostInstallationToolsTask(
+ sysroot=sysroot,
+ tools_enabled=self.post_install_tools_enabled
+ ),
ConfigureServicesTask(
sysroot=sysroot,
disabled_services=self.disabled_services,
enabled_services=self.enabled_services
),
-
]
paths = [
diff --git a/pyanaconda/screen_access.py b/pyanaconda/screen_access.py
deleted file mode 100644
index de7ff4dc3a8..00000000000
--- a/pyanaconda/screen_access.py
+++ /dev/null
@@ -1,257 +0,0 @@
-#
-# screen_access.py: screen access management for the Anaconda UI
-#
-# Copyright (C) 2016
-# Red Hat, Inc. All rights reserved.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# Author(s): Martin Kolman
-#
-import os
-CONFIG_FILE_NAME = "anaconda"
-CONFIG_FILE_PATH = os.path.join("/etc/sysconfig", CONFIG_FILE_NAME)
-CONFIG_TRUE = "1"
-CONFIG_FALSE = "0"
-CONFIG_BOOLEAN_TABLE = {
- CONFIG_TRUE: True,
- CONFIG_FALSE: False
-}
-CONFIG_VISITED_KEY = "visited"
-CONFIG_GENERAL_SECTION = "General"
-CONFIG_DISABLE_POSTINST_TOOLS_KEY = "post_install_tools_disabled"
-CONFIG_OPTION_PREFIX = "changed_"
-
-from pyanaconda.anaconda_loggers import get_module_logger
-log = get_module_logger(__name__)
-
-from configparser import ConfigParser
-from threading import RLock
-
-from pyanaconda import startup_utils
-from pyanaconda.core import util
-from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda.modules.common.constants.services import SERVICES
-
-
-class ScreenAccessManager(object):
- """A singleton that takes care about spoke visits and option changes.
-
- The visits and option changes are reflected in the user interaction config file.
- For specification of the user interaction config file see the
- user-interaction-config-file-spec.rst file in the docs directory of the
- Anaconda source code checkout.
- """
-
- def __init__(self):
- self._lock = RLock()
- self._config = ConfigParser(delimiters=("="), comment_prefixes=("#"))
-
- def open_config_file(self, config_path=None):
- """Try to open an existing config file."""
- with self._lock:
- # Don't load the user interaction config from
- # default path if no path is specified in image or
- # directory installation modes.
- # The config would be taken from the host system,
- # which is certainly not what we would want to happen.
- # But load the config if a path is specified,
- # so that it is possible to hide spokes in
- # image and directory installation modes.
- if config_path is None and conf.system.provides_user_interaction_config:
- config_path = CONFIG_FILE_PATH
- if config_path and os.path.exists(config_path):
- log.info("parsing existing user interaction config file in %s", config_path)
- with open(config_path, "rt") as f:
- self._config.read_file(f)
-
- def write_out_config_file(self, config_path=None):
- """Write the user interaction config file to persistent storage.
- - we always read the config file from the top level filesystem, as:
- -> on live media the file will be there
- -> on non-live media the config file (if any) will be injected to the top level
- -> filesystem by image generation tools or by an updates/product image
-
- - on the other hand the "output" config file needs to always end on the installed
- system, so that post installation setup tools (such as Initial Setup or
- Gnome Initial Setup) can use it
- -> therefore we always write the config file to the sysroot path
- """
-
- if config_path is None:
- config_path = util.getSysroot() + CONFIG_FILE_PATH
-
- with self._lock:
- new_config_file = not os.path.exists(config_path)
- try:
- with open(config_path, "wt") as f:
- if new_config_file:
- # we are creating a new file, so add a header that it was created by Anaconda,
- # including its version number
- f.write(self._get_new_config_header())
- self._config.write(f)
- except OSError:
- log.exception("Can't write user interaction config file.")
-
- def _get_new_config_header(self):
- """Generate a header for use when Anaconda generates the user interaction config file.
-
- :returns: an appropriate user config file header
- :rtype: str
- """
-
- return "# This file has been generated by the Anaconda Installer %s\n\n" % \
- startup_utils.get_anaconda_version_string()
-
- def _parse_bool_option(self, section_name, option_name):
- """Read a boolean option from the user config file.
-
- Make sure to convert the value used for boolean values (1 & 0) to Python booleans (True & False)
- and throw SyntaxError if an invalid value is specified and return None if the option has not been found.
-
- :param str section_name: section where to look for the option
- :param str option_name: name of the option to check
- :returns: True/False for 1/0 and None id the option has not been found in the section
- :rtype: bool or None
- """
-
- option_value = self._config.get(section_name, option_name, fallback=None)
- if option_value is None:
- return option_value
- # the option has some content
- option_boolean = CONFIG_BOOLEAN_TABLE.get(option_value)
- if option_boolean is None:
- # parsing failed for the option
- raise SyntaxError
- else:
- return option_boolean
-
- @property
- def post_install_tools_disabled(self):
- """Report if post install tools should be marked as disabled in the user interaction config file."""
-
- with self._lock:
- if self._config.has_section(CONFIG_GENERAL_SECTION):
- try:
- # convert to bool in case the option is not set (the call returns None)
- return bool(self._parse_bool_option(CONFIG_GENERAL_SECTION, CONFIG_DISABLE_POSTINST_TOOLS_KEY))
- except SyntaxError:
- log.error("Can't check if post install tools have been disabled due to config file syntax error.")
- else:
- return False
-
- @post_install_tools_disabled.setter
- def post_install_tools_disabled(self, tools_disabled):
- """Controls if post install tools should be marked as disabled in the user interaction config file."""
-
- with self._lock:
- if not self._config.has_section(CONFIG_GENERAL_SECTION):
- self._config.add_section(CONFIG_GENERAL_SECTION)
- if tools_disabled:
- self._config.set(CONFIG_GENERAL_SECTION, CONFIG_DISABLE_POSTINST_TOOLS_KEY, CONFIG_TRUE)
- else:
- self._config.set(CONFIG_GENERAL_SECTION, CONFIG_DISABLE_POSTINST_TOOLS_KEY, CONFIG_FALSE)
-
- def mark_screen_visited(self, screen_name):
- """Mark the given screen as visited
-
- :param str screen_name: screen to mark as visited
- """
-
- with self._lock:
- if not self._config.has_section(screen_name):
- self._config.add_section(screen_name)
- self._config.set(screen_name, CONFIG_VISITED_KEY, CONFIG_TRUE)
-
- def get_screen_visited(self, screen_name):
- """Report if the given screen has been marked as visited
-
- Note that this takes into account both this installation run
- and any pre-installation tools run recorded in the user interaction config file.
-
- :param str screen_name: name of a screen to check
- :returns: True if the screen has already been visited, False otherwise
- :rtype: bool
- """
-
- with self._lock:
- if self._config.has_section(screen_name):
- try:
- # convert to bool in case the option is not set (the call returns None)
- return bool(self._parse_bool_option(screen_name, CONFIG_VISITED_KEY))
- except SyntaxError:
- log.error("Can't check if %s screen has been visited due to config file syntax error.",
- screen_name)
- return False
-
- def mark_screen_option_changed(self, screen_name, option_name):
- """Mark option on a screen as changed by the user.
-
- Note that to mark an option as changed the screen needs to be marked
- as visited (section needs to exist and have the visited key set to 1).
-
- :param str screen_name: name of the screen
- :param str option_name: name of the option to mark as changed
- """
-
- with self._lock:
- if self._config.has_section(screen_name):
- if self.get_screen_visited(screen_name):
- self._config.set(screen_name, CONFIG_OPTION_PREFIX + option_name, CONFIG_TRUE)
- log.warning("Attempt to set option %s as changed for screen %s that has not been visited.",
- CONFIG_OPTION_PREFIX + option_name, screen_name)
-
- else:
- log.warning("Attempt to set option %s as changed for an unknown screen %s.",
- CONFIG_OPTION_PREFIX + option_name, screen_name)
-
- def get_screen_option_changed(self, screen_name, option_name):
- """Report if the given option on the specified screen has been changed by the user.
-
- If the screen or option is unknown or if its syntax is incorrect then the option
- is reported as not changed.
-
- :param str screen_name: name of the screen
- :param str option_name: name of the option to check
- :returns: True if the option was changed, False otherwise
- :rtype: bool
- """
-
- with self._lock:
- try:
- if self._config.has_section(screen_name):
- return bool(self._parse_bool_option(screen_name, CONFIG_OPTION_PREFIX + option_name))
- except SyntaxError:
- log.error("Can't check if options %s in section %s has been changed due to config file syntax error.",
- option_name, screen_name)
- return False
-
- def update_config_file_state(self):
- """Synchornize user interaction config file state.
-
- At the moment this means simply checking if post installation setup tools should
- be enabled/disabled based on kickstart and state of the Services module.
-
- It is assumed this method will be called at an appropriate time before the config
- file is written out to the target system.
- """
- services_proxy = SERVICES.get_proxy()
- if not services_proxy.PostInstallToolsEnabled:
- self.post_install_tools_disabled = True
-
-sam = None
-
-def initSAM():
- global sam
- sam = ScreenAccessManager()
diff --git a/pyanaconda/ui/common.py b/pyanaconda/ui/common.py
index 3194cdae4c5..c3d9158f920 100644
--- a/pyanaconda/ui/common.py
+++ b/pyanaconda/ui/common.py
@@ -19,9 +19,9 @@
from abc import ABCMeta, abstractproperty
+from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON, SETUP_ON_BOOT_RECONFIG
from pyanaconda.modules.common.constants.services import SERVICES
-from pyanaconda import screen_access
from pyanaconda.core.util import collect
from pyanaconda.core.signal import Signal
from pyanaconda import lifecycle
@@ -222,7 +222,6 @@ def __init__(self, storage, payload):
# connect default callbacks for the signals
self.entered.connect(self.entry_logger)
- self.entered.connect(self._mark_screen_visited)
self.exited.connect(self.exit_logger)
@abstractproperty
@@ -327,11 +326,6 @@ def status(self):
"""
raise NotImplementedError
-
- def _mark_screen_visited(self, spoke_instance):
- """Report the spoke screen as visited to the Spoke Access Manager."""
- screen_access.sam.mark_screen_visited(spoke_instance.__class__.__name__)
-
def entry_logger(self, spoke_instance):
"""Log immediately before this spoke is about to be displayed on the
screen. Subclasses may override this method if they want to log
@@ -624,10 +618,11 @@ def collect_spokes(mask_paths, category):
# filter out any spokes from the candidates that have already been visited by the user before
# (eq. before Anaconda or Initial Setup started) and should not be visible again
visible_spokes = []
+ hidden_spokes = conf.ui.hidden_spokes
for candidate in candidate_spokes:
- if screen_access.sam.get_screen_visited(candidate.__name__):
- log.info("Spoke %s will not be displayed because it has already been visited before.",
- candidate.__name__)
+ if candidate.__name__ in hidden_spokes:
+ log.info("Spoke %s will not be displayed because it is hidden by "
+ "the Anaconda configuration file.", candidate.__name__)
else:
visible_spokes.append(candidate)
spokes.extend(visible_spokes)
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
index a34ea90d114..184171b7b53 100644
--- a/pyanaconda/ui/gui/spokes/storage.py
+++ b/pyanaconda/ui/gui/spokes/storage.py
@@ -78,7 +78,6 @@
reset_bootloader
from pyanaconda.storage.snapshot import on_disk_storage
from pyanaconda.storage.format_dasd import DasdFormatting
-from pyanaconda.screen_access import sam
from pyanaconda.modules.common.errors.configuration import StorageConfigurationError, \
BootloaderConfigurationError
from pyanaconda.modules.common.constants.objects import DISK_SELECTION, DISK_INITIALIZATION, \
@@ -333,7 +332,7 @@ def __init__(self, *args, **kwargs):
# hide radio buttons for spokes that have been marked as visited by the
# user interaction config file
- if sam.get_screen_visited("CustomPartitioningSpoke"):
+ if "CustomPartitioningSpoke" in conf.ui.hidden_spokes:
self._custom_part_radio_button.set_visible(False)
self._custom_part_radio_button.set_no_show_all(True)
@@ -343,7 +342,7 @@ def __init__(self, *args, **kwargs):
def _enable_blivet_gui(self, supported):
if supported:
self._blivet_gui_radio_button.connect("toggled", self._method_radio_button_toggled)
- if sam.get_screen_visited("BlivetGuiSpoke"):
+ if "BlivetGuiSpoke" in conf.ui.hidden_spokes:
self._blivet_gui_radio_button.set_visible(False)
self._blivet_gui_radio_button.set_no_show_all(True)
else:
diff --git a/tests/nosetests/pyanaconda_tests/module_services_test.py b/tests/nosetests/pyanaconda_tests/module_services_test.py
index 0d7523e2482..b60eef61b34 100644
--- a/tests/nosetests/pyanaconda_tests/module_services_test.py
+++ b/tests/nosetests/pyanaconda_tests/module_services_test.py
@@ -17,12 +17,18 @@
#
# Red Hat Author(s): Vendula Poncova
#
+import os
+import tempfile
import unittest
+from textwrap import dedent
+from unittest.mock import patch
+
from mock import Mock
from pyanaconda.core.constants import GRAPHICAL_TARGET, SETUP_ON_BOOT_DISABLED, \
SETUP_ON_BOOT_RECONFIG, SETUP_ON_BOOT_ENABLED, SETUP_ON_BOOT_DEFAULT
from pyanaconda.modules.common.constants.services import SERVICES
+from pyanaconda.modules.services.installation import ConfigurePostInstallationToolsTask
from pyanaconda.modules.services.services import ServicesModule
from pyanaconda.modules.services.services_interface import ServicesInterface
from tests.nosetests.pyanaconda_tests import check_kickstart_interface
@@ -194,3 +200,75 @@ def firstboot_reconfig_kickstart_test(self):
self._test_kickstart(ks_in, ks_out)
self.assertEqual(self.services_interface.SetupOnBoot, SETUP_ON_BOOT_RECONFIG)
self.assertEqual(self.services_interface.PostInstallToolsEnabled, True)
+
+
+class ServicesTasksTestCase(unittest.TestCase):
+ """Test the services tasks."""
+
+ @patch('pyanaconda.modules.services.installation.get_anaconda_version_string')
+ def enable_post_install_tools_test(self, version_getter):
+ version_getter.return_value = "1.0"
+
+ content = dedent("""
+ # This file has been generated by the Anaconda Installer 1.0
+
+ [General]
+ post_install_tools_disabled = 0
+ """)
+
+ with tempfile.TemporaryDirectory() as sysroot:
+ os.makedirs(os.path.join(sysroot, "etc/sysconfig"))
+
+ ConfigurePostInstallationToolsTask(
+ sysroot=sysroot,
+ tools_enabled=True
+ ).run()
+
+ with open(os.path.join(sysroot, "etc/sysconfig/anaconda")) as f:
+ self.assertEqual(f.read().strip(), content.strip())
+
+ @patch('pyanaconda.modules.services.installation.get_anaconda_version_string')
+ def disable_post_install_tools_test(self, version_getter):
+ version_getter.return_value = "1.0"
+
+ content = dedent("""
+ # This file has been generated by the Anaconda Installer 1.0
+
+ [General]
+ post_install_tools_disabled = 1
+ """)
+
+ print(content)
+
+ with tempfile.TemporaryDirectory() as sysroot:
+ os.makedirs(os.path.join(sysroot, "etc/sysconfig"))
+
+ ConfigurePostInstallationToolsTask(
+ sysroot=sysroot,
+ tools_enabled=False
+ ).run()
+
+ with open(os.path.join(sysroot, "etc/sysconfig/anaconda")) as f:
+ self.assertEqual(f.read().strip(), content.strip())
+
+ @patch('pyanaconda.modules.services.installation.conf')
+ def skip_post_install_tools_test(self, conf):
+ with tempfile.TemporaryDirectory() as sysroot:
+ os.makedirs(os.path.join(sysroot, "etc/sysconfig"))
+
+ task = ConfigurePostInstallationToolsTask(
+ sysroot=sysroot,
+ tools_enabled=True
+ )
+
+ conf.target.is_directory = False
+ conf.target.is_image = True
+ task.run()
+
+ self.assertFalse(os.path.isfile(os.path.join(sysroot, "etc/sysconfig/anaconda")))
+
+ conf.target.is_directory = True
+ conf.target.is_image = False
+ task.run()
+
+ self.assertFalse(os.path.isfile(os.path.join(sysroot, "etc/sysconfig/anaconda")))
diff --git a/tests/nosetests/pyanaconda_tests/product_test.py b/tests/nosetests/pyanaconda_tests/product_test.py
index 57f1c80ae50..00743ee45d5 100644
--- a/tests/nosetests/pyanaconda_tests/product_test.py
+++ b/tests/nosetests/pyanaconda_tests/product_test.py
@@ -81,6 +81,10 @@ def fedora_products_test(self):
"Fedora", "Workstation",
["fedora.conf", "fedora-workstation.conf"]
)
+ self._check_default_product(
+ "Fedora", "Workstation Live",
+ ["fedora.conf", "fedora-workstation.conf", "fedora-workstation-live.conf"]
+ )
self._check_default_product(
"Fedora", "AtomicHost",
["fedora.conf", "fedora-atomic-host.conf"]
@@ -88,7 +92,8 @@ def fedora_products_test(self):
)
self._check_default_product(
"Fedora", "Silverblue",
- ["fedora.conf", "fedora-workstation.conf", "fedora-silverblue.conf"]
+ ["fedora.conf", "fedora-workstation.conf", "fedora-workstation-live.conf",
+ "fedora-silverblue.conf"]
)
def rhel_products_test(self):
diff --git a/tests/nosetests/pyanaconda_tests/screen_access_manager_test.py b/tests/nosetests/pyanaconda_tests/screen_access_manager_test.py
deleted file mode 100644
index e52d79e4d76..00000000000
--- a/tests/nosetests/pyanaconda_tests/screen_access_manager_test.py
+++ /dev/null
@@ -1,253 +0,0 @@
-# vim:set fileencoding=utf-8
-#
-# Copyright (C) 2016 Red Hat, Inc.
-#
-# This copyrighted material is made available to anyone wishing to use,
-# modify, copy, or redistribute it subject to the terms and conditions of
-# the GNU General Public License v.2, or (at your option) any later version.
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY expressed or implied, including the implied warranties of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-# Public License for more details. You should have received a copy of the
-# GNU General Public License along with this program; if not, write to the
-# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
-# source code or documentation are not subject to the GNU General Public
-# License and may only be used or replicated with the express permission of
-# Red Hat, Inc.
-#
-# Red Hat Author(s): Martin Kolman
-#
-
-import unittest
-import tempfile
-import shutil
-import os
-from configparser import ConfigParser
-
-from pyanaconda import screen_access
-
-class ScreenAccessManagerTest(unittest.TestCase):
- """Test the spoke access manager.
-
- Check that it can correctly handle existing user interaction config files,
- correctly handle spoke visits and option changes, state querying and
- that the config files it creates are valid.
- """
-
- def _get_config_path(self):
- test_dir = tempfile.mkdtemp()
- config_path = os.path.join(test_dir, screen_access.CONFIG_FILE_NAME)
- return test_dir, config_path
-
- def _parse_config_file(self, config_path):
- config = ConfigParser()
- with open(config_path, "rt") as f:
- config.read_file(f)
- return config
-
- def file_header_test(self):
- """Test that the Anaconda generated config file header looks fine."""
- screen_access.initSAM()
- sam = screen_access.sam
- header = sam._get_new_config_header()
- # check that the header starts with a hash
- self.assertEqual(header[0], "#")
- # check that it has a newline at the end
- self.assertEqual(header[-1], "\n")
-
- def no_interaction_test(self):
- """Test that SAM can handle no user interaction taking place."""
- screen_access.initSAM()
- sam = screen_access.sam
-
- try:
- test_dir, config_path = self._get_config_path()
- sam.write_out_config_file(os.path.join(test_dir, screen_access.CONFIG_FILE_NAME))
- self.assertTrue(os.path.exists(config_path))
- # the config should have the "created by Anaconda" header, so it should
- # be non-zero
- self.assertGreater(os.path.getsize(config_path), 0)
- finally:
- shutil.rmtree(test_dir)
-
- def spoke_visited_test(self):
- """Test that SAM correctly marks a spoke as visited in the output config."""
-
- screen_access.initSAM()
- sam = screen_access.sam
- try:
- test_dir, config_path = self._get_config_path()
- # mark a screen as visited
- sam.mark_screen_visited("FooSpoke")
- # it should be safe to mark a screen as visited multiple times
- sam.mark_screen_visited("FooSpoke")
- # write the config file
- sam.write_out_config_file(os.path.join(test_dir, screen_access.CONFIG_FILE_NAME))
- # the resulting config file needs to exist and be non-empty
- self.assertTrue(os.path.exists(config_path))
- self.assertGreater(os.path.getsize(config_path), 0)
- # parse the file and check if it contains the FooSpoke section
- # and visited key set to 0
-
- config = self._parse_config_file(config_path)
- self.assertTrue(config.has_section("FooSpoke"))
- self.assertEqual(config.get("FooSpoke", screen_access.CONFIG_VISITED_KEY), screen_access.CONFIG_TRUE)
- finally:
- shutil.rmtree(test_dir)
-
- def spoke_option_changed_test(self):
- """Test that SAM correctly marks option as changed in the output config."""
- screen_access.initSAM()
- sam = screen_access.sam
- try:
- test_dir, config_path = self._get_config_path()
- # mark a screen as visited
- sam.mark_screen_visited("FooSpoke")
- # mark some options on it as changed
- sam.mark_screen_option_changed("FooSpoke", "BarOption")
- sam.mark_screen_option_changed("FooSpoke", "BazOption")
- # it should be possible to mark an option as changed multiple
- # times so that Anaconda doesn't have to check before marking
- # an option as changed
- sam.mark_screen_option_changed("FooSpoke", "BarOption")
- sam.mark_screen_option_changed("FooSpoke", "BazOption")
- # check if SAM correctly reports the options as changed
- self.assertTrue(sam.get_screen_option_changed("FooSpoke", "BarOption"))
- self.assertTrue(sam.get_screen_option_changed("FooSpoke", "BazOption"))
-
- # try marking something as changed on a spoke
- # that has not been visited
- sam.mark_screen_option_changed("UnvisitedSpoke", "UnaccessibleOption")
- # the option should still be marked as not changed
- self.assertFalse(sam.get_screen_option_changed("UnvisitedSpoke", "UnaccessibleOption"))
-
- # now write out the config and check if it is valid
- sam.write_out_config_file(os.path.join(test_dir, screen_access.CONFIG_FILE_NAME))
-
- config = self._parse_config_file(config_path)
- self.assertTrue(config.has_section("FooSpoke"))
- self.assertEqual(config.get("FooSpoke", screen_access.CONFIG_VISITED_KEY), screen_access.CONFIG_TRUE)
- self.assertEqual(config.get("FooSpoke", screen_access.CONFIG_OPTION_PREFIX + "BarOption"), screen_access.CONFIG_TRUE)
- self.assertEqual(config.get("FooSpoke", screen_access.CONFIG_OPTION_PREFIX + "BazOption"), screen_access.CONFIG_TRUE)
- # make sure there is no "UnvisitedSpoke" in the config
- self.assertFalse(config.has_section("UnvisitedSpoke"))
- self.assertEqual(config.get("UnivisitedSpoke", screen_access.CONFIG_OPTION_PREFIX + "UnaccessibleOption", fallback=None), None)
- finally:
- shutil.rmtree(test_dir)
-
- def disale_post_install_tools_test(self):
- """Test that SAM can correctly disable post install tools via the user interaction config."""
- screen_access.initSAM()
- sam = screen_access.sam
-
- try:
- test_dir, config_path = self._get_config_path()
- # post install tools should be enabled by default
- self.assertFalse(sam.post_install_tools_disabled)
- # set post install tools to disabled
- sam.post_install_tools_disabled = True
- # check if SAM correctly reports the change
- self.assertTrue(sam.post_install_tools_disabled)
- sam.write_out_config_file(os.path.join(test_dir, screen_access.CONFIG_FILE_NAME))
- # check if the option is correctly reflected in the
- # user interaction config file
- config = self._parse_config_file(config_path)
- self.assertTrue(config.has_section(screen_access.CONFIG_GENERAL_SECTION))
- self.assertEqual(config.get(screen_access.CONFIG_GENERAL_SECTION, screen_access.CONFIG_DISABLE_POSTINST_TOOLS_KEY),
- screen_access.CONFIG_TRUE)
- finally:
- shutil.rmtree(test_dir)
-
- def existing_config_test(self):
- """Test that SAM can correctly parse an existing user interaction config."""
- screen_access.initSAM()
- sam = screen_access.sam
- try:
- test_dir, config_path = self._get_config_path()
- third_party_config = """
-# Generated by some third party tool
-
-[General]
-# disable post install tools
-post_install_tools_disabled=1
-
-[FooScreen]
-visited=1
-
-[BarScreen]
-visited=1
-# some options have been changed
-changed_some_option=1
-changed_other_option=1
-# these options use wrong boolean syntax
-# (only 1 and 0 is allowed by the spec)
-changed_bad_syntax_1=yes
-changed_bad_syntax_2=true
-changed_bad_syntax_3=True
-changed_bad_syntax_4=no
-changed_bad_syntax_5=false
-changed_bad_syntax_6=False
-changed_bad_syntax_7=2
-changed_bad_syntax_8=something
-
-# a random keys that should be ignored
-not_an_option=1
-not_changed=abc
-"""
- with open(config_path, "wt") as f:
- f.write(third_party_config)
-
- sam.open_config_file(config_path)
- # check that SAM has correctly parsed the config file
- self.assertTrue(sam.post_install_tools_disabled)
- self.assertTrue(sam.get_screen_visited("FooScreen"))
- self.assertTrue(sam.get_screen_visited("BarScreen"))
- self.assertTrue(sam.get_screen_option_changed("BarScreen", "some_option"))
- self.assertTrue(sam.get_screen_option_changed("BarScreen", "other_option"))
- # options using bad boolean syntax should be ignored
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_1"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_2"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_3"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_4"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_5"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_6"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_7"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "bad_syntax_8"))
- # those other two keys don̈́'t have the changed prefix, so should not be parsed
- # as changed options
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "not_an_option"))
- self.assertFalse(sam.get_screen_option_changed("BarScreen", "not_changed"))
-
- # now try changing more stuff and then check if it correctly propagates
- # to the resulting user interaction config file
-
- # lets say the user changed something else on the BarScreen
- sam.mark_screen_option_changed("BarScreen", "option_changed_in_anaconda")
- # and then visited the BazScreen and also changed something on it
- sam.mark_screen_visited("BazScreen")
- sam.mark_screen_option_changed("BazScreen", "baz_option_1")
- sam.mark_screen_option_changed("BazScreen", "baz_option_2")
-
- # write out the config file and check if it is valid
- sam.write_out_config_file(os.path.join(test_dir, screen_access.CONFIG_FILE_NAME))
- config = self._parse_config_file(config_path)
-
- self.assertTrue(config.has_section(screen_access.CONFIG_GENERAL_SECTION))
- self.assertTrue(config.get(screen_access.CONFIG_GENERAL_SECTION,
- screen_access.CONFIG_DISABLE_POSTINST_TOOLS_KEY),
- screen_access.CONFIG_TRUE)
-
- self.assertTrue(config.has_section("FooScreen"))
- self.assertEqual(config.get("FooScreen", screen_access.CONFIG_VISITED_KEY), screen_access.CONFIG_TRUE)
-
- self.assertTrue(config.has_section("BarScreen"))
- self.assertEqual(config.get("BarScreen", screen_access.CONFIG_OPTION_PREFIX + "some_option"), screen_access.CONFIG_TRUE)
- self.assertEqual(config.get("BarScreen", screen_access.CONFIG_OPTION_PREFIX + "other_option"), screen_access.CONFIG_TRUE)
- self.assertEqual(config.get("BarScreen", screen_access.CONFIG_OPTION_PREFIX + "option_changed_in_anaconda"), screen_access.CONFIG_TRUE)
-
- self.assertTrue(config.has_section("BazScreen"))
- self.assertEqual(config.get("BazScreen", screen_access.CONFIG_OPTION_PREFIX + "baz_option_1"), screen_access.CONFIG_TRUE)
- self.assertEqual(config.get("BazScreen", screen_access.CONFIG_OPTION_PREFIX + "baz_option_2"), screen_access.CONFIG_TRUE)
- finally:
- shutil.rmtree(test_dir)