Merge branch 'lineinfile' of git://github.com/Tinche/ansible into lif

pull/2398/merge
Michael DeHaan 12 years ago
commit 36bcfb5d49

@ -25,7 +25,8 @@ DOCUMENTATION = """
--- ---
module: lineinfile module: lineinfile
author: Daniel Hokka Zakrisson author: Daniel Hokka Zakrisson
short_description: Ensure a particular line is in a file short_description: Ensure a particular line is in a file, or replace an
existing line using a back-referenced regular expression.
description: description:
- This module will search a file for a line, and ensure that it is present or absent. - This module will search a file for a line, and ensure that it is present or absent.
- This is primarily useful when you want to change a single line in a - This is primarily useful when you want to change a single line in a
@ -36,12 +37,13 @@ options:
required: true required: true
aliases: [ name, destfile ] aliases: [ name, destfile ]
description: description:
- The file to modify - The file to modify.
regexp: regexp:
required: true required: true
description: description:
- The regular expression to look for in the file. For C(state=present), - The regular expression to look for in every line of the file. For
the pattern to replace. For C(state=absent), the pattern of the line C(state=present), the pattern to replace if found; only the last line
found will be replaced. For C(state=absent), the pattern of the line
to remove. Uses Python regular expressions; see to remove. Uses Python regular expressions; see
U(http://docs.python.org/2/library/re.html). U(http://docs.python.org/2/library/re.html).
state: state:
@ -55,7 +57,22 @@ options:
required: false required: false
description: description:
- Required for C(state=present). The line to insert/replace into the - Required for C(state=present). The line to insert/replace into the
file. May contain backreferences. file. If backrefs is set, may contain backreferences that will get
expanded with the regexp capture groups if the regexp matches. The
backreferences should be double escaped (see examples).
backrefs:
required: false
default: "no"
choices: [ "yes", "no" ]
version_added: 1.1
description:
- Used with C(state=present). If set, line can contain backreferences
(both positional and named) that will get populated if the regexp
matches. This flag changes the operation of the module slightly;
insertbefore) and insertafter will be ignored, and if the regexp
doesn't match anywhere in the file, the file will be left unchanged.
If the regexp does match, the last matching line will be replaced by
the expanded line parameter.
insertafter: insertafter:
required: false required: false
default: EOF default: EOF
@ -63,6 +80,7 @@ options:
- Used with C(state=present). If specified, the line will be inserted - Used with C(state=present). If specified, the line will be inserted
after the specified regular expression. A special value is after the specified regular expression. A special value is
available; C(EOF) for inserting the line at the end of the file. available; C(EOF) for inserting the line at the end of the file.
May not be used with backrefs.
choices: [ 'EOF', '*regex*' ] choices: [ 'EOF', '*regex*' ]
insertbefore: insertbefore:
required: false required: false
@ -70,8 +88,8 @@ options:
description: description:
- Used with C(state=present). If specified, the line will be inserted - Used with C(state=present). If specified, the line will be inserted
before the specified regular expression. A value is available; before the specified regular expression. A value is available;
C(BOF) for inserting the line at the beginning of the C(BOF) for inserting the line at the beginning of the file.
file. May not be used with backrefs.
choices: [ 'BOF', '*regex*' ] choices: [ 'BOF', '*regex*' ]
create: create:
required: false required: false
@ -90,11 +108,11 @@ options:
get the original file back if you somehow clobbered it incorrectly. get the original file back if you somehow clobbered it incorrectly.
others: others:
description: description:
- All arguments accepted by the M(file) module also work here. - All arguments accepted by the M(file) module also work here.
required: false required: false
""" """
EXAMPLES = """ EXAMPLES = r"""
lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled
lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel" lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel"
@ -105,11 +123,11 @@ EXAMPLES = """
lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default" lineinfile: dest=/etc/services regexp="^# port for http" insertbefore="^www.*80/tcp" line="# port for http by default"
lineinfile: \\\"dest=/etc/sudoers state=present regexp='^%wheel' line ='%wheel ALL=(ALL) NOPASSWD: ALL'\\\" lineinfile: dest=/etc/sudoers state=present regexp='^%wheel' line ='%wheel ALL=(ALL) NOPASSWD: ALL'
lineinfile dest=/tmp/grub.conf state=present regexp='^(splashimage=.*)$' line="#\\1" lineinfile: dest=/opt/jboss-as/bin/standalone.conf state=present regexp='^(.*)Xms(\d+)m(.*)$' line='\\1Xms${xms}m\\3'
""" """
def check_file_attrs(module, changed, message): def check_file_attrs(module, changed, message):
@ -121,9 +139,11 @@ def check_file_attrs(module, changed, message):
changed = True changed = True
message += "ownership, perms or SE linux context changed" message += "ownership, perms or SE linux context changed"
return [ message, changed ] return message, changed
def present(module, dest, regexp, line, insertafter, insertbefore, create, backup): def present(module, dest, regexp, line, insertafter, insertbefore, create,
backup, backrefs):
if os.path.isdir(dest): if os.path.isdir(dest):
module.fail_json(rc=256, msg='Destination %s is a directory !' % dest) module.fail_json(rc=256, msg='Destination %s is a directory !' % dest)
@ -142,10 +162,10 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, backu
msg = "" msg = ""
mre = re.compile(regexp) mre = re.compile(regexp)
if insertafter is not None and insertafter not in ('BOF', 'EOF'): if insertafter not in (None, 'BOF', 'EOF'):
insre = re.compile(insertafter) insre = re.compile(insertafter)
elif insertbefore is not None and insertbefore not in ('BOF'): elif insertbefore not in (None, 'BOF'):
insre = re.compile(insertbefore) insre = re.compile(insertbefore)
else: else:
insre = None insre = None
@ -154,12 +174,12 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, backu
# index[1] is the line num where insertafter/inserbefore has been found # index[1] is the line num where insertafter/inserbefore has been found
index = [-1, -1] index = [-1, -1]
m = None m = None
for lineno in range(0, len(lines)): for lineno, cur_line in enumerate(lines):
match_found = mre.search(lines[lineno]) match_found = mre.search(cur_line)
if match_found: if match_found:
index[0] = lineno index[0] = lineno
m = match_found m = match_found
elif insre is not None and insre.search(lines[lineno]): elif insre is not None and insre.search(cur_line):
if insertafter: if insertafter:
# + 1 for the next line # + 1 for the next line
index[1] = lineno + 1 index[1] = lineno + 1
@ -167,15 +187,24 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, backu
# + 1 for the previous line # + 1 for the previous line
index[1] = lineno index[1] = lineno
msg = ''
changed = False
# Regexp matched a line in the file # Regexp matched a line in the file
if index[0] != -1: if index[0] != -1:
if lines[index[0]] == m.expand(line) + os.linesep: if backrefs:
msg = '' new_line = m.expand(line)
changed = False
else: else:
lines[index[0]] = m.expand(line) + os.linesep # Don't do backref expansion if not asked.
new_line = line
if lines[index[0]] != new_line + os.linesep:
lines[index[0]] = new_line + os.linesep
msg = 'line replaced' msg = 'line replaced'
changed = True changed = True
elif backrefs:
# Do absolutely nothing, since it's not safe generating the line
# without the regexp matching to populate the backrefs.
pass
# Add it to the beginning of the file # Add it to the beginning of the file
elif insertbefore == 'BOF' or insertafter == 'BOF': elif insertbefore == 'BOF' or insertafter == 'BOF':
lines.insert(0, line + os.linesep) lines.insert(0, line + os.linesep)
@ -188,11 +217,10 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, backu
lines.append(line + os.linesep) lines.append(line + os.linesep)
msg = 'line added' msg = 'line added'
changed = True changed = True
# Do nothing if regexp didn't match # Do nothing if insert* didn't match
elif index[1] == -1: elif index[1] == -1:
msg = '' pass
changed = False # insert* matched, but not the regexp
# insertafter/insertbefore= matched
else: else:
lines.insert(index[1], line + os.linesep) lines.insert(index[1], line + os.linesep)
msg = 'line added' msg = 'line added'
@ -205,9 +233,10 @@ def present(module, dest, regexp, line, insertafter, insertbefore, create, backu
f.writelines(lines) f.writelines(lines)
f.close() f.close()
[ msg, changed ] = check_file_attrs(module, changed, msg) msg, changed = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, msg=msg) module.exit_json(changed=changed, msg=msg)
def absent(module, dest, regexp, backup): def absent(module, dest, regexp, backup):
if os.path.isdir(dest): if os.path.isdir(dest):
@ -242,36 +271,46 @@ def absent(module, dest, regexp, backup):
if changed: if changed:
msg = "%s line(s) removed" % len(found) msg = "%s line(s) removed" % len(found)
[ msg, changed ] = check_file_attrs(module, changed, msg) msg, changed = check_file_attrs(module, changed, msg)
module.exit_json(changed=changed, found=len(found), msg=msg) module.exit_json(changed=changed, found=len(found), msg=msg)
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec = dict( argument_spec=dict(
dest=dict(required=True, aliases=['name', 'destfile']), dest=dict(required=True, aliases=['name', 'destfile']),
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
regexp=dict(required=True), regexp=dict(required=True),
line=dict(aliases=['value']), line=dict(aliases=['value']),
insertafter=dict(default=None), insertafter=dict(default=None),
insertbefore=dict(default=None), insertbefore=dict(default=None),
backrefs=dict(default=False, type='bool'),
create=dict(default=False, type='bool'), create=dict(default=False, type='bool'),
backup=dict(default=False, type='bool'), backup=dict(default=False, type='bool'),
), ),
mutually_exclusive = [['insertbefore', 'insertafter']], mutually_exclusive=[['insertbefore', 'insertafter']],
add_file_common_args = True, add_file_common_args=True,
supports_check_mode = True supports_check_mode=True
) )
params = module.params params = module.params
create = module.params['create'] create = module.params['create']
backup = module.params['backup'] backup = module.params['backup']
dest = os.path.expanduser(params['dest']) backrefs = module.params['backrefs']
dest = os.path.expanduser(params['dest'])
if params['state'] == 'present': if params['state'] == 'present':
if 'line' not in params: if 'line' not in params:
module.fail_json(msg='line= is required with state=present') module.fail_json(msg='line= is required with state=present')
# Deal with the insertafter default value manually, to avoid errors
# because of the mutually_exclusive mechanism.
ins_bef, ins_aft = params['insertbefore'], params['insertafter']
if ins_bef is None and ins_aft is None:
ins_aft = 'EOF'
present(module, dest, params['regexp'], params['line'], present(module, dest, params['regexp'], params['line'],
params['insertafter'], params['insertbefore'], create, backup) ins_aft, ins_bef, create, backup, backrefs)
else: else:
absent(module, dest, params['regexp'], backup) absent(module, dest, params['regexp'], backup)
@ -279,4 +318,3 @@ def main():
#<<INCLUDE_ANSIBLE_MODULE_COMMON>> #<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main() main()

@ -10,10 +10,10 @@ import os
import shutil import shutil
import time import time
import tempfile import tempfile
import urllib2
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
def get_binary(name): def get_binary(name):
for directory in os.environ["PATH"].split(os.pathsep): for directory in os.environ["PATH"].split(os.pathsep):
path = os.path.join(directory, name) path = os.path.join(directory, name)
@ -21,6 +21,7 @@ def get_binary(name):
return path return path
return None return None
class TestRunner(unittest.TestCase): class TestRunner(unittest.TestCase):
def setUp(self): def setUp(self):
@ -110,70 +111,70 @@ class TestRunner(unittest.TestCase):
data_out = file(output).read() data_out = file(output).read()
assert data_in == data_out assert data_in == data_out
assert 'failed' not in result assert 'failed' not in result
assert result['changed'] == True assert result['changed'] is True
assert 'md5sum' in result assert 'md5sum' in result
result = self._run('copy', [ result = self._run('copy', [
"src=%s" % input_, "src=%s" % input_,
"dest=%s" % output, "dest=%s" % output,
]) ])
assert result['changed'] == False assert result['changed'] is False
def test_command(self): def test_command(self):
# test command module, change trigger, etc # test command module, change trigger, etc
result = self._run('command', [ "/bin/echo", "hi" ]) result = self._run('command', ["/bin/echo", "hi"])
assert "failed" not in result assert "failed" not in result
assert "msg" not in result assert "msg" not in result
assert result['rc'] == 0 assert result['rc'] == 0
assert result['stdout'] == 'hi' assert result['stdout'] == 'hi'
assert result['stderr'] == '' assert result['stderr'] == ''
result = self._run('command', [ "false" ]) result = self._run('command', ["false"])
assert result['rc'] == 1 assert result['rc'] == 1
assert 'failed' not in result assert 'failed' not in result
result = self._run('command', [ "/usr/bin/this_does_not_exist", "splat" ]) result = self._run('command', ["/usr/bin/this_does_not_exist", "splat"])
assert 'msg' in result assert 'msg' in result
assert 'failed' in result assert 'failed' in result
result = self._run('shell', [ "/bin/echo", "$HOME" ]) result = self._run('shell', ["/bin/echo", "$HOME"])
assert 'failed' not in result assert 'failed' not in result
assert result['rc'] == 0 assert result['rc'] == 0
result = self._run('command', [ "creates='/tmp/ansible command test'", "chdir=/tmp", "touch", "'ansible command test'" ]) result = self._run('command', ["creates='/tmp/ansible command test'", "chdir=/tmp", "touch", "'ansible command test'"])
assert 'changed' in result assert 'changed' in result
assert result['rc'] == 0 assert result['rc'] == 0
result = self._run('command', [ "creates='/tmp/ansible command test'", "false" ]) result = self._run('command', ["creates='/tmp/ansible command test'", "false"])
assert 'skipped' in result assert 'skipped' in result
result = self._run('shell', [ "removes=/tmp/ansible\\ command\\ test", "chdir=/tmp", "rm -f 'ansible command test'; echo $?" ]) result = self._run('shell', ["removes=/tmp/ansible\\ command\\ test", "chdir=/tmp", "rm -f 'ansible command test'; echo $?"])
assert 'changed' in result assert 'changed' in result
assert result['rc'] == 0 assert result['rc'] == 0
assert result['stdout'] == '0' assert result['stdout'] == '0'
result = self._run('shell', [ "removes=/tmp/ansible\\ command\\ test", "false" ]) result = self._run('shell', ["removes=/tmp/ansible\\ command\\ test", "false"])
assert 'skipped' in result assert 'skipped' in result
def test_git(self): def test_git(self):
self._run('file',['path=/tmp/gitdemo','state=absent']) self._run('file', ['path=/tmp/gitdemo', 'state=absent'])
self._run('file',['path=/tmp/gd','state=absent']) self._run('file', ['path=/tmp/gd', 'state=absent'])
self._run('command',['git init gitdemo', 'chdir=/tmp']) self._run('command', ['git init gitdemo', 'chdir=/tmp'])
self._run('command',['touch a', 'chdir=/tmp/gitdemo']) self._run('command', ['touch a', 'chdir=/tmp/gitdemo'])
self._run('command',['git add *', 'chdir=/tmp/gitdemo']) self._run('command', ['git add *', 'chdir=/tmp/gitdemo'])
self._run('command',['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo']) self._run('command', ['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo'])
self._run('command',['touch b', 'chdir=/tmp/gitdemo']) self._run('command', ['touch b', 'chdir=/tmp/gitdemo'])
self._run('command',['git add *', 'chdir=/tmp/gitdemo']) self._run('command', ['git add *', 'chdir=/tmp/gitdemo'])
self._run('command',['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo']) self._run('command', ['git commit -m "test commit 2"', 'chdir=/tmp/gitdemo'])
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd" ]) result = self._run('git', ["repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd"])
assert result['changed'] assert result['changed']
# test the force option not set # test the force option not set
self._run('file',['path=/tmp/gd/a','state=absent']) self._run('file', ['path=/tmp/gd/a', 'state=absent'])
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=no" ]) result = self._run('git', ["repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=no"])
assert result['failed'] assert result['failed']
# test the force option when set # test the force option when set
result = self._run('git', [ "repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=yes" ]) result = self._run('git', ["repo=\"file:///tmp/gitdemo\"", "dest=/tmp/gd", "force=yes"])
assert result['changed'] assert result['changed']
def test_file(self): def test_file(self):
filedemo = tempfile.mkstemp()[1] filedemo = tempfile.mkstemp()[1]
assert self._run('file', ['dest=' + filedemo, 'state=directory'])['failed'] assert self._run('file', ['dest=' + filedemo, 'state=directory'])['failed']
@ -191,7 +192,6 @@ class TestRunner(unittest.TestCase):
assert not os.path.exists(filedemo) assert not os.path.exists(filedemo)
assert not self._run('file', ['dest=' + filedemo, 'state=absent'])['changed'] assert not self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
filedemo = tempfile.mkdtemp() filedemo = tempfile.mkdtemp()
assert self._run('file', ['dest=' + filedemo, 'state=file'])['failed'] assert self._run('file', ['dest=' + filedemo, 'state=file'])['failed']
assert os.path.isdir(filedemo) assert os.path.isdir(filedemo)
@ -209,7 +209,6 @@ class TestRunner(unittest.TestCase):
assert not os.path.exists(filedemo) assert not os.path.exists(filedemo)
assert not self._run('file', ['dest=' + filedemo, 'state=absent'])['changed'] assert not self._run('file', ['dest=' + filedemo, 'state=absent'])['changed']
tmp_dir = tempfile.mkdtemp() tmp_dir = tempfile.mkdtemp()
filedemo = os.path.join(tmp_dir, 'link') filedemo = os.path.join(tmp_dir, 'link')
os.symlink('/dev/zero', filedemo) os.symlink('/dev/zero', filedemo)
@ -234,7 +233,7 @@ class TestRunner(unittest.TestCase):
if not os.path.exists(large_path): if not os.path.exists(large_path):
raise SkipTest raise SkipTest
# Ensure reading a large amount of output from a command doesn't hang. # Ensure reading a large amount of output from a command doesn't hang.
result = self._run('command', [ "/bin/cat", large_path ]) result = self._run('command', ["/bin/cat", large_path])
assert "failed" not in result assert "failed" not in result
assert "msg" not in result assert "msg" not in result
assert result['rc'] == 0 assert result['rc'] == 0
@ -244,14 +243,14 @@ class TestRunner(unittest.TestCase):
def test_async(self): def test_async(self):
# test async launch and job status # test async launch and job status
# of any particular module # of any particular module
result = self._run('command', [ get_binary("sleep"), "3" ], background=20) result = self._run('command', [get_binary("sleep"), "3"], background=20)
assert 'ansible_job_id' in result assert 'ansible_job_id' in result
assert 'started' in result assert 'started' in result
jid = result['ansible_job_id'] jid = result['ansible_job_id']
# no real chance of this op taking a while, but whatever # no real chance of this op taking a while, but whatever
time.sleep(5) time.sleep(5)
# CLI will abstract this (when polling), but this is how it works internally # CLI will abstract this (when polling), but this is how it works internally
result = self._run('async_status', [ "jid=%s" % jid ]) result = self._run('async_status', ["jid=%s" % jid])
# TODO: would be nice to have tests for supervisory process # TODO: would be nice to have tests for supervisory process
# killing job after X seconds # killing job after X seconds
assert 'finished' in result assert 'finished' in result
@ -263,7 +262,7 @@ class TestRunner(unittest.TestCase):
def test_fetch(self): def test_fetch(self):
input_ = self._get_test_file('sample.j2') input_ = self._get_test_file('sample.j2')
output = os.path.join(self.stage_dir, 'localhost', input_) output = os.path.join(self.stage_dir, 'localhost', input_)
result = self._run('fetch', [ "src=%s" % input_, "dest=%s" % self.stage_dir ]) self._run('fetch', ["src=%s" % input_, "dest=%s" % self.stage_dir])
assert os.path.exists(output) assert os.path.exists(output)
assert open(input_).read() == open(output).read() assert open(input_).read() == open(output).read()
@ -280,7 +279,7 @@ class TestRunner(unittest.TestCase):
assert out.find("first") != -1 assert out.find("first") != -1
assert out.find("second") != -1 assert out.find("second") != -1
assert out.find("third") != -1 assert out.find("third") != -1
assert result['changed'] == True assert result['changed'] is True
assert 'md5sum' in result assert 'md5sum' in result
assert 'failed' not in result assert 'failed' not in result
result = self._run('assemble', [ result = self._run('assemble', [
@ -288,13 +287,14 @@ class TestRunner(unittest.TestCase):
"dest=%s" % output, "dest=%s" % output,
]) ])
print result print result
assert result['changed'] == False assert result['changed'] is False
def test_lineinfile(self): def test_lineinfile(self):
"""Unit tests for the lineinfile module, without backref features."""
sampleroot = 'rocannon' sampleroot = 'rocannon'
sample_origin = self._get_test_file(sampleroot + '.txt') sample_origin = self._get_test_file(sampleroot + '.txt')
sample = self._get_stage_file(sampleroot + '.out' + '.txt') sample = self._get_stage_file(sampleroot + '.out' + '.txt')
shutil.copy( sample_origin, sample) shutil.copy(sample_origin, sample)
# The order of the test cases is important # The order of the test cases is important
# defaults to insertafter at the end of the file # defaults to insertafter at the end of the file
@ -303,19 +303,19 @@ class TestRunner(unittest.TestCase):
"dest=%s" % sample, "dest=%s" % sample,
"regexp='^First: '", "regexp='^First: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line added' assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact[-1] == testline assert artifact[-1] == testline
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
# run a second time, verify only one line has been added # run a second time, verify only one line has been added
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == False assert not result['changed']
assert result['msg'] == '' assert result['msg'] == ''
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
# insertafter with EOF # insertafter with EOF
@ -325,48 +325,48 @@ class TestRunner(unittest.TestCase):
"insertafter=EOF", "insertafter=EOF",
"regexp='^Second: '", "regexp='^Second: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line added' assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact[-1] == testline assert artifact[-1] == testline
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
# with invalid insertafter regex # with invalid insertafter regex
# If the regexp doesn't match and the insertafter doesn't match,
# do nothing.
testline = 'Third: Line added with an invalid insertafter regex' testline = 'Third: Line added with an invalid insertafter regex'
testcase = ('lineinfile', [ testcase = ('lineinfile', [
"dest=%s" % sample, "dest=%s" % sample,
"insertafter='^abcdefgh'", "insertafter='^abcdefgh'",
"regexp='^Third: '", "regexp='^Third: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert not result['changed']
assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ]
assert artifact[-1] == testline
assert artifact.count(testline) == 1
# with an insertafter regex # with an insertafter regex
# The regexp doesn't match, but the insertafter is specified and does,
# so insert after insertafter.
testline = 'Fourth: Line added with a valid insertafter regex' testline = 'Fourth: Line added with a valid insertafter regex'
testcase = ('lineinfile', [ testcase = ('lineinfile', [
"dest=%s" % sample, "dest=%s" % sample,
"insertafter='^receive messages to '", "insertafter='^receive messages to '",
"regexp='^Fourth: '", "regexp='^Fourth: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line added' assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
idx = artifact.index('receive messages to and from a corresponding device over any distance') idx = artifact.index('receive messages to and from a corresponding device over any distance')
assert artifact[idx + 1] == testline assert artifact[idx + 1] == testline
# replacement of a line from a regex # replacement of a line from a regex
# we replace the line, so we need to get its idx before the run # we replace the line, so we need to get its idx before the run
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
target_line = 'combination of microphone, speaker, keyboard and display. It can send and' target_line = 'combination of microphone, speaker, keyboard and display. It can send and'
idx = artifact.index(target_line) idx = artifact.index(target_line)
@ -375,18 +375,18 @@ class TestRunner(unittest.TestCase):
"dest=%s" % sample, "dest=%s" % sample,
"regexp='combination of microphone'", "regexp='combination of microphone'",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line replaced' assert result['msg'] == 'line replaced'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
assert artifact.index(testline) == idx assert artifact.index(testline) == idx
assert target_line not in artifact assert target_line not in artifact
# removal of a line # removal of a line
# we replace the line, so we need to get its idx before the run # we replace the line, so we need to get its idx before the run
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
target_line = 'receive messages to and from a corresponding device over any distance' target_line = 'receive messages to and from a corresponding device over any distance'
idx = artifact.index(target_line) idx = artifact.index(target_line)
@ -394,13 +394,12 @@ class TestRunner(unittest.TestCase):
"dest=%s" % sample, "dest=%s" % sample,
"regexp='^receive messages to and from '", "regexp='^receive messages to and from '",
"state=absent" "state=absent"
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert target_line not in artifact assert target_line not in artifact
# with both insertafter and insertbefore (should fail) # with both insertafter and insertbefore (should fail)
testline = 'Seventh: this line should not be there' testline = 'Seventh: this line should not be there'
testcase = ('lineinfile', [ testcase = ('lineinfile', [
@ -409,9 +408,9 @@ class TestRunner(unittest.TestCase):
"insertbefore='BOF'", "insertbefore='BOF'",
"regexp='^communication. '", "regexp='^communication. '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['failed'] == True assert result['failed']
# insertbefore with BOF # insertbefore with BOF
testline = 'Eighth: insertbefore BOF' testline = 'Eighth: insertbefore BOF'
@ -420,11 +419,11 @@ class TestRunner(unittest.TestCase):
"insertbefore=BOF", "insertbefore=BOF",
"regexp='^Eighth: '", "regexp='^Eighth: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line added' assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
assert artifact[0] == testline assert artifact[0] == testline
@ -435,11 +434,11 @@ class TestRunner(unittest.TestCase):
"insertbefore='^communication. Typically '", "insertbefore='^communication. Typically '",
"regexp='^Ninth: '", "regexp='^Ninth: '",
"line='%s'" % testline "line='%s'" % testline
]) ])
result = self._run(*testcase) result = self._run(*testcase)
assert result['changed'] == True assert result['changed']
assert result['msg'] == 'line added' assert result['msg'] == 'line added'
artifact = [ x.strip() for x in open(sample).readlines() ] artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline) == 1 assert artifact.count(testline) == 1
idx = artifact.index('communication. Typically it is depicted as a lunch-box sized object with some') idx = artifact.index('communication. Typically it is depicted as a lunch-box sized object with some')
assert artifact[idx - 1] == testline assert artifact[idx - 1] == testline
@ -447,4 +446,139 @@ class TestRunner(unittest.TestCase):
# cleanup # cleanup
os.unlink(sample) os.unlink(sample)
def test_lineinfile_backrefs(self):
"""Unit tests for the lineinfile module, with backref features."""
sampleroot = 'rocannon'
sample_origin = self._get_test_file(sampleroot + '.txt')
origin_lines = [line.strip() for line in open(sample_origin)]
sample = self._get_stage_file(sampleroot + '.out' + '.txt')
shutil.copy(sample_origin, sample)
# The order of the test cases is important
# The regexp doesn't match, so the line will not be added anywhere.
testline = r'\\1: Line added by default at the end of the file.'
testcase = ('lineinfile', [
"dest=%s" % sample,
"regexp='^(First): '",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert not result['changed']
assert result['msg'] == ''
artifact = [x.strip() for x in open(sample)]
assert artifact == origin_lines
# insertafter with EOF
# The regexp doesn't match, so the line will not be added anywhere.
testline = r'\\1: Line added with insertafter=EOF'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter=EOF",
"regexp='^(Second): '",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert not result['changed']
assert result['msg'] == ''
artifact = [x.strip() for x in open(sample)]
assert artifact == origin_lines
# with invalid insertafter regex
# The regexp doesn't match, so do nothing.
testline = r'\\1: Line added with an invalid insertafter regex'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter='^abcdefgh'",
"regexp='^(Third): '",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert not result['changed']
assert artifact == origin_lines
# with an insertafter regex
# The regexp doesn't match, so do nothing.
testline = r'\\1: Line added with a valid insertafter regex'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter='^receive messages to '",
"regexp='^(Fourth): '",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert not result['changed']
assert result['msg'] == ''
assert artifact == origin_lines
# replacement of a line from a regex
# we replace the line, so we need to get its idx before the run
artifact = [x.strip() for x in open(sample)]
target_line = 'combination of microphone, speaker, keyboard and display. It can send and'
idx = artifact.index(target_line)
testline = r'\\1 of megaphone'
testline_after = 'combination of megaphone'
testcase = ('lineinfile', [
"dest=%s" % sample,
"regexp='(combination) of microphone'",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert result['changed']
assert result['msg'] == 'line replaced'
artifact = [x.strip() for x in open(sample)]
assert artifact.count(testline_after) == 1
assert artifact.index(testline_after) == idx
assert target_line not in artifact
# Go again, should be unchanged now.
testline = r'\\1 of megaphone'
testline_after = 'combination of megaphone'
testcase = ('lineinfile', [
"dest=%s" % sample,
"regexp='(combination) of megaphone'",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
assert not result['changed']
assert result['msg'] == ''
# Try a numeric, named capture group example.
f = open(sample, 'a+')
f.write("1 + 1 = 3" + os.linesep)
f.close()
testline = r"2 + \\g<num> = 3"
testline_after = "2 + 1 = 3"
testcase = ('lineinfile', [
"dest=%s" % sample,
r"regexp='1 \\+ (?P<num>\\d) = 3'",
"line='%s'" % testline,
"backrefs=yes",
])
result = self._run(*testcase)
artifact = [x.strip() for x in open(sample)]
assert result['changed']
assert result['msg'] == 'line replaced'
artifact = [x.strip() for x in open(sample)]
assert '1 + 1 = 3' not in artifact
assert testline_after == artifact[-1]
# with both insertafter and insertbefore (should fail)
testline = 'Seventh: this line should not be there'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter='BOF'",
"insertbefore='BOF'",
"regexp='^communication. '",
"line='%s'" % testline
])
result = self._run(*testcase)
assert result['failed']
os.unlink(sample)

Loading…
Cancel
Save