PageRenderTime 67ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/salt/modules/puppet.py

https://gitlab.com/ricardo.hernandez/salt
Python | 399 lines | 362 code | 13 blank | 24 comment | 14 complexity | 2db0106cf45962fb0c01ae15e41de9a8 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Execute puppet routines
  4. '''
  5. # Import python libs
  6. from __future__ import absolute_import
  7. import logging
  8. import os
  9. import datetime
  10. # Import salt libs
  11. import salt.utils
  12. from salt.exceptions import CommandExecutionError
  13. # Import 3rd-party libs
  14. import yaml
  15. import salt.ext.six as six
  16. from salt.ext.six.moves import range
  17. log = logging.getLogger(__name__)
  18. def __virtual__():
  19. '''
  20. Only load if puppet is installed
  21. '''
  22. unavailable_exes = ', '.join(exe for exe in ('facter', 'puppet')
  23. if salt.utils.which(exe) is None)
  24. if unavailable_exes:
  25. return (False,
  26. ('The puppet execution module cannot be loaded: '
  27. '{0} unavailable.'.format(unavailable_exes)))
  28. else:
  29. return 'puppet'
  30. def _format_fact(output):
  31. try:
  32. fact, value = output.split(' => ', 1)
  33. value = value.strip()
  34. except ValueError:
  35. fact = None
  36. value = None
  37. return (fact, value)
  38. class _Puppet(object):
  39. '''
  40. Puppet helper class. Used to format command for execution.
  41. '''
  42. def __init__(self):
  43. '''
  44. Setup a puppet instance, based on the premis that default usage is to
  45. run 'puppet agent --test'. Configuration and run states are stored in
  46. the default locations.
  47. '''
  48. self.subcmd = 'agent'
  49. self.subcmd_args = [] # e.g. /a/b/manifest.pp
  50. self.kwargs = {'color': 'false'} # e.g. --tags=apache::server
  51. self.args = [] # e.g. --noop
  52. if salt.utils.is_windows():
  53. self.vardir = 'C:\\ProgramData\\PuppetLabs\\puppet\\var'
  54. self.rundir = 'C:\\ProgramData\\PuppetLabs\\puppet\\run'
  55. self.confdir = 'C:\\ProgramData\\PuppetLabs\\puppet\\etc'
  56. self.useshell = True
  57. else:
  58. self.useshell = False
  59. if 'Enterprise' in __salt__['cmd.run']('puppet --version'):
  60. self.vardir = '/var/opt/lib/pe-puppet'
  61. self.rundir = '/var/opt/run/pe-puppet'
  62. self.confdir = '/etc/puppetlabs/puppet'
  63. else:
  64. self.vardir = '/var/lib/puppet'
  65. self.rundir = '/var/run/puppet'
  66. self.confdir = '/etc/puppet'
  67. self.disabled_lockfile = self.vardir + '/state/agent_disabled.lock'
  68. self.run_lockfile = self.vardir + '/state/agent_catalog_run.lock'
  69. self.agent_pidfile = self.rundir + '/agent.pid'
  70. self.lastrunfile = self.vardir + '/state/last_run_summary.yaml'
  71. def __repr__(self):
  72. '''
  73. Format the command string to executed using cmd.run_all.
  74. '''
  75. cmd = 'puppet {subcmd} --vardir {vardir} --confdir {confdir}'.format(
  76. **self.__dict__
  77. )
  78. args = ' '.join(self.subcmd_args)
  79. args += ''.join(
  80. [' --{0}'.format(k) for k in self.args] # single spaces
  81. )
  82. args += ''.join([
  83. ' --{0} {1}'.format(k, v) for k, v in six.iteritems(self.kwargs)]
  84. )
  85. return '{0} {1}'.format(cmd, args)
  86. def arguments(self, args=None):
  87. '''
  88. Read in arguments for the current subcommand. These are added to the
  89. cmd line without '--' appended. Any others are redirected as standard
  90. options with the double hyphen prefixed.
  91. '''
  92. # permits deleting elements rather than using slices
  93. args = args and list(args) or []
  94. # match against all known/supported subcmds
  95. if self.subcmd == 'apply':
  96. # apply subcommand requires a manifest file to execute
  97. self.subcmd_args = [args[0]]
  98. del args[0]
  99. if self.subcmd == 'agent':
  100. # no arguments are required
  101. args.extend([
  102. 'test'
  103. ])
  104. # finally do this after subcmd has been matched for all remaining args
  105. self.args = args
  106. def run(*args, **kwargs):
  107. '''
  108. Execute a puppet run and return a dict with the stderr, stdout,
  109. return code, etc. The first positional argument given is checked as a
  110. subcommand. Following positional arguments should be ordered with arguments
  111. required by the subcommand first, followed by non-keyword arguments.
  112. Tags are specified by a tag keyword and comma separated list of values. --
  113. http://docs.puppetlabs.com/puppet/latest/reference/lang_tags.html
  114. CLI Examples:
  115. .. code-block:: bash
  116. salt '*' puppet.run
  117. salt '*' puppet.run tags=basefiles::edit,apache::server
  118. salt '*' puppet.run agent onetime no-daemonize no-usecacheonfailure no-splay ignorecache
  119. salt '*' puppet.run debug
  120. salt '*' puppet.run apply /a/b/manifest.pp modulepath=/a/b/modules tags=basefiles::edit,apache::server
  121. '''
  122. puppet = _Puppet()
  123. # new args tuple to filter out agent/apply for _Puppet.arguments()
  124. buildargs = ()
  125. for arg in range(len(args)):
  126. # based on puppet documentation action must come first. making the same
  127. # assertion. need to ensure the list of supported cmds here matches
  128. # those defined in _Puppet.arguments()
  129. if args[arg] in ['agent', 'apply']:
  130. puppet.subcmd = args[arg]
  131. else:
  132. buildargs += (args[arg],)
  133. # args will exist as an empty list even if none have been provided
  134. puppet.arguments(buildargs)
  135. puppet.kwargs.update(salt.utils.clean_kwargs(**kwargs))
  136. ret = __salt__['cmd.run_all'](repr(puppet), python_shell=puppet.useshell)
  137. if ret['retcode'] in [0, 2]:
  138. ret['retcode'] = 0
  139. else:
  140. ret['retcode'] = 1
  141. return ret
  142. def noop(*args, **kwargs):
  143. '''
  144. Execute a puppet noop run and return a dict with the stderr, stdout,
  145. return code, etc. Usage is the same as for puppet.run.
  146. CLI Example:
  147. .. code-block:: bash
  148. salt '*' puppet.noop
  149. salt '*' puppet.noop tags=basefiles::edit,apache::server
  150. salt '*' puppet.noop debug
  151. salt '*' puppet.noop apply /a/b/manifest.pp modulepath=/a/b/modules tags=basefiles::edit,apache::server
  152. '''
  153. args += ('noop',)
  154. return run(*args, **kwargs)
  155. def enable():
  156. '''
  157. .. versionadded:: 2014.7.0
  158. Enable the puppet agent
  159. CLI Example:
  160. .. code-block:: bash
  161. salt '*' puppet.enable
  162. '''
  163. puppet = _Puppet()
  164. if os.path.isfile(puppet.disabled_lockfile):
  165. try:
  166. os.remove(puppet.disabled_lockfile)
  167. except (IOError, OSError) as exc:
  168. msg = 'Failed to enable: {0}'.format(exc)
  169. log.error(msg)
  170. raise CommandExecutionError(msg)
  171. else:
  172. return True
  173. return False
  174. def disable(message=None):
  175. '''
  176. .. versionadded:: 2014.7.0
  177. Disable the puppet agent
  178. message
  179. .. versionadded:: 2015.5.2
  180. Disable message to send to puppet
  181. CLI Example:
  182. .. code-block:: bash
  183. salt '*' puppet.disable
  184. salt '*' puppet.disable 'Disabled, contact XYZ before enabling'
  185. '''
  186. puppet = _Puppet()
  187. if os.path.isfile(puppet.disabled_lockfile):
  188. return False
  189. else:
  190. with salt.utils.fopen(puppet.disabled_lockfile, 'w') as lockfile:
  191. try:
  192. # Puppet chokes when no valid json is found
  193. str = '{{"disabled_message":"{0}"}}'.format(message) if message is not None else '{}'
  194. lockfile.write(str)
  195. lockfile.close()
  196. return True
  197. except (IOError, OSError) as exc:
  198. msg = 'Failed to disable: {0}'.format(exc)
  199. log.error(msg)
  200. raise CommandExecutionError(msg)
  201. def status():
  202. '''
  203. .. versionadded:: 2014.7.0
  204. Display puppet agent status
  205. CLI Example:
  206. .. code-block:: bash
  207. salt '*' puppet.status
  208. '''
  209. puppet = _Puppet()
  210. if os.path.isfile(puppet.disabled_lockfile):
  211. return 'Administratively disabled'
  212. if os.path.isfile(puppet.run_lockfile):
  213. try:
  214. with salt.utils.fopen(puppet.run_lockfile, 'r') as fp_:
  215. pid = int(fp_.read())
  216. os.kill(pid, 0) # raise an OSError if process doesn't exist
  217. except (OSError, ValueError):
  218. return 'Stale lockfile'
  219. else:
  220. return 'Applying a catalog'
  221. if os.path.isfile(puppet.agent_pidfile):
  222. try:
  223. with salt.utils.fopen(puppet.agent_pidfile, 'r') as fp_:
  224. pid = int(fp_.read())
  225. os.kill(pid, 0) # raise an OSError if process doesn't exist
  226. except (OSError, ValueError):
  227. return 'Stale pidfile'
  228. else:
  229. return 'Idle daemon'
  230. return 'Stopped'
  231. def summary():
  232. '''
  233. .. versionadded:: 2014.7.0
  234. Show a summary of the last puppet agent run
  235. CLI Example:
  236. .. code-block:: bash
  237. salt '*' puppet.summary
  238. '''
  239. puppet = _Puppet()
  240. try:
  241. with salt.utils.fopen(puppet.lastrunfile, 'r') as fp_:
  242. report = yaml.safe_load(fp_.read())
  243. result = {}
  244. if 'time' in report:
  245. try:
  246. result['last_run'] = datetime.datetime.fromtimestamp(
  247. int(report['time']['last_run'])).isoformat()
  248. except (TypeError, ValueError, KeyError):
  249. result['last_run'] = 'invalid or missing timestamp'
  250. result['time'] = {}
  251. for key in ('total', 'config_retrieval'):
  252. if key in report['time']:
  253. result['time'][key] = report['time'][key]
  254. if 'resources' in report:
  255. result['resources'] = report['resources']
  256. except yaml.YAMLError as exc:
  257. raise CommandExecutionError(
  258. 'YAML error parsing puppet run summary: {0}'.format(exc)
  259. )
  260. except IOError as exc:
  261. raise CommandExecutionError(
  262. 'Unable to read puppet run summary: {0}'.format(exc)
  263. )
  264. return result
  265. def plugin_sync():
  266. '''
  267. Runs a plugin synch between the puppet master and agent
  268. CLI Example:
  269. .. code-block:: bash
  270. salt '*' puppet.plugin_sync
  271. '''
  272. ret = __salt__['cmd.run']('puppet plugin download')
  273. if not ret:
  274. return ''
  275. return ret
  276. def facts(puppet=False):
  277. '''
  278. Run facter and return the results
  279. CLI Example:
  280. .. code-block:: bash
  281. salt '*' puppet.facts
  282. '''
  283. ret = {}
  284. opt_puppet = '--puppet' if puppet else ''
  285. output = __salt__['cmd.run']('facter {0}'.format(opt_puppet))
  286. # Loop over the facter output and properly
  287. # parse it into a nice dictionary for using
  288. # elsewhere
  289. for line in output.splitlines():
  290. if not line:
  291. continue
  292. fact, value = _format_fact(line)
  293. if not fact:
  294. continue
  295. ret[fact] = value
  296. return ret
  297. def fact(name, puppet=False):
  298. '''
  299. Run facter for a specific fact
  300. CLI Example:
  301. .. code-block:: bash
  302. salt '*' puppet.fact kernel
  303. '''
  304. opt_puppet = '--puppet' if puppet else ''
  305. ret = __salt__['cmd.run'](
  306. 'facter {0} {1}'.format(opt_puppet, name),
  307. python_shell=False)
  308. if not ret:
  309. return ''
  310. return ret