Rework CentOS configurator into two subclasses

This commit is contained in:
Will Greenberg 2022-10-07 16:25:04 -07:00
parent 6a7d8768db
commit aecb2690c8
7 changed files with 127 additions and 80 deletions

View file

@ -166,7 +166,7 @@ class ApacheConfigurator(common.Configurator):
return apache_util.find_ssl_apache_conf("old")
return apache_util.find_ssl_apache_conf("current")
def _prepare_options(self) -> None:
def _set_options(self) -> None:
"""
Set the values possibly changed by command line parameters to
OS_DEFAULTS constant dictionary
@ -181,11 +181,19 @@ class ApacheConfigurator(common.Configurator):
else:
setattr(self.options, o, getattr(self.OS_DEFAULTS, o))
# Special cases
def _override_cmds(self) -> None:
"""
Override the various command binaries w/ whatever we have set for
options.ctl
"""
self.options.version_cmd[0] = self.options.ctl
self.options.restart_cmd[0] = self.options.ctl
self.options.conftest_cmd[0] = self.options.ctl
def _prepare_options(self) -> None:
self._set_options()
self._override_cmds()
@classmethod
def add_parser_arguments(cls, add: Callable[..., None]) -> None:
# When adding, modifying or deleting command line arguments, be sure to

View file

@ -16,32 +16,35 @@ from certbot import util
OVERRIDE_CLASSES: Dict[str, Type[configurator.ApacheConfigurator]] = {
"arch": override_arch.ArchConfigurator,
"cloudlinux": override_centos.CentOSConfigurator,
"darwin": override_darwin.DarwinConfigurator,
"debian": override_debian.DebianConfigurator,
"ubuntu": override_debian.DebianConfigurator,
"centos": override_centos.CentOSConfigurator,
"centos linux": override_centos.CentOSConfigurator,
"fedora_old": override_centos.CentOSConfigurator,
"fedora": override_fedora.FedoraConfigurator,
"linuxmint": override_debian.DebianConfigurator,
"ol": override_centos.CentOSConfigurator,
"oracle": override_centos.CentOSConfigurator,
"redhatenterpriseserver": override_centos.CentOSConfigurator,
"red hat enterprise linux server": override_centos.CentOSConfigurator,
"rhel": override_centos.CentOSConfigurator,
"amazon": override_centos.CentOSConfigurator,
"gentoo": override_gentoo.GentooConfigurator,
"gentoo base system": override_gentoo.GentooConfigurator,
"opensuse": override_suse.OpenSUSEConfigurator,
"suse": override_suse.OpenSUSEConfigurator,
"sles": override_suse.OpenSUSEConfigurator,
"scientific": override_centos.CentOSConfigurator,
"scientific linux": override_centos.CentOSConfigurator,
"void": override_void.VoidConfigurator,
}
def rhel_derived_os(os_name) -> bool:
"""
Returns whether the given OS is RHEL derived, i.e. tracks RHEL's versioning
scheme, and thus should use our CentOS configurator
"""
return os_name in [
"cloudlinux",
"centos", "centos linux",
"ol", "oracle",
"rhel", "redhatenterpriseserver", "red hat enterprise linux server",
"scientific", "scientific linux",
]
def get_configurator() -> Type[configurator.ApacheConfigurator]:
""" Get correct configurator class based on the OS fingerprint """
os_name, os_version = util.get_os_info()
@ -51,7 +54,14 @@ def get_configurator() -> Type[configurator.ApacheConfigurator]:
# Special case for older Fedora versions
min_version = util.parse_loose_version('29')
if os_name == 'fedora' and util.parse_loose_version(os_version) < min_version:
os_name = 'fedora_old'
return override_centos.OldCentOSConfigurator
if rhel_derived_os(os_name):
old = util.parse_loose_version(os_version) < util.parse_loose_version('9')
if old:
return override_centos.OldCentOSConfigurator
else:
return override_centos.CentOSConfigurator
try:
override_class = OVERRIDE_CLASSES[os_name]

View file

@ -16,22 +16,9 @@ from certbot.errors import MisconfigurationError
logger = logging.getLogger(__name__)
class CentOSConfigurator(configurator.ApacheConfigurator):
class BaseCentOSConfigurator(configurator.ApacheConfigurator):
"""CentOS specific ApacheConfigurator override class"""
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
challenge_location="/etc/httpd/conf.d",
)
def config_test(self) -> None:
"""
Override config_test to mitigate configtest error in vanilla installation
@ -64,29 +51,6 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
# Finish with actual config check to see if systemctl restart helped
super().config_test()
def _prepare_options(self) -> None:
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in CentOS.
"""
super()._prepare_options()
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for CentOS.")
# Make sure new versions (>=9) of RHEL and other derived distros (that
# track RHEL's versioning scheme) use the httpd executable
os_name, os_version = util.get_os_info()
rhel_derived = os_name not in [
"fedora",
"amazon", # Amazon has a yearly release version
]
new = util.parse_loose_version(os_version) >= util.parse_loose_version('9')
if rhel_derived and new:
self.options.ctl = 'httpd'
self.options.version_cmd[0] = self.options.ctl
else:
self.options.restart_cmd_alt[0] = self.options.ctl
def get_parser(self) -> "CentOSParser":
"""Initializes the ApacheParser"""
return CentOSParser(
@ -169,6 +133,59 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
"inside of <IfModule !mod_ssl> block.\n")
class CentOSConfigurator(BaseCentOSConfigurator):
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="httpd",
version_cmd=['httpd', '-v'],
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
challenge_location="/etc/httpd/conf.d",
)
def _prepare_options(self) -> None:
"""
By only calling self._set_options() and not the parent class'
_prepare_options() method, we ensure that httpd is used for version_cmd
but not restart_cmd, unless the user has specifically overwritten those
in their config
"""
self._set_options()
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for CentOS.")
class OldCentOSConfigurator(BaseCentOSConfigurator):
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
challenge_location="/etc/httpd/conf.d",
)
def _prepare_options(self) -> None:
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in CentOS.
"""
super()._prepare_options()
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for CentOS.")
self.options.restart_cmd_alt[0] = self.options.ctl
class CentOSParser(parser.ApacheParser):
"""CentOS specific ApacheParser override class"""
def __init__(self, *args: Any, **kwargs: Any) -> None:

View file

@ -43,7 +43,7 @@ class CentOS6Tests(util.ApacheTest):
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
version=(2, 2, 15), os_info="centos")
version=(2, 2, 15), config_class=override_centos.OldCentOSConfigurator)
self.vh_truth = get_vh_truth(
self.temp_dir, "centos6_apache/apache")

View file

@ -47,12 +47,13 @@ class FedoraRestartTest(util.ApacheTest):
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="fedora_old")
os_info="fedora_old",
config_class=override_centos.OldCentOSConfigurator)
self.vh_truth = get_vh_truth(
self.temp_dir, "centos7_apache/apache")
def _run_fedora_test(self):
self.assertIsInstance(self.config, override_centos.CentOSConfigurator)
self.assertIsInstance(self.config, override_centos.OldCentOSConfigurator)
with mock.patch("certbot.util.get_os_info") as mock_info:
mock_info.return_value = ["fedora", "28"]
self.config.config_test()
@ -99,27 +100,21 @@ class UseCorrectApacheExecutableTest(util.ApacheTest):
config_root=config_root,
vhost_root=vhost_root)
@mock.patch("certbot.util.get_os_info")
def test_old_centos_rhel_and_fedora(self, mock_get_os_info):
for os_info in [("centos", "7"), ("rhel", "7"), ("fedora", "28"), ("scientific", "6")]:
mock_get_os_info.return_value = os_info
config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="centos")
self.assertEqual(config.options.ctl, "apachectl")
self.assertEqual(config.options.version_cmd, ["apachectl", "-v"])
self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"])
def test_old_centos(self):
config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
config_class=override_centos.OldCentOSConfigurator)
self.assertEqual(config.options.ctl, "apachectl")
self.assertEqual(config.options.version_cmd, ["apachectl", "-v"])
self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"])
@mock.patch("certbot.util.get_os_info")
def test_new_rhel_derived(self, mock_get_os_info):
for os_info in [("centos", "9"), ("rhel", "9"), ("oracle", "9")]:
mock_get_os_info.return_value = os_info
config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="centos")
self.assertEqual(config.options.ctl, "httpd")
self.assertEqual(config.options.version_cmd, ["httpd", "-v"])
self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"])
def test_new_rhel_derived(self):
config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
config_class=override_centos.CentOSConfigurator)
self.assertEqual(config.options.ctl, "httpd")
self.assertEqual(config.options.version_cmd, ["httpd", "-v"])
self.assertEqual(config.options.restart_cmd, ["apachectl", "graceful"])
class MultipleVhostsTestCentOS(util.ApacheTest):
@ -138,7 +133,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
mock_get_os_info.return_value = ("centos", "9")
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="centos")
config_class=override_centos.CentOSConfigurator)
self.vh_truth = get_vh_truth(
self.temp_dir, "centos7_apache/apache")

View file

@ -8,6 +8,7 @@ except ImportError: # pragma: no cover
from certbot_apache._internal import configurator
from certbot_apache._internal import entrypoint
from certbot_apache._internal import override_centos
class EntryPointTest(unittest.TestCase):
@ -28,6 +29,20 @@ class EntryPointTest(unittest.TestCase):
self.assertEqual(entrypoint.get_configurator(),
entrypoint.OVERRIDE_CLASSES[distro])
@mock.patch("certbot.util.get_os_info")
def test_old_centos_rhel_and_fedora(self, mock_get_os_info):
for os_info in [("centos", "7"), ("rhel", "7"), ("fedora", "28"), ("scientific", "6")]:
mock_get_os_info.return_value = os_info
self.assertEqual(entrypoint.get_configurator(),
override_centos.OldCentOSConfigurator)
@mock.patch("certbot.util.get_os_info")
def test_new_rhel_derived(self, mock_get_os_info):
for os_info in [("centos", "9"), ("rhel", "9"), ("oracle", "9")]:
mock_get_os_info.return_value = os_info
self.assertEqual(entrypoint.get_configurator(),
override_centos.CentOSConfigurator)
def test_nonexistent_like(self):
with mock.patch("certbot.util.get_os_info") as mock_info:
mock_info.return_value = ("nonexistent", "irrelevant")

View file

@ -82,7 +82,8 @@ def get_apache_configurator(
os_info="generic",
conf_vhost_path=None,
use_parsernode=False,
openssl_version="1.1.1a"):
openssl_version="1.1.1a",
config_class=None):
"""Create an Apache Configurator with the specified options.
:param conf: Function that returns binary paths. self.conf in Configurator
@ -110,10 +111,11 @@ def get_apache_configurator(
"update_runtime_variables"):
with mock.patch("certbot_apache._internal.apache_util.parse_from_subprocess") as mock_sp:
mock_sp.return_value = []
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
if config_class is None:
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version, use_parsernode=use_parsernode,
openssl_version=openssl_version)