From cc0c908ceee228624116ec9161715b4646959700 Mon Sep 17 00:00:00 2001 From: Joshua Kehn Date: Sun, 6 Oct 2013 13:43:16 -0400 Subject: [PATCH] Added validate option to lineinfile The validate option is constructed similarly to the template command's validate option. TestRunner.py has been updated to include two new tests, one for passing and one for failing validation. --- library/files/lineinfile | 24 +++++++++++++++++++++--- test/TestRunner.py | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/library/files/lineinfile b/library/files/lineinfile index b518158cdf4..783c3d29110 100644 --- a/library/files/lineinfile +++ b/library/files/lineinfile @@ -107,6 +107,13 @@ options: description: - Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly. + validate: + required: false + description: + - validation to run before copying into place + required: false + default: None + version_added: "1.4" others: description: - All arguments accepted by the M(file) module also work here. @@ -128,6 +135,9 @@ EXAMPLES = r""" - lineinfile: "dest=/etc/sudoers state=present regexp='^%wheel' line='%wheel ALL=(ALL) NOPASSWD: ALL'" - lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\1Xms${xms}m\3' backrefs=yes + +# Validate a the sudoers file before saving +- lineinfile: dest=/etc/sudoers state=present regexp='^%ADMIN ALL\=' line='%ADMIN ALL=(ALL) NOPASSWD:ALL' validate='visudo -cf %s' """ def write_changes(module,lines,dest): @@ -137,7 +147,16 @@ def write_changes(module,lines,dest): f.writelines(lines) f.close() - module.atomic_move(tmpfile, dest) + validate = module.params.get('validate', None) + valid = not validate + if validate: + (rc, out, err) = module.run_command(validate % tmpfile) + valid = rc == 0 + if rc != 0: + module.fail_json(msg='failed to validate: ' + 'rc:%s error:%s' % (rc,err)) + if valid: + module.atomic_move(tmpfile, dest) def check_file_attrs(module, changed, message): @@ -167,8 +186,6 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, lines = f.readlines() f.close() - msg = "" - mre = re.compile(regexp) if insertafter not in (None, 'BOF', 'EOF'): @@ -289,6 +306,7 @@ def main(): backrefs=dict(default=False, type='bool'), create=dict(default=False, type='bool'), backup=dict(default=False, type='bool'), + validate=dict(default=None, type='str'), ), mutually_exclusive=[['insertbefore', 'insertafter']], add_file_common_args=True, diff --git a/test/TestRunner.py b/test/TestRunner.py index 193b560d848..f991d02bd3c 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -460,6 +460,32 @@ class TestRunner(unittest.TestCase): idx = artifact.index('communication. Typically it is depicted as a lunch-box sized object with some') assert artifact[idx - 1] == testline + # Testing validate + testline = 'Tenth: Testing with validate' + testcase = ('lineinfile', [ + "dest=%s" % sample, + "regexp='^Tenth: '", + "line='%s'" % testline, + "validate='grep -q Tenth %s'", + ]) + result = self._run(*testcase) + assert result['changed'], "File wasn't changed when it should have been" + assert result['msg'] == 'line added', "msg was incorrect" + artifact = [ x.strip() for x in open(sample) ] + assert artifact[-1] == testline + + + # Testing validate + testline = '#11: Testing with validate' + testcase = ('lineinfile', [ + "dest=%s" % sample, + "regexp='^#11: '", + "line='%s'" % testline, + "validate='grep -q #12# %s'", + ]) + result = self._run(*testcase) + assert result['failed'] + # cleanup os.unlink(sample)