From ca581840ef94b2a504b6a264b8e831473a25dbc2 Mon Sep 17 00:00:00 2001 From: tin Date: Tue, 26 Mar 2013 22:00:34 +0100 Subject: [PATCH] Added additional lineinfile documentation. A little more unit testing. --- library/lineinfile | 38 ++++++++++++++++++++++++++++---------- test/TestRunner.py | 45 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/library/lineinfile b/library/lineinfile index dab42e2a8a..355251c3b7 100644 --- a/library/lineinfile +++ b/library/lineinfile @@ -25,7 +25,8 @@ DOCUMENTATION = """ --- module: lineinfile 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: - 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 @@ -36,12 +37,13 @@ options: required: true aliases: [ name, destfile ] description: - - The file to modify + - The file to modify. regexp: required: true description: - - The regular expression to look for in the file. For C(state=present), - the pattern to replace. For C(state=absent), the pattern of the line + - The regular expression to look for in every line of the file. For + 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 U(http://docs.python.org/2/library/re.html). state: @@ -55,7 +57,22 @@ options: required: false description: - 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: required: false default: EOF @@ -63,6 +80,7 @@ options: - Used with C(state=present). If specified, the line will be inserted after the specified regular expression. A special value is available; C(EOF) for inserting the line at the end of the file. + May not be used with backrefs. choices: [ 'EOF', '*regex*' ] insertbefore: required: false @@ -70,8 +88,8 @@ options: description: - Used with C(state=present). If specified, the line will be inserted before the specified regular expression. A value is available; - C(BOF) for inserting the line at the beginning of the - file. + C(BOF) for inserting the line at the beginning of the file. + May not be used with backrefs. choices: [ 'BOF', '*regex*' ] create: required: false @@ -94,7 +112,7 @@ options: required: false """ -EXAMPLES = """ +EXAMPLES = r""" lineinfile: dest=/etc/selinux/config regexp=^SELINUX= line=SELINUX=disabled lineinfile: dest=/etc/sudoers state=absent regexp="^%wheel" @@ -105,9 +123,9 @@ EXAMPLES = """ 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' """ diff --git a/test/TestRunner.py b/test/TestRunner.py index 630f98922e..ac24396410 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -456,7 +456,7 @@ class TestRunner(unittest.TestCase): # The order of the test cases is important # The regexp doesn't match, so the line will not be added anywhere. - testline = '\\1: Line added by default at the end of the file.' + testline = r'\\1: Line added by default at the end of the file.' testcase = ('lineinfile', [ "dest=%s" % sample, "regexp='^(First): '", @@ -471,7 +471,7 @@ class TestRunner(unittest.TestCase): # insertafter with EOF # The regexp doesn't match, so the line will not be added anywhere. - testline = '\\1: Line added with insertafter=EOF' + testline = r'\\1: Line added with insertafter=EOF' testcase = ('lineinfile', [ "dest=%s" % sample, "insertafter=EOF", @@ -487,7 +487,7 @@ class TestRunner(unittest.TestCase): # with invalid insertafter regex # The regexp doesn't match, so do nothing. - testline = '\\1: Line added with an invalid insertafter regex' + testline = r'\\1: Line added with an invalid insertafter regex' testcase = ('lineinfile', [ "dest=%s" % sample, "insertafter='^abcdefgh'", @@ -501,7 +501,7 @@ class TestRunner(unittest.TestCase): # with an insertafter regex # The regexp doesn't match, so do nothing. - testline = '\\1: Line added with a valid insertafter regex' + testline = r'\\1: Line added with a valid insertafter regex' testcase = ('lineinfile', [ "dest=%s" % sample, "insertafter='^receive messages to '", @@ -520,8 +520,8 @@ class TestRunner(unittest.TestCase): target_line = 'combination of microphone, speaker, keyboard and display. It can send and' idx = artifact.index(target_line) - testline = '\\\\1 of microphone' - testline_after = 'combination of microphone' + testline = r'\\1 of megaphone' + testline_after = 'combination of megaphone' testcase = ('lineinfile', [ "dest=%s" % sample, "regexp='(combination) of microphone'", @@ -536,6 +536,39 @@ class TestRunner(unittest.TestCase): 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 = 3" + testline_after = "2 + 1 = 3" + testcase = ('lineinfile', [ + "dest=%s" % sample, + r"regexp='1 \\+ (?P\\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', [