Add assemble module

This adds a module that concatenates (ie. assembles) a file from
fragments in a directory in alphabetical order.  It chains the file
module afterward to fix up ownership and permission.  This also adds
tests for the assemble module with fragments in assemble.d.
This commit is contained in:
Stephen Fromm 2012-06-11 23:09:54 -07:00
parent e88d62634f
commit 86d03713cb
6 changed files with 158 additions and 0 deletions

View file

@ -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): def _executor(self, host):
try: try:
exec_rc = self._executor_internal(host) exec_rc = self._executor_internal(host)
@ -656,6 +670,8 @@ class Runner(object):
result = self._execute_template(conn, tmp) result = self._execute_template(conn, tmp)
elif self.module_name == 'raw': elif self.module_name == 'raw':
result = self._execute_raw(conn, tmp) result = self._execute_raw(conn, tmp)
elif self.module_name == 'assemble':
result = self._execute_assemble(conn, tmp)
else: else:
if self.background == 0: if self.background == 0:
result = self._execute_normal_module(conn, tmp, module_name) result = self._execute_normal_module(conn, tmp, module_name)

111
library/assemble Executable file
View file

@ -0,0 +1,111 @@
#!/usr/bin/python
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)

View file

@ -231,5 +231,28 @@ class TestRunner(unittest.TestCase):
def test_service(self): def test_service(self):
# TODO: tests for the service module # TODO: tests for the service module
pass 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

View file

@ -0,0 +1,2 @@
# This is the first comment.
[somegroup]

View file

@ -0,0 +1,2 @@
# This is the second comment.
127.0.0.2

View file

@ -0,0 +1,4 @@
# This is the third comment.
[somegroup:vars]
foo=bar
# 無 可 無 非 常 無