Source code for watchmaker.workers.salt

# -*- coding: utf-8 -*-
"""Watchmaker salt worker."""
from __future__ import (
    absolute_import,
    division,
    print_function,
    unicode_literals,
    with_statement,
)

import ast
import codecs
import glob
import json
import os
import re
import shutil

import distro
import importlib_resources as resources
import yaml

import watchmaker.utils
from watchmaker import static
from watchmaker.exceptions import (
    InvalidComputerNameError,
    InvalidValueError,
    OuPathRequiredError,
    WatchmakerError,
)
from watchmaker.managers.platform_manager import (
    LinuxPlatformManager,
    PlatformManagerBase,
    WindowsPlatformManager,
)
from watchmaker.workers.base import WorkerBase


[docs] class SaltBase(WorkerBase, PlatformManagerBase): r""" Cross-platform worker for running salt. Args: salt_debug_log: (:obj:`list`) Filesystem path to a file where the salt debug output should be saved. When unset, the salt debug log is saved to the Watchmaker log directory. (*Default*: ``''``) salt_content: (:obj:`str`) URL to a salt content archive (zip file) that will be uncompressed in the watchmaker salt "srv" directory. This typically is used to create a top.sls file and to populate salt's file_roots. (*Default*: ``''``) - *Linux*: ``/srv/watchmaker/salt`` - *Windows*: ``C:\Watchmaker\Salt\srv`` salt_content_path: (:obj:`str`) Used in conjunction with the "salt_content" arg. Glob pattern for the location of salt content files inside the provided salt_content archive. To be used when salt content files are located within a sub-path of the archive, rather than at its top-level. Multiple paths matching the given pattern will result in error. E.g. ``salt_content_path='*/'`` (*Default*: ``''``) salt_states: (:obj:`str`) Comma-separated string of salt states to execute. When "highstate" is included with additional states, "highstate" runs first, then the other states. Accepts two special keywords (case-insensitive): (*Default*: ``'highstate'``) - ``none``: Do not apply any salt states. - ``highstate``: Apply the salt "highstate". exclude_states: (:obj:`str`) Comma-separated string of states to exclude from execution. (*Default*: ``''``) user_formulas: (:obj:`dict`) Map of formula names and URLs to zip archives of salt formulas. These formulas will be downloaded, extracted, and added to the salt file roots. The zip archive must contain a top-level directory that, itself, contains the actual salt formula. To "overwrite" bundled submodule formulas, make sure the formula name matches the submodule name. (*Default*: ``{}``) admin_groups: (:obj:`str`) Sets a salt grain that specifies the domain groups that should have root privileges on Linux or admin privileges on Windows. Value must be a colon-separated string. E.g. ``"group1:group2"`` (*Default*: ``''``) admin_users: (:obj:`str`) Sets a salt grain that specifies the domain users that should have root privileges on Linux or admin privileges on Windows. Value must be a colon-separated string. E.g. ``"user1:user2"`` (*Default*: ``''``) environment: (:obj:`str`) Sets a salt grain that specifies the environment in which the system is being built. E.g. ``dev``, ``test``, ``prod``, etc. (*Default*: ``''``) ou_path: (:obj:`str`) Sets a salt grain that specifies the full DN of the OU where the computer account will be created when joining a domain. E.g. ``"OU=SuperCoolApp,DC=example,DC=com"`` (*Default*: ``''``) pip_install: (:obj:`list`) Python packages to be installed prior to applying the high state. (*Default*: ``[]``) pip_args: (:obj:`list`) Options to pass to pip when installing packages. (*Default*: ``[]``) pip_index: (:obj:`str`) URL used for an index by pip. (*Default*: ``https://pypi.org/simple``) """ def __init__(self, *args, **kwargs): # Init inherited classes super(SaltBase, self).__init__(*args, **kwargs) # Pop arguments used by SaltBase self.user_formulas = kwargs.pop("user_formulas", None) or {} self.computer_name = kwargs.pop("computer_name", None) or "" self.computer_name_pattern = kwargs.pop("computer_name_pattern", None) or "" self.ent_env = kwargs.pop("environment", None) or "" self.valid_envs = kwargs.pop("valid_environments", []) or [] self.salt_debug_log = kwargs.pop("salt_debug_log", None) or "" self.salt_content = kwargs.pop("salt_content", None) or "" self.salt_content_path = kwargs.pop("salt_content_path", None) or "" self.ou_path_required = kwargs.pop("ou_path_required", None) or False self.ou_path = kwargs.pop("ou_path", None) or "" self.admin_groups = kwargs.pop("admin_groups", None) or "" self.admin_users = kwargs.pop("admin_users", None) or "" self.pip_install = kwargs.pop("pip_install", None) or [] self.pip_args = kwargs.pop("pip_args", None) or [] self.pip_index = kwargs.pop("pip_index", None) or "https://pypi.org/simple" self.salt_version = kwargs.pop("salt_version", None) or "" self.salt_states = kwargs.pop("salt_states", "highstate") or "" self.exclude_states = kwargs.pop("exclude_states", None) or "" self.computer_name = watchmaker.utils.config_none_deprecate( self.computer_name, self.log ) self.computer_name_pattern = watchmaker.utils.config_none_deprecate( self.computer_name_pattern, self.log ) self.salt_debug_log = watchmaker.utils.config_none_deprecate( self.salt_debug_log, self.log ) self.salt_content = watchmaker.utils.config_none_deprecate( self.salt_content, self.log ) self.salt_content_path = watchmaker.utils.config_none_deprecate( self.salt_content_path, self.log ) self.ou_path = watchmaker.utils.config_none_deprecate(self.ou_path, self.log) self.admin_groups = watchmaker.utils.config_none_deprecate( self.admin_groups, self.log ) self.admin_users = watchmaker.utils.config_none_deprecate( self.admin_users, self.log ) self.salt_states = watchmaker.utils.config_none_deprecate( self.salt_states, self.log ) # Init attributes used by SaltBase, overridden by inheriting classes self.salt_working_dir = None self.salt_working_dir_prefix = None self.salt_log_dir = None self.salt_conf_path = None self.salt_conf = None self.salt_call = None self.salt_base_env = None self.salt_formula_root = None self.salt_file_roots = None self.salt_state_args = None self.salt_debug_logfile = None
[docs] def before_install(self): """Validate configuration before starting install.""" # Convert environment to lowercase env = str(self.ent_env).lower() # Convert all valid environment to lowercase valid_envs = [str(x).lower() for x in self.valid_envs] if valid_envs and env not in valid_envs: msg = ( "Selected environment ({0}) is not one of the valid" " environment types: {1}".format(env, valid_envs) ) self.log.critical(msg) raise InvalidValueError(msg) if self.ou_path_required and not self.ou_path: raise OuPathRequiredError( "The 'ou_path' option is required, but not provided." ) if self.computer_name and self.computer_name_pattern: if not re.match(self.computer_name_pattern + r"\Z", self.computer_name): raise InvalidComputerNameError( "Computer name: %s does not match pattern %s" % (self.computer_name, self.computer_name_pattern) )
[docs] def install(self): """Install Salt.""" pass
@staticmethod def _get_salt_dirs(srv): salt_base_env = os.sep.join((srv, "states")) salt_formula_root = os.sep.join((srv, "formulas")) salt_pillar_root = os.sep.join((srv, "pillar")) return (salt_base_env, salt_formula_root, salt_pillar_root) def _prepare_for_install(self): self.working_dir = self.create_working_dir( self.salt_working_dir, self.salt_working_dir_prefix ) if self.salt_debug_log: self.salt_debug_logfile = self.salt_debug_log else: self.salt_debug_logfile = os.sep.join( (self.salt_log_dir, "salt_call.debug.log") ) self.salt_state_args = [ "--log-file", self.salt_debug_logfile, "--log-file-level", "debug", "--log-level", "error", "--out", "quiet", "--return", "local", ] for salt_dir in [self.salt_formula_root, self.salt_conf_path]: try: os.makedirs(salt_dir) except OSError: if not os.path.isdir(salt_dir): msg = "Unable to create directory - {0}".format(salt_dir) self.log.error(msg) raise SystemError(msg) with codecs.open( os.path.join(self.salt_conf_path, "minion"), "w", encoding="utf-8" ) as fh_: yaml.safe_dump(self.salt_conf, fh_, default_flow_style=False) def _check_salt_version(self): current_salt_version = self.call_process([self.salt_call, "--version"]) if ( self.salt_version and self.salt_version in current_salt_version["stdout"].decode() ): self.log.info("Salt version %s is already installed.", self.salt_version) return True self.log.info("Salt version was not specified or does not match.") return False def _get_formulas_conf(self): # Append Salt formulas bundled with Watchmaker package. with resources.as_file( resources.files(static) / "salt" / "formulas" ) as formulas_handle: formulas_path = str(formulas_handle) for formula in os.listdir(formulas_path): formula_path = os.sep.join((self.salt_formula_root, formula)) watchmaker.utils.copytree( os.sep.join((formulas_path, formula)), formula_path, force=True ) # Obtain & extract any Salt formulas specified in user_formulas. for formula_name, formula_url in self.user_formulas.items(): filename = os.path.basename(formula_url) file_loc = os.sep.join((self.working_dir, filename)) # Download the formula self.retrieve_file(formula_url, file_loc) # Extract the formula formula_working_dir = self.create_working_dir( self.working_dir, "{0}-".format(filename) ) self.extract_contents(filepath=file_loc, to_directory=formula_working_dir) # Get the first directory within the extracted directory formula_inner_dir = os.path.join( formula_working_dir, next(os.walk(formula_working_dir))[1][0] ) # Move the formula to the formula root formula_loc = os.sep.join((self.salt_formula_root, formula_name)) self.log.debug( "Placing user formula in salt file roots. formula_url=%s, " "formula_loc=%s", formula_url, formula_loc, ) if os.path.exists(formula_loc): shutil.rmtree(formula_loc) shutil.move(formula_inner_dir, formula_loc) return [ os.path.join(self.salt_formula_root, x) for x in next(os.walk(self.salt_formula_root))[1] ] def _build_salt_formula(self, extract_dir): if self.salt_content: salt_content_filename = watchmaker.utils.basename_from_uri( self.salt_content ) salt_content_file = os.sep.join((self.working_dir, salt_content_filename)) self.retrieve_file(self.salt_content, salt_content_file) if not self.salt_content_path: self.extract_contents( filepath=salt_content_file, to_directory=extract_dir ) else: self.log.debug("Using salt content path: %s", self.salt_content_path) temp_extract_dir = os.sep.join((self.working_dir, "salt-archive")) self.extract_contents( filepath=salt_content_file, to_directory=temp_extract_dir ) salt_content_src = os.sep.join( (temp_extract_dir, self.salt_content_path) ) salt_content_glob = glob.glob(salt_content_src) self.log.debug("salt_content_glob: %s", salt_content_glob) if len(salt_content_glob) > 1: msg = "Found multiple paths matching" " '{0}' in {1}".format( self.salt_content_path, self.salt_content ) self.log.critical(msg) raise WatchmakerError(msg) try: salt_files_dir = salt_content_glob[0] except IndexError: msg = "Salt content glob path '{0}' not" " found in {1}".format( self.salt_content_path, self.salt_content ) self.log.critical(msg) raise WatchmakerError(msg) watchmaker.utils.copy_subdirectories( salt_files_dir, extract_dir, self.log ) with resources.as_file( resources.files(static) / "salt" / "content" ) as bundled_content: watchmaker.utils.copy_subdirectories(bundled_content, extract_dir, self.log) with codecs.open( os.path.join(self.salt_conf_path, "minion"), "r+", encoding="utf-8" ) as fh_: salt_conf = yaml.safe_load(fh_) salt_conf.update(self.salt_file_roots) fh_.seek(0) yaml.safe_dump(salt_conf, fh_, default_flow_style=False) def _set_grain(self, grain, value): cmd = ["grains.setval", grain, str(json.dumps(value))] self.run_salt(cmd) def _get_grain(self, grain): grain_full = self.run_salt(["grains.get", grain]) return grain_full["stdout"].decode().split("\n")[1].strip() def _get_failed_states(self, state_ret): failed_states = {} try: # parse state return salt_id_delim = "_|-" salt_id_pos = 1 for state, data in state_ret["return"].items(): if data["result"] is False: state_id = state.split(salt_id_delim)[salt_id_pos] failed_states[state_id] = data except AttributeError: # some error other than a failed state, msg is in the 'return' key self.log.debug("Salt return (AttributeError): %s", state_ret) failed_states = state_ret["return"] except KeyError: # not sure what failed, just return everything self.log.debug("Salt return (KeyError): %s", state_ret) failed_states = state_ret return failed_states def _install_pip_packages(self): self.log.info("Pip installing module(s): `%s`", " ".join(self.pip_install)) pip_cmd = [ "pip.install", ] pip_cmd.extend([",".join(self.pip_install)]) pip_cmd.extend(["index_url={0}".format(self.pip_index)]) pip_cmd.extend(self.pip_args) self.run_salt(pip_cmd)
[docs] def run_salt(self, command, **kwargs): """ Execute salt command. Args: command: (:obj:`str` or :obj:`list`) Salt options and a salt module to be executed by salt-call. Watchmaker will always begin the command with the options ``--local``, ``--retcode-passthrough``, and ``--no-color``, so do not specify those options in the command. """ cmd = [ self.salt_call, "--local", "--retcode-passthrough", "--no-color", "--config-dir", self.salt_conf_path, ] if isinstance(command, list): cmd.extend(command) else: cmd.append(command) return self.call_process(cmd, **kwargs)
[docs] def service_status(self, service): """ Get the service status using salt. Args: service: (obj:`str`) Name of the service to query. Returns: :obj:`tuple`: ``('running', 'enabled')`` First element is the service running status. Second element is the service enabled status. Each element is a :obj:`bool` representing whether the service is running or enabled. """ cmd_status = ["service.status", service, "--out", "newline_values_only"] cmd_enabled = ["service.enabled", service, "--out", "newline_values_only"] return ( self.run_salt(cmd_status)["stdout"].strip().lower() == b"true", self.run_salt(cmd_enabled)["stdout"].strip().lower() == b"true", )
[docs] def service_stop(self, service): """ Stop a service status using salt. Args: service: (:obj:`str`) Name of the service to stop. Returns: :obj:`bool`: ``True`` if the service was stopped. ``False`` if the service could not be stopped. """ cmd = ["service.stop", service, "--out", "newline_values_only"] return self.run_salt(cmd)["stdout"].strip().lower() == b"true"
[docs] def service_start(self, service): """ Start a service status using salt. Args: service: (:obj:`str`) Name of the service to start. Returns: :obj:`bool`: ``True`` if the service was started. ``False`` if the service could not be started. """ cmd = ["service.start", service, "--out", "newline_values_only"] return self.run_salt(cmd)["stdout"].strip().lower() == b"true"
[docs] def service_disable(self, service): """ Disable a service using salt. Args: service: (:obj:`str`) Name of the service to disable. Returns: :obj:`bool`: ``True`` if the service was disabled. ``False`` if the service could not be disabled. """ cmd = ["service.disable", service, "--out", "newline_values_only"] return self.run_salt(cmd)["stdout"].strip().lower() == b"true"
[docs] def service_enable(self, service): """ Enable a service using salt. Args: service: (:obj:`str`) Name of the service to enable. Returns: :obj:`bool`: ``True`` if the service was enabled. ``False`` if the service could not be enabled. """ cmd = ["service.enable", service, "--out", "newline_values_only"] return self.run_salt(cmd)["stdout"].strip().lower() == b"true"
[docs] def process_grains(self): """Set salt grains.""" ent_env = {"enterprise_environment": str(self.ent_env)} self._set_grain("systemprep", ent_env) self._set_grain("watchmaker", ent_env) grain = {} if self.ou_path: grain["oupath"] = self.ou_path if self.admin_groups: grain["admin_groups"] = self.admin_groups.split(":") if self.admin_users: grain["admin_users"] = self.admin_users.split(":") if grain: self._set_grain("join-domain", grain) grain = {} if self.computer_name: grain["computername"] = self.computer_name if self.computer_name_pattern: grain["pattern"] = self.computer_name_pattern if grain: self._set_grain("name-computer", grain) self.log.info("Syncing custom salt modules...") self.run_salt("saltutil.sync_all")
[docs] def process_states(self, states, exclude): """ Apply salt states but exclude certain states. Args: states: (:obj:`str`) Comma-separated string of salt states to execute. When "highstate" is included with additional states, "highstate" runs first, then the other states. Accepts two special keywords (case-insensitive): - ``none``: Do not apply any salt states. - ``highstate``: Apply the salt "highstate". exclude: (:obj:`str`) Comma-separated string of states to exclude from execution. """ if not states: self.log.info("No States were specified. Will not apply any salt states.") return cmds = [] states = states.split(",") salt_cmd = self.salt_state_args highstate = False for state in states: if state.lower() == "highstate": highstate = True states.remove(state) if highstate: self.log.info('Applying the salt "highstate"') cmds.append(salt_cmd + ["state.highstate"]) if states: self.log.info("Applying the user-defined list of states, states=%s", states) states = ",".join(states) cmds.append(salt_cmd + ["state.sls", states]) for cmd in cmds: if exclude: cmd.extend(["exclude={0}".format(exclude)]) ret = self.run_salt(cmd, log_pipe="stderr", raise_error=False) if ret["retcode"] != 0: failed_states = self._get_failed_states( ast.literal_eval(ret["stdout"].decode("utf-8")) ) if failed_states: raise WatchmakerError( yaml.safe_dump( {"Salt state execution failed": failed_states}, default_flow_style=False, indent=4, ) ) self.log.info("Salt states all applied successfully!")
[docs] class SaltLinux(SaltBase, LinuxPlatformManager): """ Run salt on Linux. Args: install_method: (:obj:`str`) **Required**. Method to use to install salt. (*Default*: ``yum``) - ``yum``: Install salt from an RPM using yum. - ``git``: Install salt from source, using the salt bootstrap. bootstrap_source: (:obj:`str`) URL to the salt bootstrap script. Required if ``install_method`` is ``git``. (*Default*: ``''``) git_repo: (:obj:`str`) URL to the salt git repo. Required if ``install_method`` is ``git``. (*Default*: ``''``) salt_version: (:obj:`str`) A git reference present in ``git_repo``, such as a commit or a tag. If not specified, the HEAD of the default branch is used. (*Default*: ``''``) """ def __init__(self, *args, **kwargs): # Init inherited classes super(SaltLinux, self).__init__(*args, **kwargs) # Pop arguments used by SaltLinux self.install_method = kwargs.pop("install_method", None) or "yum" self.bootstrap_source = kwargs.pop("bootstrap_source", None) or "" self.git_repo = kwargs.pop("git_repo", None) or "" # Enforce lowercase and replace spaces with ^ in Linux if self.admin_groups: self.admin_groups = self.admin_groups.lower().replace(" ", "^") # Extra variables needed for SaltLinux. self.yum_pkgs = [ "selinux-policy-targeted", "salt-minion", ] # Add distro-specific policycoreutils RPM to package-list self.yum_pkgs.append(self._policy_rpm(distro.version()[0])) # Set up variables for paths to Salt directories and applications. self.salt_call = "/usr/bin/salt-call" self.salt_conf_path = "/opt/watchmaker/salt" self.salt_srv = "/srv/watchmaker/salt" self.salt_log_dir = self.system_params["logdir"] self.salt_working_dir = self.system_params["workingdir"] self.salt_working_dir_prefix = "salt-" salt_dirs = self._get_salt_dirs(self.salt_srv) self.salt_base_env = salt_dirs[0] self.salt_formula_root = salt_dirs[1] self.salt_pillar_root = salt_dirs[2] # Set up the salt config self.salt_conf = { "file_client": "local", "hash_type": "sha512", "pillar_roots": {"base": [str(self.salt_pillar_root)]}, "pillar_merge_lists": True, "conf_dir": self.salt_conf_path, "log_granular_levels": { "salt.template": "info", "salt.loaded.int.render.yaml": "info", }, } def _configuration_validation(self): if self.install_method.lower() == "git": if not self.bootstrap_source: self.log.error( "Detected `git` as the install method, but the required " "parameter `bootstrap_source` was not provided." ) if not self.git_repo: self.log.error( "Detected `git` as the install method, but the required " "parameter `git_repo` was not provided." ) def _install_package(self): if os.path.exists(self.salt_call) and self._check_salt_version(): return self.log.info("Starting Salt installation") if self.install_method.lower() == "yum": self._install_from_yum(self.yum_pkgs) elif self.install_method.lower() == "git": salt_bootstrap_filename = os.sep.join( ( self.working_dir, watchmaker.utils.basename_from_uri(self.bootstrap_source), ) ) self.retrieve_file(self.bootstrap_source, salt_bootstrap_filename) bootstrap_cmd = ["sh", salt_bootstrap_filename, "-g", self.git_repo] if self.salt_version: bootstrap_cmd.append("git") bootstrap_cmd.append(self.salt_version) else: self.log.debug("No salt version defined in config.") self.call_process(bootstrap_cmd) def _build_salt_formula(self, extract_dir): formulas_conf = self._get_formulas_conf() file_roots = [str(self.salt_base_env)] file_roots += [str(x) for x in formulas_conf] self.salt_file_roots = {"file_roots": {"base": file_roots}} super(SaltLinux, self)._build_salt_formula(extract_dir) def _set_grain(self, grain, value): self.log.info("Setting grain `%s` ...", grain) super(SaltLinux, self)._set_grain(grain, value) def _selinux_status(self): selinux_getenforce = self.call_process(["getenforce"]) return selinux_getenforce["stdout"].strip().lower() == b"enforcing" def _selinux_setenforce(self, state): return self.call_process(["setenforce", state])
[docs] def install(self): """Install salt and execute salt states.""" self._configuration_validation() self._prepare_for_install() salt_running = False salt_enabled = False salt_svc = "salt-minion" if os.path.exists(self.salt_call): salt_running, salt_enabled = self.service_status(salt_svc) self._install_package() if self.pip_install: self._install_pip_packages() salt_stopped = self.service_stop(salt_svc) self._build_salt_formula(self.salt_srv) if salt_enabled: if not self.service_enable(salt_svc): self.log.error("Failed to enable %s service", salt_svc) else: if not self.service_disable(salt_svc): self.log.error("Failed to disable %s service", salt_svc) if salt_running and salt_stopped: if not self.service_start(salt_svc): self.log.error("Failed to restart %s service", salt_svc) self.process_grains() selinux_enforcing = self._selinux_status() if selinux_enforcing: self.log.info("Making selinux permisive for salt execution") self._selinux_setenforce("permissive") try: self.process_states(self.salt_states, self.exclude_states) finally: if selinux_enforcing: self.log.info("Setting selinux back to enforcing mode") self._selinux_setenforce("enforcing") if self.working_dir: self.cleanup()
def _policy_rpm(self, arg): """Return name of distro version-appropriate policycoreutils RPM.""" if arg < "8": result = "policycoreutils-python" else: result = "policycoreutils-python-utils" return result
[docs] class SaltWindows(SaltBase, WindowsPlatformManager): """ Run salt on Windows. Args: installer_url: (:obj:`str`) **Required**. URL to the salt installer for Windows. (*Default*: ``''``) ash_role: (:obj:`str`) Sets a salt grain that specifies the role used by the ash-windows salt formula. E.g. ``"MemberServer"``, ``"DomainController"``, or ``"Workstation"`` (*Default*: ``''``) """ def __init__(self, *args, **kwargs): # Pop arguments used by SaltWindows self.installer_url = kwargs.pop("installer_url", None) or "" self.ash_role = kwargs.pop("ash_role", None) or "" # Init inherited classes super(SaltWindows, self).__init__(*args, **kwargs) self.ash_role = watchmaker.utils.config_none_deprecate(self.ash_role, self.log) # Set up variables for paths to Salt directories and applications. self.salt_call = SaltWindows._get_salt_call() self.salt_wam_root = os.sep.join((self.system_params["prepdir"], "Salt")) self.salt_conf_path = os.sep.join((self.salt_wam_root, "conf")) self.salt_srv = os.sep.join((self.salt_wam_root, "srv")) self.salt_win_repo = os.sep.join((self.salt_srv, "winrepo")) self.salt_log_dir = self.system_params["logdir"] self.salt_working_dir = self.system_params["workingdir"] self.salt_working_dir_prefix = "Salt-" salt_dirs = self._get_salt_dirs(self.salt_srv) self.salt_base_env = salt_dirs[0] self.salt_formula_root = salt_dirs[1] self.salt_pillar_root = salt_dirs[2] # Set up the salt config self.salt_conf = { "file_client": "local", "hash_type": "sha512", "pillar_roots": {"base": [str(self.salt_pillar_root)]}, "pillar_merge_lists": True, "conf_dir": self.salt_conf_path, "log_granular_levels": { "salt.template": "info", "salt.loaded.int.render.yaml": "info", }, "winrepo_source_dir": "salt://winrepo", "winrepo_dir": os.sep.join((self.salt_win_repo, "winrepo")), } def _install_package(self): if os.path.exists(self.salt_call) and self._check_salt_version(): return installer_name = os.sep.join( (self.working_dir, watchmaker.utils.basename_from_uri(self.installer_url)) ) self.retrieve_file(self.installer_url, installer_name) install_cmd = [installer_name, "/S"] self.call_process(install_cmd) def _prepare_for_install(self): if not self.installer_url: self.log.error( "Parameter `installer_url` was not provided and is" " needed for installation of Salt in Windows." ) super(SaltWindows, self)._prepare_for_install() def _build_salt_formula(self, extract_dir): formulas_conf = self._get_formulas_conf() file_roots = [str(self.salt_base_env), str(self.salt_win_repo)] file_roots += [str(x) for x in formulas_conf] self.salt_file_roots = {"file_roots": {"base": file_roots}} super(SaltWindows, self)._build_salt_formula(extract_dir) def _set_grain(self, grain, value): self.log.info("Setting grain `%s` ...", grain) super(SaltWindows, self)._set_grain(grain, value) @staticmethod def _get_salt_call(): """Retrieve installation path for Salt if it exists.""" system_drive = os.environ["systemdrive"] program_files = os.environ["ProgramFiles"] old_salt_path = os.sep.join((system_drive, "Salt", "salt-call.bat")) new_salt_paths = [ os.sep.join((program_files, "Salt Project", "Salt", "salt-call.exe")), os.sep.join((program_files, "Salt Project", "Salt", "salt-call.bat")), ] for salt_path in new_salt_paths: if os.path.isfile(salt_path): return salt_path return old_salt_path
[docs] def install(self): """Install salt and execute salt states.""" self._prepare_for_install() salt_running = False salt_enabled = False salt_svc = "salt-minion" if os.path.exists(self.salt_call): salt_running, salt_enabled = self.service_status(salt_svc) self._install_package() if self.pip_install: self._install_pip_packages() self.salt_call = SaltWindows._get_salt_call() salt_stopped = self.service_stop(salt_svc) self._build_salt_formula(self.salt_srv) if salt_enabled: if not self.service_enable(salt_svc): self.log.error("Failed to enable %s service", salt_svc) else: if not self.service_disable(salt_svc): self.log.error("Failed to disable %s service", salt_svc) if salt_running and salt_stopped: if not self.service_start(salt_svc): self.log.error("Failed to restart %s service", salt_svc) if self.ash_role: role = {"lookup": {"role": str(self.ash_role)}} self._set_grain("ash-windows", role) self.process_grains() self.log.info("Refreshing package database...") self.run_salt("pkg.refresh_db") self.process_states(self.salt_states, self.exclude_states) if self.working_dir: self.cleanup()