mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
401 lines
11 KiB
Python
401 lines
11 KiB
Python
"""
|
|
Execute puppet routines
|
|
"""
|
|
|
|
|
|
import datetime
|
|
import logging
|
|
import os
|
|
|
|
import salt.utils.args
|
|
import salt.utils.files
|
|
import salt.utils.path
|
|
import salt.utils.platform
|
|
import salt.utils.stringutils
|
|
import salt.utils.yaml
|
|
from salt.exceptions import CommandExecutionError
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def __virtual__():
|
|
"""
|
|
Only load if puppet is installed
|
|
"""
|
|
unavailable_exes = ", ".join(
|
|
exe for exe in ("facter", "puppet") if salt.utils.path.which(exe) is None
|
|
)
|
|
if unavailable_exes:
|
|
return (
|
|
False,
|
|
"The puppet execution module cannot be loaded: {} unavailable.".format(
|
|
unavailable_exes
|
|
),
|
|
)
|
|
else:
|
|
return "puppet"
|
|
|
|
|
|
def _format_fact(output):
|
|
try:
|
|
fact, value = output.split(" => ", 1)
|
|
value = value.strip()
|
|
except ValueError:
|
|
fact = None
|
|
value = None
|
|
return (fact, value)
|
|
|
|
|
|
class _Puppet:
|
|
"""
|
|
Puppet helper class. Used to format command for execution.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Setup a puppet instance, based on the premis that default usage is to
|
|
run 'puppet agent --test'. Configuration and run states are stored in
|
|
the default locations.
|
|
"""
|
|
self.subcmd = "agent"
|
|
self.subcmd_args = [] # e.g. /a/b/manifest.pp
|
|
|
|
self.kwargs = {"color": "false"} # e.g. --tags=apache::server
|
|
self.args = [] # e.g. --noop
|
|
|
|
puppet_config = __salt__["cmd.run"](
|
|
"puppet config print --render-as yaml vardir rundir confdir"
|
|
)
|
|
conf = salt.utils.yaml.safe_load(puppet_config)
|
|
self.vardir = conf["vardir"]
|
|
self.rundir = conf["rundir"]
|
|
self.confdir = conf["confdir"]
|
|
|
|
self.disabled_lockfile = self.vardir + "/state/agent_disabled.lock"
|
|
self.run_lockfile = self.vardir + "/state/agent_catalog_run.lock"
|
|
self.agent_pidfile = self.rundir + "/agent.pid"
|
|
self.lastrunfile = self.vardir + "/state/last_run_summary.yaml"
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Format the command string to executed using cmd.run_all.
|
|
"""
|
|
cmd = "puppet {subcmd} --vardir {vardir} --confdir {confdir}".format(
|
|
**self.__dict__
|
|
)
|
|
|
|
args = " ".join(self.subcmd_args)
|
|
args += "".join([" --{}".format(k) for k in self.args]) # single spaces
|
|
args += "".join([" --{} {}".format(k, v) for k, v in self.kwargs.items()])
|
|
|
|
# Ensure that the puppet call will return 0 in case of exit code 2
|
|
if salt.utils.platform.is_windows():
|
|
return "cmd /V:ON /c {} {} ^& if !ERRORLEVEL! EQU 2 (EXIT 0) ELSE (EXIT /B)".format(
|
|
cmd, args
|
|
)
|
|
return "({} {}) || test $? -eq 2".format(cmd, args)
|
|
|
|
def arguments(self, args=None):
|
|
"""
|
|
Read in arguments for the current subcommand. These are added to the
|
|
cmd line without '--' appended. Any others are redirected as standard
|
|
options with the double hyphen prefixed.
|
|
"""
|
|
# permits deleting elements rather than using slices
|
|
args = args and list(args) or []
|
|
|
|
# match against all known/supported subcmds
|
|
if self.subcmd == "apply":
|
|
# apply subcommand requires a manifest file to execute
|
|
self.subcmd_args = [args[0]]
|
|
del args[0]
|
|
|
|
if self.subcmd == "agent":
|
|
# no arguments are required
|
|
args.extend(["test"])
|
|
|
|
# finally do this after subcmd has been matched for all remaining args
|
|
self.args = args
|
|
|
|
|
|
def run(*args, **kwargs):
|
|
"""
|
|
Execute a puppet run and return a dict with the stderr, stdout,
|
|
return code, etc. The first positional argument given is checked as a
|
|
subcommand. Following positional arguments should be ordered with arguments
|
|
required by the subcommand first, followed by non-keyword arguments.
|
|
Tags are specified by a tag keyword and comma separated list of values. --
|
|
http://docs.puppetlabs.com/puppet/latest/reference/lang_tags.html
|
|
|
|
CLI Examples:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.run
|
|
salt '*' puppet.run tags=basefiles::edit,apache::server
|
|
salt '*' puppet.run agent onetime no-daemonize no-usecacheonfailure no-splay ignorecache
|
|
salt '*' puppet.run debug
|
|
salt '*' puppet.run apply /a/b/manifest.pp modulepath=/a/b/modules tags=basefiles::edit,apache::server
|
|
"""
|
|
puppet = _Puppet()
|
|
|
|
# new args tuple to filter out agent/apply for _Puppet.arguments()
|
|
buildargs = ()
|
|
for arg in args:
|
|
# based on puppet documentation action must come first. making the same
|
|
# assertion. need to ensure the list of supported cmds here matches
|
|
# those defined in _Puppet.arguments()
|
|
if arg in ["agent", "apply"]:
|
|
puppet.subcmd = arg
|
|
else:
|
|
buildargs += (arg,)
|
|
# args will exist as an empty list even if none have been provided
|
|
puppet.arguments(buildargs)
|
|
|
|
puppet.kwargs.update(salt.utils.args.clean_kwargs(**kwargs))
|
|
|
|
ret = __salt__["cmd.run_all"](repr(puppet), python_shell=True)
|
|
return ret
|
|
|
|
|
|
def noop(*args, **kwargs):
|
|
"""
|
|
Execute a puppet noop run and return a dict with the stderr, stdout,
|
|
return code, etc. Usage is the same as for puppet.run.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.noop
|
|
salt '*' puppet.noop tags=basefiles::edit,apache::server
|
|
salt '*' puppet.noop debug
|
|
salt '*' puppet.noop apply /a/b/manifest.pp modulepath=/a/b/modules tags=basefiles::edit,apache::server
|
|
"""
|
|
args += ("noop",)
|
|
return run(*args, **kwargs)
|
|
|
|
|
|
def enable():
|
|
"""
|
|
.. versionadded:: 2014.7.0
|
|
|
|
Enable the puppet agent
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.enable
|
|
"""
|
|
puppet = _Puppet()
|
|
|
|
if os.path.isfile(puppet.disabled_lockfile):
|
|
try:
|
|
os.remove(puppet.disabled_lockfile)
|
|
except OSError as exc:
|
|
msg = "Failed to enable: {}".format(exc)
|
|
log.error(msg)
|
|
raise CommandExecutionError(msg)
|
|
else:
|
|
return True
|
|
return False
|
|
|
|
|
|
def disable(message=None):
|
|
"""
|
|
.. versionadded:: 2014.7.0
|
|
|
|
Disable the puppet agent
|
|
|
|
message
|
|
.. versionadded:: 2015.5.2
|
|
|
|
Disable message to send to puppet
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.disable
|
|
salt '*' puppet.disable 'Disabled, contact XYZ before enabling'
|
|
"""
|
|
|
|
puppet = _Puppet()
|
|
|
|
if os.path.isfile(puppet.disabled_lockfile):
|
|
return False
|
|
else:
|
|
with salt.utils.files.fopen(puppet.disabled_lockfile, "w") as lockfile:
|
|
try:
|
|
# Puppet chokes when no valid json is found
|
|
msg = (
|
|
'{{"disabled_message":"{0}"}}'.format(message)
|
|
if message is not None
|
|
else "{}"
|
|
)
|
|
lockfile.write(salt.utils.stringutils.to_str(msg))
|
|
lockfile.close()
|
|
return True
|
|
except OSError as exc:
|
|
msg = "Failed to disable: {}".format(exc)
|
|
log.error(msg)
|
|
raise CommandExecutionError(msg)
|
|
|
|
|
|
def status():
|
|
"""
|
|
.. versionadded:: 2014.7.0
|
|
|
|
Display puppet agent status
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.status
|
|
"""
|
|
puppet = _Puppet()
|
|
|
|
if os.path.isfile(puppet.disabled_lockfile):
|
|
return "Administratively disabled"
|
|
|
|
if os.path.isfile(puppet.run_lockfile):
|
|
try:
|
|
with salt.utils.files.fopen(puppet.run_lockfile, "r") as fp_:
|
|
pid = int(salt.utils.stringutils.to_unicode(fp_.read()))
|
|
os.kill(pid, 0) # raise an OSError if process doesn't exist
|
|
except (OSError, ValueError):
|
|
return "Stale lockfile"
|
|
else:
|
|
return "Applying a catalog"
|
|
|
|
if os.path.isfile(puppet.agent_pidfile):
|
|
try:
|
|
with salt.utils.files.fopen(puppet.agent_pidfile, "r") as fp_:
|
|
pid = int(salt.utils.stringutils.to_unicode(fp_.read()))
|
|
os.kill(pid, 0) # raise an OSError if process doesn't exist
|
|
except (OSError, ValueError):
|
|
return "Stale pidfile"
|
|
else:
|
|
return "Idle daemon"
|
|
|
|
return "Stopped"
|
|
|
|
|
|
def summary():
|
|
"""
|
|
.. versionadded:: 2014.7.0
|
|
|
|
Show a summary of the last puppet agent run
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.summary
|
|
"""
|
|
|
|
puppet = _Puppet()
|
|
|
|
try:
|
|
with salt.utils.files.fopen(puppet.lastrunfile, "r") as fp_:
|
|
report = salt.utils.yaml.safe_load(fp_)
|
|
result = {}
|
|
|
|
if "time" in report:
|
|
try:
|
|
result["last_run"] = datetime.datetime.fromtimestamp(
|
|
int(report["time"]["last_run"])
|
|
).isoformat()
|
|
except (TypeError, ValueError, KeyError):
|
|
result["last_run"] = "invalid or missing timestamp"
|
|
|
|
result["time"] = {}
|
|
for key in ("total", "config_retrieval"):
|
|
if key in report["time"]:
|
|
result["time"][key] = report["time"][key]
|
|
|
|
if "resources" in report:
|
|
result["resources"] = report["resources"]
|
|
|
|
except salt.utils.yaml.YAMLError as exc:
|
|
raise CommandExecutionError(
|
|
"YAML error parsing puppet run summary: {}".format(exc)
|
|
)
|
|
except OSError as exc:
|
|
raise CommandExecutionError("Unable to read puppet run summary: {}".format(exc))
|
|
|
|
return result
|
|
|
|
|
|
def plugin_sync():
|
|
"""
|
|
Runs a plugin sync between the puppet master and agent
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.plugin_sync
|
|
"""
|
|
ret = __salt__["cmd.run"]("puppet plugin download")
|
|
|
|
if not ret:
|
|
return ""
|
|
return ret
|
|
|
|
|
|
def facts(puppet=False):
|
|
"""
|
|
Run facter and return the results
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.facts
|
|
"""
|
|
ret = {}
|
|
opt_puppet = "--puppet" if puppet else ""
|
|
cmd_ret = __salt__["cmd.run_all"]("facter {}".format(opt_puppet))
|
|
|
|
if cmd_ret["retcode"] != 0:
|
|
raise CommandExecutionError(cmd_ret["stderr"])
|
|
|
|
output = cmd_ret["stdout"]
|
|
|
|
# Loop over the facter output and properly
|
|
# parse it into a nice dictionary for using
|
|
# elsewhere
|
|
for line in output.splitlines():
|
|
if not line:
|
|
continue
|
|
fact, value = _format_fact(line)
|
|
if not fact:
|
|
continue
|
|
ret[fact] = value
|
|
return ret
|
|
|
|
|
|
def fact(name, puppet=False):
|
|
"""
|
|
Run facter for a specific fact
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' puppet.fact kernel
|
|
"""
|
|
opt_puppet = "--puppet" if puppet else ""
|
|
ret = __salt__["cmd.run_all"](
|
|
"facter {} {}".format(opt_puppet, name), python_shell=False
|
|
)
|
|
|
|
if ret["retcode"] != 0:
|
|
raise CommandExecutionError(ret["stderr"])
|
|
|
|
if not ret["stdout"]:
|
|
return ""
|
|
return ret["stdout"]
|