diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index e4ed7cb5587..a2857a9aebc 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -612,6 +612,20 @@ class Runner(object): # ***************************************************** + def _execute_assemble(self, conn, tmp): + ''' handler for assemble operations ''' + module_name = 'assemble' + options = utils.parse_kv(self.module_args) + module = self._transfer_module(conn, tmp, module_name) + exec_rc = self._execute_module(conn, tmp, module, self.module_args) + + if exec_rc.is_successful(): + return self._chain_file_module(conn, tmp, exec_rc, options) + else: + return exec_rc + + # ***************************************************** + def _executor(self, host): try: exec_rc = self._executor_internal(host) @@ -656,6 +670,8 @@ class Runner(object): result = self._execute_template(conn, tmp) elif self.module_name == 'raw': result = self._execute_raw(conn, tmp) + elif self.module_name == 'assemble': + result = self._execute_assemble(conn, tmp) else: if self.background == 0: result = self._execute_normal_module(conn, tmp, module_name) diff --git a/library/assemble b/library/assemble new file mode 100755 index 00000000000..fcfd6bf52c0 --- /dev/null +++ b/library/assemble @@ -0,0 +1,111 @@ +#!/usr/bin/python + +# (c) 2012, Stephen Fromm +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +try: + import json +except ImportError: + import simplejson as json +import os +import os.path +import md5 +import sys +import shlex +import shutil +import syslog +import tempfile + +# Since hashlib is only available in 2.5 and onwards, this module +# uses md5 which is available in 2.4. + +# =========================================== +# Support methods + +def exit_json(rc=0, **kwargs): + print json.dumps(kwargs) + sys.exit(rc) + +def fail_json(**kwargs): + kwargs['failed'] = True + exit_json(rc=1, **kwargs) + +def assemble_from_fragments(path): + ''' assemble a file from a directory of fragments ''' + assembled = [] + for f in sorted(os.listdir(path)): + fragment = "%s/%s" % (path, f) + if os.path.isfile(fragment): + assembled.append(file(fragment).read()) + return "".join(assembled) + +def write_temp_file(data): + fd, path = tempfile.mkstemp() + os.write(fd, data) + os.close(fd) + return path + +# =========================================== + +if len(sys.argv) == 1: + fail_json(msg="the assemble module requires arguments (-a)") + +argfile = sys.argv[1] +if not os.path.exists(argfile): + fail_json(msg="Argument file not found") + +args = open(argfile, 'r').read() +items = shlex.split(args) +syslog.openlog('ansible-%s' % os.path.basename(__file__)) +syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) + +if not len(items): + fail_json(msg="the assemble module requires arguments (-a)") + +params = {} +for x in items: + (k, v) = x.split("=") + params[k] = v + +changed = False +pathmd5 = None +destmd5 = None +src = params.get('src', None) +dest = params.get('dest', None) + +if src: + src = os.path.expanduser(src) +if dest: + dest = os.path.expanduser(dest) + +if not os.path.exists(src): + fail_json(msg="Source (%s) does not exist" % src) + +if not os.path.isdir(src): + fail_json(msg="Source (%s) is not a directory" % src) + +path = write_temp_file(assemble_from_fragments(src)) +pathmd5 = md5.new(file(path).read()).hexdigest() + +if os.path.exists(dest): + destmd5 = md5.new(file(dest).read()).hexdigest() + +if pathmd5 != destmd5: + shutil.copy(path, dest) + changed = True + +exit_json(md5sum=pathmd5, changed=changed) diff --git a/test/TestRunner.py b/test/TestRunner.py index e97e9d6ce4c..76c85f7af0a 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -231,5 +231,28 @@ class TestRunner(unittest.TestCase): def test_service(self): # TODO: tests for the service module pass + def test_assemble(self): + input = self._get_test_file('assemble.d') + metadata = self._get_test_file('metadata.json') + output = self._get_stage_file('sample.out') + result = self._run('assemble', [ + "src=%s" % input, + "dest=%s" % output, + "metadata=%s" % metadata + ]) + assert os.path.exists(output) + out = file(output).read() + assert out.find("first") != -1 + assert out.find("second") != -1 + assert out.find("third") != -1 + assert result['changed'] == True + assert 'md5sum' in result + assert 'failed' not in result + result = self._run('assemble', [ + "src=%s" % input, + "dest=%s" % output, + "metadata=%s" % metadata + ]) + assert result['changed'] == False diff --git a/test/assemble.d/00-source.txt b/test/assemble.d/00-source.txt new file mode 100644 index 00000000000..1b3985419f4 --- /dev/null +++ b/test/assemble.d/00-source.txt @@ -0,0 +1,2 @@ +# This is the first comment. +[somegroup] diff --git a/test/assemble.d/01-source.txt b/test/assemble.d/01-source.txt new file mode 100644 index 00000000000..fc88cb924e9 --- /dev/null +++ b/test/assemble.d/01-source.txt @@ -0,0 +1,2 @@ +# This is the second comment. +127.0.0.2 diff --git a/test/assemble.d/02.source.txt b/test/assemble.d/02.source.txt new file mode 100644 index 00000000000..a3f1d96b48e --- /dev/null +++ b/test/assemble.d/02.source.txt @@ -0,0 +1,4 @@ +# This is the third comment. +[somegroup:vars] +foo=bar +# 無 可 無 非 常 無