From ffcd657a88881c088a79dbcbe26248644a5d5e61 Mon Sep 17 00:00:00 2001 From: cliffmccarthy <16453869+cliffmccarthy@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:39:55 -0500 Subject: [PATCH] refactor: Add Deployer base class - Added a Deployer class that defines the base for objects that will handle installation of individual components, with install, configure, and activate stages. Subclasses will override the implementation methods of those stages as needed, while the base class handles all the logic of deciding which stages to execute. - The CMDEPLOY_STAGES environment variable is used to determine what stages to run. If this is not defined, all stages run as usual. - Added import of Deployer to cmdeploy/__init__.py. This is not yet used, but the next series of commits will use it. - In deploy_chatmail(), define an empty list of deployers, and call the create_groups() and create_users() methods for the items in the list. This list will get filled with Deployer objects in the next series of commits. --- cmdeploy/src/cmdeploy/__init__.py | 14 ++++++ cmdeploy/src/cmdeploy/deployer.py | 80 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 cmdeploy/src/cmdeploy/deployer.py diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index 623403ab..0a482568 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -20,6 +20,7 @@ from pyinfra.facts.systemd import SystemdEnabled from pyinfra.operations import apt, files, pip, server, systemd from .acmetool import deploy_acmetool +from .deployer import Deployer from .www import build_webpages, find_merge_conflict, get_paths @@ -690,6 +691,19 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: line="nameserver 9.9.9.9", ) + all_deployers = [ + ] + + # + # Create all groups before users, because some users reference groups + # from other classes. + # + for deployer in all_deployers: + deployer.create_groups() + + for deployer in all_deployers: + deployer.create_users() + server.group(name="Create vmail group", group="vmail", system=True) server.user(name="Create vmail user", user="vmail", group="vmail", system=True) server.group(name="Create opendkim group", group="opendkim", system=True) diff --git a/cmdeploy/src/cmdeploy/deployer.py b/cmdeploy/src/cmdeploy/deployer.py new file mode 100644 index 00000000..d3ba6f59 --- /dev/null +++ b/cmdeploy/src/cmdeploy/deployer.py @@ -0,0 +1,80 @@ +import os + +from pyinfra.operations import server + + +class Deployer: + def __init__(self, **kwargs): + super().__init__(**kwargs) + + default_stages = "install,configure,activate" + stages = os.getenv("CMDEPLOY_STAGES", default_stages).split(",") + + self.run_install = "install" in stages + self.run_configure = "configure" in stages + self.run_activate = "activate" in stages + self.need_restart = False + + # + # In any override, this method should return a list of 3-element + # (user, group, secondary-group-list) tuples. If the group is None, + # no group is created corresponding to that user. If the secondary + # group list is not None, the listed groups are created as well. + # + @staticmethod + def required_users(): + return [] + + def create_groups(self): + if not self.run_install: + return + + for user, group, groups in self.required_users(): + if group is not None: + server.group( + name="Create {} group".format(group), group=group, system=True + ) + if groups is not None: + for group2 in groups: + server.group( + name="Create {} group".format(group2), group=group2, system=True + ) + + def create_users(self): + if not self.run_install: + return + + for user, group, groups in self.required_users(): + server.user( + name="Create {} user".format(user), + user=user, + group=group, + groups=groups, + system=True, + ) + + def install(self): + if self.run_install: + self.need_restart |= bool(self.install_impl()) + + # + # If a subclass overrides this with a method that returns a true + # value, self.need_restart will be set when install() is called. + # + @staticmethod + def install_impl(): + pass + + def configure(self): + if self.run_configure: + self.configure_impl() + + def configure_impl(self): + pass + + def activate(self): + if self.run_activate: + self.activate_impl() + + def activate_impl(self): + pass