PageRenderTime 65ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/collections/ansible_collections/community/general/plugins/modules/system/cronvar.py

https://gitlab.com/cfernand/acm-aap-demo
Python | 423 lines | 392 code | 13 blank | 18 comment | 11 complexity | 5fb9cfb93a87f3b5f5cde63b28522cff MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # Copyright: (c) 2017, Ansible Project
  4. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  5. # Cronvar Plugin: The goal of this plugin is to provide an idempotent
  6. # method for set cron variable values. It should play well with the
  7. # existing cron module as well as allow for manually added variables.
  8. # Each variable entered will be preceded with a comment describing the
  9. # variable so that it can be found later. This is required to be
  10. # present in order for this plugin to find/modify the variable
  11. # This module is based on the crontab module.
  12. from __future__ import absolute_import, division, print_function
  13. __metaclass__ = type
  14. DOCUMENTATION = r'''
  15. ---
  16. module: cronvar
  17. short_description: Manage variables in crontabs
  18. description:
  19. - Use this module to manage crontab variables.
  20. - This module allows you to create, update, or delete cron variable definitions.
  21. options:
  22. name:
  23. description:
  24. - Name of the crontab variable.
  25. type: str
  26. required: yes
  27. value:
  28. description:
  29. - The value to set this variable to.
  30. - Required if C(state=present).
  31. type: str
  32. insertafter:
  33. description:
  34. - If specified, the variable will be inserted after the variable specified.
  35. - Used with C(state=present).
  36. type: str
  37. insertbefore:
  38. description:
  39. - Used with C(state=present). If specified, the variable will be inserted
  40. just before the variable specified.
  41. type: str
  42. state:
  43. description:
  44. - Whether to ensure that the variable is present or absent.
  45. type: str
  46. choices: [ absent, present ]
  47. default: present
  48. user:
  49. description:
  50. - The specific user whose crontab should be modified.
  51. - This parameter defaults to C(root) when unset.
  52. type: str
  53. cron_file:
  54. description:
  55. - If specified, uses this file instead of an individual user's crontab.
  56. - Without a leading C(/), this is assumed to be in I(/etc/cron.d).
  57. - With a leading C(/), this is taken as absolute.
  58. type: str
  59. backup:
  60. description:
  61. - If set, create a backup of the crontab before it is modified.
  62. The location of the backup is returned in the C(backup) variable by this module.
  63. type: bool
  64. default: no
  65. requirements:
  66. - cron
  67. author:
  68. - Doug Luce (@dougluce)
  69. '''
  70. EXAMPLES = r'''
  71. - name: Ensure entry like "EMAIL=doug@ansibmod.con.com" exists
  72. community.general.cronvar:
  73. name: EMAIL
  74. value: doug@ansibmod.con.com
  75. - name: Ensure a variable does not exist. This may remove any variable named "LEGACY"
  76. community.general.cronvar:
  77. name: LEGACY
  78. state: absent
  79. - name: Add a variable to a file under /etc/cron.d
  80. community.general.cronvar:
  81. name: LOGFILE
  82. value: /var/log/yum-autoupdate.log
  83. user: root
  84. cron_file: ansible_yum-autoupdate
  85. '''
  86. import os
  87. import platform
  88. import pwd
  89. import re
  90. import shlex
  91. import sys
  92. import tempfile
  93. from ansible.module_utils.basic import AnsibleModule
  94. from ansible.module_utils.six.moves import shlex_quote
  95. class CronVarError(Exception):
  96. pass
  97. class CronVar(object):
  98. """
  99. CronVar object to write variables to crontabs.
  100. user - the user of the crontab (defaults to root)
  101. cron_file - a cron file under /etc/cron.d
  102. """
  103. def __init__(self, module, user=None, cron_file=None):
  104. self.module = module
  105. self.user = user
  106. self.lines = None
  107. self.wordchars = ''.join(chr(x) for x in range(128) if chr(x) not in ('=', "'", '"',))
  108. self.cron_cmd = self.module.get_bin_path('crontab', required=True)
  109. if cron_file:
  110. self.cron_file = ""
  111. if os.path.isabs(cron_file):
  112. self.cron_file = cron_file
  113. else:
  114. self.cron_file = os.path.join('/etc/cron.d', cron_file)
  115. else:
  116. self.cron_file = None
  117. self.read()
  118. def read(self):
  119. # Read in the crontab from the system
  120. self.lines = []
  121. if self.cron_file:
  122. # read the cronfile
  123. try:
  124. f = open(self.cron_file, 'r')
  125. self.lines = f.read().splitlines()
  126. f.close()
  127. except IOError:
  128. # cron file does not exist
  129. return
  130. except Exception:
  131. raise CronVarError("Unexpected error:", sys.exc_info()[0])
  132. else:
  133. # using safely quoted shell for now, but this really should be two non-shell calls instead. FIXME
  134. (rc, out, err) = self.module.run_command(self._read_user_execute(), use_unsafe_shell=True)
  135. if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
  136. raise CronVarError("Unable to read crontab")
  137. lines = out.splitlines()
  138. count = 0
  139. for l in lines:
  140. if count > 2 or (not re.match(r'# DO NOT EDIT THIS FILE - edit the master and reinstall.', l
  141. ) and not re.match(r'# \(/tmp/.*installed on.*\)', l) and not re.match(r'# \(.*version.*\)', l)):
  142. self.lines.append(l)
  143. count += 1
  144. def log_message(self, message):
  145. self.module.debug('ansible: "%s"' % message)
  146. def write(self, backup_file=None):
  147. """
  148. Write the crontab to the system. Saves all information.
  149. """
  150. if backup_file:
  151. fileh = open(backup_file, 'w')
  152. elif self.cron_file:
  153. fileh = open(self.cron_file, 'w')
  154. else:
  155. filed, path = tempfile.mkstemp(prefix='crontab')
  156. fileh = os.fdopen(filed, 'w')
  157. fileh.write(self.render())
  158. fileh.close()
  159. # return if making a backup
  160. if backup_file:
  161. return
  162. # Add the entire crontab back to the user crontab
  163. if not self.cron_file:
  164. # quoting shell args for now but really this should be two non-shell calls. FIXME
  165. (rc, out, err) = self.module.run_command(self._write_execute(path), use_unsafe_shell=True)
  166. os.unlink(path)
  167. if rc != 0:
  168. self.module.fail_json(msg=err)
  169. def remove_variable_file(self):
  170. try:
  171. os.unlink(self.cron_file)
  172. return True
  173. except OSError:
  174. # cron file does not exist
  175. return False
  176. except Exception:
  177. raise CronVarError("Unexpected error:", sys.exc_info()[0])
  178. def parse_for_var(self, line):
  179. lexer = shlex.shlex(line)
  180. lexer.wordchars = self.wordchars
  181. varname = lexer.get_token()
  182. is_env_var = lexer.get_token() == '='
  183. value = ''.join(lexer)
  184. if is_env_var:
  185. return (varname, value)
  186. raise CronVarError("Not a variable.")
  187. def find_variable(self, name):
  188. for l in self.lines:
  189. try:
  190. (varname, value) = self.parse_for_var(l)
  191. if varname == name:
  192. return value
  193. except CronVarError:
  194. pass
  195. return None
  196. def get_var_names(self):
  197. var_names = []
  198. for l in self.lines:
  199. try:
  200. var_name, dummy = self.parse_for_var(l)
  201. var_names.append(var_name)
  202. except CronVarError:
  203. pass
  204. return var_names
  205. def add_variable(self, name, value, insertbefore, insertafter):
  206. if insertbefore is None and insertafter is None:
  207. # Add the variable to the top of the file.
  208. self.lines.insert(0, "%s=%s" % (name, value))
  209. else:
  210. newlines = []
  211. for l in self.lines:
  212. try:
  213. varname, dummy = self.parse_for_var(l) # Throws if not a var line
  214. if varname == insertbefore:
  215. newlines.append("%s=%s" % (name, value))
  216. newlines.append(l)
  217. elif varname == insertafter:
  218. newlines.append(l)
  219. newlines.append("%s=%s" % (name, value))
  220. else:
  221. raise CronVarError # Append.
  222. except CronVarError:
  223. newlines.append(l)
  224. self.lines = newlines
  225. def remove_variable(self, name):
  226. self.update_variable(name, None, remove=True)
  227. def update_variable(self, name, value, remove=False):
  228. newlines = []
  229. for l in self.lines:
  230. try:
  231. varname, dummy = self.parse_for_var(l) # Throws if not a var line
  232. if varname != name:
  233. raise CronVarError # Append.
  234. if not remove:
  235. newlines.append("%s=%s" % (name, value))
  236. except CronVarError:
  237. newlines.append(l)
  238. self.lines = newlines
  239. def render(self):
  240. """
  241. Render a proper crontab
  242. """
  243. result = '\n'.join(self.lines)
  244. if result and result[-1] not in ['\n', '\r']:
  245. result += '\n'
  246. return result
  247. def _read_user_execute(self):
  248. """
  249. Returns the command line for reading a crontab
  250. """
  251. user = ''
  252. if self.user:
  253. if platform.system() == 'SunOS':
  254. return "su %s -c '%s -l'" % (shlex_quote(self.user), shlex_quote(self.cron_cmd))
  255. elif platform.system() == 'AIX':
  256. return "%s -l %s" % (shlex_quote(self.cron_cmd), shlex_quote(self.user))
  257. elif platform.system() == 'HP-UX':
  258. return "%s %s %s" % (self.cron_cmd, '-l', shlex_quote(self.user))
  259. elif pwd.getpwuid(os.getuid())[0] != self.user:
  260. user = '-u %s' % shlex_quote(self.user)
  261. return "%s %s %s" % (self.cron_cmd, user, '-l')
  262. def _write_execute(self, path):
  263. """
  264. Return the command line for writing a crontab
  265. """
  266. user = ''
  267. if self.user:
  268. if platform.system() in ['SunOS', 'HP-UX', 'AIX']:
  269. return "chown %s %s ; su '%s' -c '%s %s'" % (
  270. shlex_quote(self.user), shlex_quote(path), shlex_quote(self.user), self.cron_cmd, shlex_quote(path))
  271. elif pwd.getpwuid(os.getuid())[0] != self.user:
  272. user = '-u %s' % shlex_quote(self.user)
  273. return "%s %s %s" % (self.cron_cmd, user, shlex_quote(path))
  274. # ==================================================
  275. def main():
  276. # The following example playbooks:
  277. #
  278. # - community.general.cronvar: name="SHELL" value="/bin/bash"
  279. #
  280. # - name: Set the email
  281. # community.general.cronvar: name="EMAILTO" value="doug@ansibmod.con.com"
  282. #
  283. # - name: Get rid of the old new host variable
  284. # community.general.cronvar: name="NEW_HOST" state=absent
  285. #
  286. # Would produce:
  287. # SHELL = /bin/bash
  288. # EMAILTO = doug@ansibmod.con.com
  289. module = AnsibleModule(
  290. argument_spec=dict(
  291. name=dict(type='str', required=True),
  292. value=dict(type='str'),
  293. user=dict(type='str'),
  294. cron_file=dict(type='str'),
  295. insertafter=dict(type='str'),
  296. insertbefore=dict(type='str'),
  297. state=dict(type='str', default='present', choices=['absent', 'present']),
  298. backup=dict(type='bool', default=False),
  299. ),
  300. mutually_exclusive=[['insertbefore', 'insertafter']],
  301. supports_check_mode=False,
  302. )
  303. name = module.params['name']
  304. value = module.params['value']
  305. user = module.params['user']
  306. cron_file = module.params['cron_file']
  307. insertafter = module.params['insertafter']
  308. insertbefore = module.params['insertbefore']
  309. state = module.params['state']
  310. backup = module.params['backup']
  311. ensure_present = state == 'present'
  312. changed = False
  313. res_args = dict()
  314. # Ensure all files generated are only writable by the owning user. Primarily relevant for the cron_file option.
  315. os.umask(int('022', 8))
  316. cronvar = CronVar(module, user, cron_file)
  317. module.debug('cronvar instantiated - name: "%s"' % name)
  318. # --- user input validation ---
  319. if name is None and ensure_present:
  320. module.fail_json(msg="You must specify 'name' to insert a new cron variable")
  321. if value is None and ensure_present:
  322. module.fail_json(msg="You must specify 'value' to insert a new cron variable")
  323. if name is None and not ensure_present:
  324. module.fail_json(msg="You must specify 'name' to remove a cron variable")
  325. # if requested make a backup before making a change
  326. if backup:
  327. dummy, backup_file = tempfile.mkstemp(prefix='cronvar')
  328. cronvar.write(backup_file)
  329. if cronvar.cron_file and not name and not ensure_present:
  330. changed = cronvar.remove_job_file()
  331. module.exit_json(changed=changed, cron_file=cron_file, state=state)
  332. old_value = cronvar.find_variable(name)
  333. if ensure_present:
  334. if old_value is None:
  335. cronvar.add_variable(name, value, insertbefore, insertafter)
  336. changed = True
  337. elif old_value != value:
  338. cronvar.update_variable(name, value)
  339. changed = True
  340. else:
  341. if old_value is not None:
  342. cronvar.remove_variable(name)
  343. changed = True
  344. res_args = {
  345. "vars": cronvar.get_var_names(),
  346. "changed": changed
  347. }
  348. if changed:
  349. cronvar.write()
  350. # retain the backup only if crontab or cron file have changed
  351. if backup:
  352. if changed:
  353. res_args['backup_file'] = backup_file
  354. else:
  355. os.unlink(backup_file)
  356. if cron_file:
  357. res_args['cron_file'] = cron_file
  358. module.exit_json(**res_args)
  359. if __name__ == '__main__':
  360. main()