From 440bac4a95db0f14b8bd2d378d9c7d8921429eb8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 24 Feb 2012 04:35:51 -0500 Subject: [PATCH] Added remote templating engine using jinja2, see examples/playbook.yml for usage. Cleanup is due in runner.py --- README.md | 6 +++- lib/ansible/runner.py | 41 ++++++++++++++++++++----- library/command | 15 +++++++-- library/setup | 7 ----- library/template | 71 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 14dfff0d43..8ad06d1417 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,13 @@ Requirements For the server the tool is running from, *only*: * python 2.6 -- or the 2.4/2.5 backport of the multiprocessing module - * PyYAML (if using playbooks) + * PyYAML (install on 'overlord' if using playbooks) * paramiko +Optional -- If you want to push templates, the nodes need: + + * python-jinja2 + Inventory file ============== diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index fa046c0eed..ed9c786421 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -119,7 +119,12 @@ class Runner(object): result = self._exec_command(conn, cmd) self._exec_command(conn, "rm -f %s" % outpath) conn.close() - return [ host, True, json.loads(result) ] + try: + return [ host, True, json.loads(result) ] + except: + traceback.print_exc() + return [ host, False, result ] + elif self.module_name == 'copy': # SFTP file copy module is not really a module self.remote_log(conn, 'COPY remote:%s local:%s' % (self.module_args[0], self.module_args[1])) @@ -128,25 +133,47 @@ class Runner(object): ftp.close() conn.close() return [ host, True, 1 ] + elif self.module_name == 'template': # template runs COPY then the template module # TODO: DRY/refactor these # TODO: things like _copy_module should take the name as a param - tempname = os.path.split(self.module_args[0])[-1] + # TODO: make it possible to override the /etc/ansible/setup file + # location for templating files as non-root + + source = self.module_args[0] + dest = self.module_args[1] + metadata = '/etc/ansible/setup' + + # first copy the source template over + tempname = os.path.split(source)[-1] temppath = self._get_tmp_path(conn, tempname) - self.remote_log(conn, 'COPY remote:%s local:%s' % (self.module_args[0], temppath)) + self.remote_log(conn, 'COPY remote:%s local:%s' % (source, temppath)) ftp = conn.open_sftp() - ftp.put(self.module_args[0], temppath) + ftp.put(source, temppath) ftp.close() + + # install the template module self.module_name = 'template' - self.module_args = [ self.module_args[0], temppath ] + self.module_args = [ source, temppath ] outpath = self._copy_module(conn) + + # run the template module + self.module_args = [ temppath, dest, metadata ] self._exec_command(conn, "chmod +x %s" % outpath) result = self._exec_command(conn, self._command(outpath)) self._exec_command(conn, "rm -f %s" % outpath) + self._exec_command(conn, "rm -f %s" % temppath) + + # TODO: remove tmppath conn.close() - return [ host, True, json.loads(result) ] - + try: + return [ host, True, json.loads(result) ] + except: + traceback.print_exc() + return [ host, False, result ] + + return [ host, False, 1 ] def _command(self, outpath): ''' form up a command string ''' diff --git a/library/command b/library/command index 780e50dd6d..b1a9ce3021 100755 --- a/library/command +++ b/library/command @@ -4,17 +4,26 @@ try: import json except ImportError: import simplejson as json + import subprocess import sys import datetime +import traceback args = sys.argv[1:] startd = datetime.datetime.now() -cmd = subprocess.Popen(args, shell=False, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) +try: + cmd = subprocess.Popen(args, shell=False, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = cmd.communicate() +except: + print json.dumps({ + "failed" : 1, + "traceback" : traceback.format_exc() + }) + sys.exit(1) -out, err = cmd.communicate() endd = datetime.datetime.now() delta = endd - startd diff --git a/library/setup b/library/setup index 77cc1c2300..de52cba709 100755 --- a/library/setup +++ b/library/setup @@ -2,7 +2,6 @@ ANSIBLE_DIR = "/etc/ansible" ANSIBLE_SETUP = "/etc/ansible/setup" -ANSIBLE_TEMPLATES = "/srv/ansible/templates" import sys import os @@ -18,12 +17,6 @@ except ImportError: input_data = sys.argv[1:] new_options = dict([ x.split("=") for x in input_data ]) -# make a directory to store templates -# if it does not already exist - -if not os.path.exists(ANSIBLE_TEMPLATES): - os.makedirs(ANSIBLE_TEMPLATES) - # create the config dir if it doesn't exist if not os.path.exists(ANSIBLE_DIR): diff --git a/library/template b/library/template index db68911cea..5348dd6147 100644 --- a/library/template +++ b/library/template @@ -1,3 +1,72 @@ #!/usr/bin/python -print {} +import sys +import os +import jinja2 +try: + import json +except ImportError: + import simplejson as json + +source = sys.argv[1] +dest = sys.argv[2] +metadata = sys.argv[3] + +# raise an error if there is no template metadata +if not os.path.exists(metadata): + print json.dumps({ + "failed" : 1, + "msg" : "Missing %s, did you run the setup module yet?" % metadata + }) + sys.exit(1) + +# raise an error if we can't parse the template metadata +try: + f = open(metadata) + data = json.loads(f.read()) + f.close() +except: + print json.dumps({ + "failed" : 1, + "msg" : "Failed to parse/load %s, rerun the setup module?" % metadata + }) + sys.exit(1) + +if not os.path.exists(source): + print json.dumps({ + "failed" : 1, + "msg" : "Source template could not be read: %s" % source + }) + sys.exit(1) + +source = file(source).read() + +# record md5sum of original source file so we can report if it changed +changed = False +md5sum = None +if os.path.exists(dest): + md5sum = os.popen("md5sum %s" % dest).read() + +# call Jinja2 here and save the new template file +template = jinja2.Template(source) +data_out = template.render(data) +f = open(dest, "w+") +f.write(data_out) +f.close() + +# TODO: catch templating errors and do not clobber the file on the +# other end unless things were successful + +# record m5sum and return success and whether things have changed +md5sum2 = os.popen("md5sum %s" % dest).read() + +if md5sum != md5sum2: + changed = True + +# mission accomplished +print json.dumps({ + "md5sum" : md5sum2, + "changed" : changed +}) + +