From be55145a1eece3af8d3cb803bb9db6dbf3824205 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 15 Mar 2012 21:53:14 -0400 Subject: [PATCH] Initial crack at the file module --- hacking/test-module | 21 ++++- lib/ansible/utils.py | 2 + library/file | 219 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 5 deletions(-) create mode 100755 library/file diff --git a/hacking/test-module b/hacking/test-module index 3fcd0071c97..971b56a11c5 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -61,16 +61,27 @@ cmd = subprocess.Popen("%s %s" % (modfile, argspath), stderr=subprocess.PIPE) (out, err) = cmd.communicate() +if err and err != '': + print "***********************************" + print "RECIEVED DATA ON STDOUT, WILL IGNORE THIS:" + print err + try: - results = ansible.utils.parse_json(out) -except: - print "INVALID OUTPUT FORMAT" - print "*********************" + print "***********************************" + print "RAW OUTPUT" + print out + results = ansible.utils.parse_json(out) + +except: + print "***********************************" + print "INVALID OUTPUT FORMAT" print out - print "*********************" traceback.print_exc() sys.exit(1) +print "***********************************" +print "PARSED OUTPUT" + print results sys.exit(0) diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 86774463b4a..da87ca3139e 100755 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -206,6 +206,8 @@ def parse_json(data): if key == 'rc': value = int(value) results[key] = value + if len(results.keys()) == 0: + return { "failed" : True, "parsed" : False, "msg" : data } return results diff --git a/library/file b/library/file new file mode 100755 index 00000000000..fb637026b47 --- /dev/null +++ b/library/file @@ -0,0 +1,219 @@ +#!/usr/bin/python + +# (c) 2012, Michael DeHaan +# +# 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 sys +import shlex +import subprocess +import shutil +import stat +import grp +import pwd + +def debug(msg): + # ansible ignores stderr, so it's safe to use for debug + print >>sys.stderr, msg + +def exit_json(rc=0, **kwargs): + + # FIXME: if path exists, include the user, group, mode and context + # in the data if not already present, such that this module is + # also useful for inventory purposes + + print json.dumps(kwargs) + sys.exit(1) + +def fail_json(**kwargs): + kwargs['failed'] = True + exit_json(rc=1, **kwargs) + + + +# =========================================== + +argfile = sys.argv[1] +args = open(argfile, 'r').read() +items = shlex.split(args) + +if not len(items): + exit_json(msg='the module requires arguments -a') + sys.exit(1) + +params = {} +for x in items: + (k, v) = x.split("=") + params[k] = v + +state = params.get('state','file') +path = params.get('path', params.get('dest', params.get('name', None))) +link = params.get('link', 'false') +mode = params.get('mode', None) +owner = params.get('owner', None) +group = params.get('group', None) +recurse = params.get('recurse', 'false') +secontext = params.get('secontext', None) + +if state not in [ 'file', 'directory', 'absent' ]: + fail_json(msg='invalid state') +if path is None: + fail_json(msg='path is required') + +changed = False + +# =========================================== +# support functions + +def md5sum(filename): + return os.popen("/usr/bin/md5sum %s" % f).read() + +def user_and_group(filename): + st = os.stat(filename) + uid = st.st_uid + gid = st.st_gid + user = pwd.getpwuid(uid)[0] + group = grp.getgrgid(gid)[0] + debug("got user=%s and group=%s" % (user, group)) + return (user, group) + + +def set_context_if_different(path, context, changed): + if context is None: + return changed + if context is not None: + fail_json(msg='context not yet supported') + +def set_owner_if_different(path, owner, changed): + if owner is None: + debug('not tweaking owner') + return changed + user, group = user_and_group(path) + if owner != user: + debug('setting owner') + rc = os.system("/bin/chown -R %s %s" % (owner, path)) + if rc != 0: + fail_json(msg='chown failed') + return True + + return changed + +def set_group_if_different(path, group, changed): + if group is None: + debug('not tweaking group') + return changed + old_user, old_group = user_and_group(path) + if old_group != group: + debug('setting group') + rc = os.system("/bin/chgrp -R %s %s" % (group, path)) + if rc != 0: + fail_json(msg='chgrp failed') + return True + return changed + +def set_mode_if_different(path, mode, changed): + if mode is None: + debug('not tweaking mode') + return changed + try: + # FIXME: support English modes + mode = int("0%s" % mode) + except Exception, e: + fail_json(msg='mode needs to be something octalish', details=str(e)) + + st = os.stat(path) + actual_mode = stat.S_IMODE(st[stat.ST_MODE]) + + if actual_mode != mode: + try: + debug('setting mode') + os.chmod(path, mode) + except Exception, e: + fail_json(msg='chmod failed', details=str(e)) + return True + return changed + +def rmtree_error(func, path, exc_info): + fail_json(msg='failed to remove directory') + +# =========================================== +# go... + +prev_state = 'absent' +if os.path.exists(path): + if os.path.isfile(path): + prev_state = 'file' + else: + prev_state = 'directory' + +if prev_state != 'absent' and state == 'absent': + debug('requesting absent') + try: + if prev_state == 'directory': + if os.path.islink(path): + os.unlink(path) + else: + shutil.rmtree(path, ignore_errors=False, onerror=rmtree_error) + else: + os.unlink(path) + except Exception, e: + fail_json(msg=str(e)) + exit_json(changed=True) + sys.exit(0) + +if prev_state != 'absent' and prev_state != state: + fail_json(msg='refusing to convert between file and directory') + +if prev_state == 'absent' and state == 'absent': + exit_json(changed=False) + +if state == 'file': + + debug('requesting file') + if prev_state == 'absent': + fail_json(msg='file does not exist, use copy or template module to create') + + # set modes owners and context as needed + changed = set_context_if_different(path, secontext, changed) + changed = set_owner_if_different(path, owner, changed) + changed = set_group_if_different(path, group, changed) + changed = set_mode_if_different(path, mode, changed) + + exit_json(changed=changed) + +elif state == 'directory': + + debug('requesting directory') + if prev_state == 'absent': + os.makedirs(path) + changed = True + + # set modes owners and context as needed + changed = set_context_if_different(path, secontext, changed) + changed = set_owner_if_different(path, owner, changed) + changed = set_group_if_different(path, owner, changed) + changed = set_mode_if_different(path, owner, changed) + + exit_json(changed=changed) + +fail_json(msg='unexpected position reached') +sys.exit(0) +