We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Blockinfile module : Documentation mistake
##### Issue Type:
- Documentation Report
##### Plugin Name:

Blockinfile module
##### Ansible Version:

```
$ ansible --version
ansible 2.0.1.0
  config file = /data/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### Ansible Configuration:

n.a.
##### Environment:

n.a
##### Summary:

Wrong spacing in official documentation
##### Steps To Reproduce:

```
https://docs.ansible.com/ansible/blockinfile_module.html
Chapter Example, last example using < with_items >
```
##### Expected Results:

Expression with_items: should be level with blockinfile:
##### Actual Results:

```
Example does not work as expression < with_items: > is not level with  <blockinfile:> 
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `files/blockinfile.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # (c) 2014, 2015 YAEGASHI Takeshi <yaegashi@debian.org>
5 #
6 # This file is part of Ansible
7 #
8 # Ansible is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Ansible is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
20 
21 import re
22 import os
23 import tempfile
24 
25 DOCUMENTATION = """
26 ---
27 module: blockinfile
28 author:
29     - 'YAEGASHI Takeshi (@yaegashi)'
30 extends_documentation_fragment:
31     - files
32     - validate
33 short_description: Insert/update/remove a text block
34                    surrounded by marker lines.
35 version_added: '2.0'
36 description:
37   - This module will insert/update/remove a block of multi-line text
38     surrounded by customizable marker lines.
39 notes:
40   - This module supports check mode.
41   - When using 'with_*' loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.
42 options:
43   dest:
44     aliases: [ name, destfile ]
45     required: true
46     description:
47       - The file to modify.
48   state:
49     required: false
50     choices: [ present, absent ]
51     default: present
52     description:
53       - Whether the block should be there or not.
54   marker:
55     required: false
56     default: '# {mark} ANSIBLE MANAGED BLOCK'
57     description:
58       - The marker line template.
59         "{mark}" will be replaced with "BEGIN" or "END".
60   block:
61     aliases: [ content ]
62     required: false
63     default: ''
64     description:
65       - The text to insert inside the marker lines.
66         If it's missing or an empty string,
67         the block will be removed as if C(state) were specified to C(absent).
68   insertafter:
69     required: false
70     default: EOF
71     description:
72       - If specified, the block will be inserted after the last match of
73         specified regular expression. A special value is available; C(EOF) for
74         inserting the block at the end of the file.  If specified regular
75         expresion has no matches, C(EOF) will be used instead.
76     choices: [ 'EOF', '*regex*' ]
77   insertbefore:
78     required: false
79     default: None
80     description:
81       - If specified, the block will be inserted before the last match of
82         specified regular expression. A special value is available; C(BOF) for
83         inserting the block at the beginning of the file.  If specified regular
84         expresion has no matches, the block will be inserted at the end of the
85         file.
86     choices: [ 'BOF', '*regex*' ]
87   create:
88     required: false
89     default: 'no'
90     choices: [ 'yes', 'no' ]
91     description:
92       - Create a new file if it doesn't exist.
93   backup:
94     required: false
95     default: 'no'
96     choices: [ 'yes', 'no' ]
97     description:
98       - Create a backup file including the timestamp information so you can
99         get the original file back if you somehow clobbered it incorrectly.
100   follow:
101     required: false
102     default: "no"
103     choices: [ "yes", "no" ]
104     description:
105       - 'This flag indicates that filesystem links, if they exist, should be followed.'
106     version_added: "2.1"
107 """
108 
109 EXAMPLES = r"""
110 - name: insert/update "Match User" configuation block in /etc/ssh/sshd_config
111   blockinfile:
112     dest: /etc/ssh/sshd_config
113     block: |
114       Match User ansible-agent
115       PasswordAuthentication no
116 
117 - name: insert/update eth0 configuration stanza in /etc/network/interfaces
118         (it might be better to copy files into /etc/network/interfaces.d/)
119   blockinfile:
120     dest: /etc/network/interfaces
121     block: |
122       iface eth0 inet static
123           address 192.168.0.1
124           netmask 255.255.255.0
125 
126 - name: insert/update HTML surrounded by custom markers after <body> line
127   blockinfile:
128     dest: /var/www/html/index.html
129     marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
130     insertafter: "<body>"
131     content: |
132       <h1>Welcome to {{ansible_hostname}}</h1>
133       <p>Last updated on {{ansible_date_time.iso8601}}</p>
134 
135 - name: remove HTML as well as surrounding markers
136   blockinfile:
137     dest: /var/www/html/index.html
138     marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
139     content: ""
140 
141 - name: insert/update "Match User" configuation block in /etc/ssh/sshd_config
142   blockinfile:
143     dest: /etc/hosts
144     block: |
145       {{item.name}} {{item.ip}}
146     marker: "# {mark} ANSIBLE MANAGED BLOCK {{item.name}}"
147     with_items:
148         - { name: host1, ip: 10.10.1.10 }
149         - { name: host2, ip: 10.10.1.11 }
150         - { name: host3, ip: 10.10.1.12 }
151 """
152 
153 
154 def write_changes(module, contents, dest):
155 
156     tmpfd, tmpfile = tempfile.mkstemp()
157     f = os.fdopen(tmpfd, 'wb')
158     f.write(contents)
159     f.close()
160 
161     validate = module.params.get('validate', None)
162     valid = not validate
163     if validate:
164         if "%s" not in validate:
165             module.fail_json(msg="validate must contain %%s: %s" % (validate))
166         (rc, out, err) = module.run_command(validate % tmpfile)
167         valid = rc == 0
168         if rc != 0:
169             module.fail_json(msg='failed to validate: '
170                                  'rc:%s error:%s' % (rc, err))
171     if valid:
172         module.atomic_move(tmpfile, dest)
173 
174 
175 def check_file_attrs(module, changed, message):
176 
177     file_args = module.load_file_common_arguments(module.params)
178     if module.set_file_attributes_if_different(file_args, False):
179 
180         if changed:
181             message += " and "
182         changed = True
183         message += "ownership, perms or SE linux context changed"
184 
185     return message, changed
186 
187 
188 def main():
189     module = AnsibleModule(
190         argument_spec=dict(
191             dest=dict(required=True, aliases=['name', 'destfile']),
192             state=dict(default='present', choices=['absent', 'present']),
193             marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'),
194             block=dict(default='', type='str', aliases=['content']),
195             insertafter=dict(default=None),
196             insertbefore=dict(default=None),
197             create=dict(default=False, type='bool'),
198             backup=dict(default=False, type='bool'),
199             validate=dict(default=None, type='str'),
200         ),
201         mutually_exclusive=[['insertbefore', 'insertafter']],
202         add_file_common_args=True,
203         supports_check_mode=True
204     )
205 
206     params = module.params
207     dest = os.path.expanduser(params['dest'])
208     if module.boolean(params.get('follow', None)):
209         dest = os.path.realpath(dest)
210 
211     if os.path.isdir(dest):
212         module.fail_json(rc=256,
213                          msg='Destination %s is a directory !' % dest)
214 
215     if not os.path.exists(dest):
216         if not module.boolean(params['create']):
217             module.fail_json(rc=257,
218                              msg='Destination %s does not exist !' % dest)
219         original = None
220         lines = []
221     else:
222         f = open(dest, 'rb')
223         original = f.read()
224         f.close()
225         lines = original.splitlines()
226 
227     insertbefore = params['insertbefore']
228     insertafter = params['insertafter']
229     block = params['block']
230     marker = params['marker']
231     present = params['state'] == 'present'
232 
233     if insertbefore is None and insertafter is None:
234         insertafter = 'EOF'
235 
236     if insertafter not in (None, 'EOF'):
237         insertre = re.compile(insertafter)
238     elif insertbefore not in (None, 'BOF'):
239         insertre = re.compile(insertbefore)
240     else:
241         insertre = None
242 
243     marker0 = re.sub(r'{mark}', 'BEGIN', marker)
244     marker1 = re.sub(r'{mark}', 'END', marker)
245     if present and block:
246         # Escape seqeuences like '\n' need to be handled in Ansible 1.x
247         if ANSIBLE_VERSION.startswith('1.'):
248             block = re.sub('', block, '')
249         blocklines = [marker0] + block.splitlines() + [marker1]
250     else:
251         blocklines = []
252 
253     n0 = n1 = None
254     for i, line in enumerate(lines):
255         if line.startswith(marker0):
256             n0 = i
257         if line.startswith(marker1):
258             n1 = i
259 
260     if None in (n0, n1):
261         n0 = None
262         if insertre is not None:
263             for i, line in enumerate(lines):
264                 if insertre.search(line):
265                     n0 = i
266             if n0 is None:
267                 n0 = len(lines)
268             elif insertafter is not None:
269                 n0 += 1
270         elif insertbefore is not None:
271             n0 = 0           # insertbefore=BOF
272         else:
273             n0 = len(lines)  # insertafter=EOF
274     elif n0 < n1:
275         lines[n0:n1+1] = []
276     else:
277         lines[n1:n0+1] = []
278         n0 = n1
279 
280     lines[n0:n0] = blocklines
281 
282     if lines:
283         result = '\n'.join(lines)+'\n'
284     else:
285         result = ''
286     if original == result:
287         msg = ''
288         changed = False
289     elif original is None:
290         msg = 'File created'
291         changed = True
292     elif not blocklines:
293         msg = 'Block removed'
294         changed = True
295     else:
296         msg = 'Block inserted'
297         changed = True
298 
299     if changed and not module.check_mode:
300         if module.boolean(params['backup']) and os.path.exists(dest):
301             module.backup_local(dest)
302         write_changes(module, result, dest)
303 
304     msg, changed = check_file_attrs(module, changed, msg)
305     module.exit_json(changed=changed, msg=msg)
306 
307 # import module snippets
308 from ansible.module_utils.basic import *
309 from ansible.module_utils.splitter import *
310 if __name__ == '__main__':
311     main()
312
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/files/blockinfile.py b/files/blockinfile.py
--- a/files/blockinfile.py
+++ b/files/blockinfile.py
@@ -144,10 +144,10 @@
     block: |
       {{item.name}} {{item.ip}}
     marker: "# {mark} ANSIBLE MANAGED BLOCK {{item.name}}"
-    with_items:
-        - { name: host1, ip: 10.10.1.10 }
-        - { name: host2, ip: 10.10.1.11 }
-        - { name: host3, ip: 10.10.1.12 }
+  with_items:
+      - { name: host1, ip: 10.10.1.10 }
+      - { name: host2, ip: 10.10.1.11 }
+      - { name: host3, ip: 10.10.1.12 }
 """

{"golden_diff": "diff --git a/files/blockinfile.py b/files/blockinfile.py\n--- a/files/blockinfile.py\n+++ b/files/blockinfile.py\n@@ -144,10 +144,10 @@\n     block: |\n       {{item.name}} {{item.ip}}\n     marker: \"# {mark} ANSIBLE MANAGED BLOCK {{item.name}}\"\n-    with_items:\n-        - { name: host1, ip: 10.10.1.10 }\n-        - { name: host2, ip: 10.10.1.11 }\n-        - { name: host3, ip: 10.10.1.12 }\n+  with_items:\n+      - { name: host1, ip: 10.10.1.10 }\n+      - { name: host2, ip: 10.10.1.11 }\n+      - { name: host3, ip: 10.10.1.12 }\n \"\"\"\n", "issue": "Blockinfile module : Documentation mistake\n##### Issue Type:\n- Documentation Report\n##### Plugin Name:\n\nBlockinfile module\n##### Ansible Version:\n\n```\n$ ansible --version\nansible 2.0.1.0\n  config file = /data/ansible/ansible.cfg\n  configured module search path = Default w/o overrides\n```\n##### Ansible Configuration:\n\nn.a.\n##### Environment:\n\nn.a\n##### Summary:\n\nWrong spacing in official documentation\n##### Steps To Reproduce:\n\n```\nhttps://docs.ansible.com/ansible/blockinfile_module.html\nChapter Example, last example using < with_items >\n```\n##### Expected Results:\n\nExpression with_items: should be level with blockinfile:\n##### Actual Results:\n\n```\nExample does not work as expression < with_items: > is not level with  <blockinfile:> \n```\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2014, 2015 YAEGASHI Takeshi <yaegashi@debian.org>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nimport re\nimport os\nimport tempfile\n\nDOCUMENTATION = \"\"\"\n---\nmodule: blockinfile\nauthor:\n    - 'YAEGASHI Takeshi (@yaegashi)'\nextends_documentation_fragment:\n    - files\n    - validate\nshort_description: Insert/update/remove a text block\n                   surrounded by marker lines.\nversion_added: '2.0'\ndescription:\n  - This module will insert/update/remove a block of multi-line text\n    surrounded by customizable marker lines.\nnotes:\n  - This module supports check mode.\n  - When using 'with_*' loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.\noptions:\n  dest:\n    aliases: [ name, destfile ]\n    required: true\n    description:\n      - The file to modify.\n  state:\n    required: false\n    choices: [ present, absent ]\n    default: present\n    description:\n      - Whether the block should be there or not.\n  marker:\n    required: false\n    default: '# {mark} ANSIBLE MANAGED BLOCK'\n    description:\n      - The marker line template.\n        \"{mark}\" will be replaced with \"BEGIN\" or \"END\".\n  block:\n    aliases: [ content ]\n    required: false\n    default: ''\n    description:\n      - The text to insert inside the marker lines.\n        If it's missing or an empty string,\n        the block will be removed as if C(state) were specified to C(absent).\n  insertafter:\n    required: false\n    default: EOF\n    description:\n      - If specified, the block will be inserted after the last match of\n        specified regular expression. A special value is available; C(EOF) for\n        inserting the block at the end of the file.  If specified regular\n        expresion has no matches, C(EOF) will be used instead.\n    choices: [ 'EOF', '*regex*' ]\n  insertbefore:\n    required: false\n    default: None\n    description:\n      - If specified, the block will be inserted before the last match of\n        specified regular expression. A special value is available; C(BOF) for\n        inserting the block at the beginning of the file.  If specified regular\n        expresion has no matches, the block will be inserted at the end of the\n        file.\n    choices: [ 'BOF', '*regex*' ]\n  create:\n    required: false\n    default: 'no'\n    choices: [ 'yes', 'no' ]\n    description:\n      - Create a new file if it doesn't exist.\n  backup:\n    required: false\n    default: 'no'\n    choices: [ 'yes', 'no' ]\n    description:\n      - Create a backup file including the timestamp information so you can\n        get the original file back if you somehow clobbered it incorrectly.\n  follow:\n    required: false\n    default: \"no\"\n    choices: [ \"yes\", \"no\" ]\n    description:\n      - 'This flag indicates that filesystem links, if they exist, should be followed.'\n    version_added: \"2.1\"\n\"\"\"\n\nEXAMPLES = r\"\"\"\n- name: insert/update \"Match User\" configuation block in /etc/ssh/sshd_config\n  blockinfile:\n    dest: /etc/ssh/sshd_config\n    block: |\n      Match User ansible-agent\n      PasswordAuthentication no\n\n- name: insert/update eth0 configuration stanza in /etc/network/interfaces\n        (it might be better to copy files into /etc/network/interfaces.d/)\n  blockinfile:\n    dest: /etc/network/interfaces\n    block: |\n      iface eth0 inet static\n          address 192.168.0.1\n          netmask 255.255.255.0\n\n- name: insert/update HTML surrounded by custom markers after <body> line\n  blockinfile:\n    dest: /var/www/html/index.html\n    marker: \"<!-- {mark} ANSIBLE MANAGED BLOCK -->\"\n    insertafter: \"<body>\"\n    content: |\n      <h1>Welcome to {{ansible_hostname}}</h1>\n      <p>Last updated on {{ansible_date_time.iso8601}}</p>\n\n- name: remove HTML as well as surrounding markers\n  blockinfile:\n    dest: /var/www/html/index.html\n    marker: \"<!-- {mark} ANSIBLE MANAGED BLOCK -->\"\n    content: \"\"\n\n- name: insert/update \"Match User\" configuation block in /etc/ssh/sshd_config\n  blockinfile:\n    dest: /etc/hosts\n    block: |\n      {{item.name}} {{item.ip}}\n    marker: \"# {mark} ANSIBLE MANAGED BLOCK {{item.name}}\"\n    with_items:\n        - { name: host1, ip: 10.10.1.10 }\n        - { name: host2, ip: 10.10.1.11 }\n        - { name: host3, ip: 10.10.1.12 }\n\"\"\"\n\n\ndef write_changes(module, contents, dest):\n\n    tmpfd, tmpfile = tempfile.mkstemp()\n    f = os.fdopen(tmpfd, 'wb')\n    f.write(contents)\n    f.close()\n\n    validate = module.params.get('validate', None)\n    valid = not validate\n    if validate:\n        if \"%s\" not in validate:\n            module.fail_json(msg=\"validate must contain %%s: %s\" % (validate))\n        (rc, out, err) = module.run_command(validate % tmpfile)\n        valid = rc == 0\n        if rc != 0:\n            module.fail_json(msg='failed to validate: '\n                                 'rc:%s error:%s' % (rc, err))\n    if valid:\n        module.atomic_move(tmpfile, dest)\n\n\ndef check_file_attrs(module, changed, message):\n\n    file_args = module.load_file_common_arguments(module.params)\n    if module.set_file_attributes_if_different(file_args, False):\n\n        if changed:\n            message += \" and \"\n        changed = True\n        message += \"ownership, perms or SE linux context changed\"\n\n    return message, changed\n\n\ndef main():\n    module = AnsibleModule(\n        argument_spec=dict(\n            dest=dict(required=True, aliases=['name', 'destfile']),\n            state=dict(default='present', choices=['absent', 'present']),\n            marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'),\n            block=dict(default='', type='str', aliases=['content']),\n            insertafter=dict(default=None),\n            insertbefore=dict(default=None),\n            create=dict(default=False, type='bool'),\n            backup=dict(default=False, type='bool'),\n            validate=dict(default=None, type='str'),\n        ),\n        mutually_exclusive=[['insertbefore', 'insertafter']],\n        add_file_common_args=True,\n        supports_check_mode=True\n    )\n\n    params = module.params\n    dest = os.path.expanduser(params['dest'])\n    if module.boolean(params.get('follow', None)):\n        dest = os.path.realpath(dest)\n\n    if os.path.isdir(dest):\n        module.fail_json(rc=256,\n                         msg='Destination %s is a directory !' % dest)\n\n    if not os.path.exists(dest):\n        if not module.boolean(params['create']):\n            module.fail_json(rc=257,\n                             msg='Destination %s does not exist !' % dest)\n        original = None\n        lines = []\n    else:\n        f = open(dest, 'rb')\n        original = f.read()\n        f.close()\n        lines = original.splitlines()\n\n    insertbefore = params['insertbefore']\n    insertafter = params['insertafter']\n    block = params['block']\n    marker = params['marker']\n    present = params['state'] == 'present'\n\n    if insertbefore is None and insertafter is None:\n        insertafter = 'EOF'\n\n    if insertafter not in (None, 'EOF'):\n        insertre = re.compile(insertafter)\n    elif insertbefore not in (None, 'BOF'):\n        insertre = re.compile(insertbefore)\n    else:\n        insertre = None\n\n    marker0 = re.sub(r'{mark}', 'BEGIN', marker)\n    marker1 = re.sub(r'{mark}', 'END', marker)\n    if present and block:\n        # Escape seqeuences like '\\n' need to be handled in Ansible 1.x\n        if ANSIBLE_VERSION.startswith('1.'):\n            block = re.sub('', block, '')\n        blocklines = [marker0] + block.splitlines() + [marker1]\n    else:\n        blocklines = []\n\n    n0 = n1 = None\n    for i, line in enumerate(lines):\n        if line.startswith(marker0):\n            n0 = i\n        if line.startswith(marker1):\n            n1 = i\n\n    if None in (n0, n1):\n        n0 = None\n        if insertre is not None:\n            for i, line in enumerate(lines):\n                if insertre.search(line):\n                    n0 = i\n            if n0 is None:\n                n0 = len(lines)\n            elif insertafter is not None:\n                n0 += 1\n        elif insertbefore is not None:\n            n0 = 0           # insertbefore=BOF\n        else:\n            n0 = len(lines)  # insertafter=EOF\n    elif n0 < n1:\n        lines[n0:n1+1] = []\n    else:\n        lines[n1:n0+1] = []\n        n0 = n1\n\n    lines[n0:n0] = blocklines\n\n    if lines:\n        result = '\\n'.join(lines)+'\\n'\n    else:\n        result = ''\n    if original == result:\n        msg = ''\n        changed = False\n    elif original is None:\n        msg = 'File created'\n        changed = True\n    elif not blocklines:\n        msg = 'Block removed'\n        changed = True\n    else:\n        msg = 'Block inserted'\n        changed = True\n\n    if changed and not module.check_mode:\n        if module.boolean(params['backup']) and os.path.exists(dest):\n            module.backup_local(dest)\n        write_changes(module, result, dest)\n\n    msg, changed = check_file_attrs(module, changed, msg)\n    module.exit_json(changed=changed, msg=msg)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.splitter import *\nif __name__ == '__main__':\n    main()\n", "path": "files/blockinfile.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2014, 2015 YAEGASHI Takeshi <yaegashi@debian.org>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nimport re\nimport os\nimport tempfile\n\nDOCUMENTATION = \"\"\"\n---\nmodule: blockinfile\nauthor:\n    - 'YAEGASHI Takeshi (@yaegashi)'\nextends_documentation_fragment:\n    - files\n    - validate\nshort_description: Insert/update/remove a text block\n                   surrounded by marker lines.\nversion_added: '2.0'\ndescription:\n  - This module will insert/update/remove a block of multi-line text\n    surrounded by customizable marker lines.\nnotes:\n  - This module supports check mode.\n  - When using 'with_' loops be aware that if you do not set a unique mark the block will be overwritten on each iteration.\noptions:\n  dest:\n    aliases: [ name, destfile ]\n    required: true\n    description:\n      - The file to modify.\n  state:\n    required: false\n    choices: [ present, absent ]\n    default: present\n    description:\n      - Whether the block should be there or not.\n  marker:\n    required: false\n    default: '# {mark} ANSIBLE MANAGED BLOCK'\n    description:\n      - The marker line template.\n        \"{mark}\" will be replaced with \"BEGIN\" or \"END\".\n  block:\n    aliases: [ content ]\n    required: false\n    default: ''\n    description:\n      - The text to insert inside the marker lines.\n        If it's missing or an empty string,\n        the block will be removed as if C(state) were specified to C(absent).\n  insertafter:\n    required: false\n    default: EOF\n    description:\n      - If specified, the block will be inserted after the last match of\n        specified regular expression. A special value is available; C(EOF) for\n        inserting the block at the end of the file.  If specified regular\n        expresion has no matches, C(EOF) will be used instead.\n    choices: [ 'EOF', '*regex*' ]\n  insertbefore:\n    required: false\n    default: None\n    description:\n      - If specified, the block will be inserted before the last match of\n        specified regular expression. A special value is available; C(BOF) for\n        inserting the block at the beginning of the file.  If specified regular\n        expresion has no matches, the block will be inserted at the end of the\n        file.\n    choices: [ 'BOF', '*regex*' ]\n  create:\n    required: false\n    default: 'no'\n    choices: [ 'yes', 'no' ]\n    description:\n      - Create a new file if it doesn't exist.\n  backup:\n    required: false\n    default: 'no'\n    choices: [ 'yes', 'no' ]\n    description:\n      - Create a backup file including the timestamp information so you can\n        get the original file back if you somehow clobbered it incorrectly.\n  follow:\n    required: false\n    default: \"no\"\n    choices: [ \"yes\", \"no\" ]\n    description:\n      - 'This flag indicates that filesystem links, if they exist, should be followed.'\n    version_added: \"2.1\"\n\"\"\"\n\nEXAMPLES = r\"\"\"\n- name: insert/update \"Match User\" configuation block in /etc/ssh/sshd_config\n  blockinfile:\n    dest: /etc/ssh/sshd_config\n    block: |\n      Match User ansible-agent\n      PasswordAuthentication no\n\n- name: insert/update eth0 configuration stanza in /etc/network/interfaces\n        (it might be better to copy files into /etc/network/interfaces.d/)\n  blockinfile:\n    dest: /etc/network/interfaces\n    block: |\n      iface eth0 inet static\n          address 192.168.0.1\n          netmask 255.255.255.0\n\n- name: insert/update HTML surrounded by custom markers after <body> line\n  blockinfile:\n    dest: /var/www/html/index.html\n    marker: \"<!-- {mark} ANSIBLE MANAGED BLOCK -->\"\n    insertafter: \"<body>\"\n    content: |\n      <h1>Welcome to {{ansible_hostname}}</h1>\n      <p>Last updated on {{ansible_date_time.iso8601}}</p>\n\n- name: remove HTML as well as surrounding markers\n  blockinfile:\n    dest: /var/www/html/index.html\n    marker: \"<!-- {mark} ANSIBLE MANAGED BLOCK -->\"\n    content: \"\"\n\n- name: insert/update \"Match User\" configuation block in /etc/ssh/sshd_config\n  blockinfile:\n    dest: /etc/hosts\n    block: |\n      {{item.name}} {{item.ip}}\n    marker: \"# {mark} ANSIBLE MANAGED BLOCK {{item.name}}\"\n  with_items:\n      - { name: host1, ip: 10.10.1.10 }\n      - { name: host2, ip: 10.10.1.11 }\n      - { name: host3, ip: 10.10.1.12 }\n\"\"\"\n\n\ndef write_changes(module, contents, dest):\n\n    tmpfd, tmpfile = tempfile.mkstemp()\n    f = os.fdopen(tmpfd, 'wb')\n    f.write(contents)\n    f.close()\n\n    validate = module.params.get('validate', None)\n    valid = not validate\n    if validate:\n        if \"%s\" not in validate:\n            module.fail_json(msg=\"validate must contain %%s: %s\" % (validate))\n        (rc, out, err) = module.run_command(validate % tmpfile)\n        valid = rc == 0\n        if rc != 0:\n            module.fail_json(msg='failed to validate: '\n                                 'rc:%s error:%s' % (rc, err))\n    if valid:\n        module.atomic_move(tmpfile, dest)\n\n\ndef check_file_attrs(module, changed, message):\n\n    file_args = module.load_file_common_arguments(module.params)\n    if module.set_file_attributes_if_different(file_args, False):\n\n        if changed:\n            message += \" and \"\n        changed = True\n        message += \"ownership, perms or SE linux context changed\"\n\n    return message, changed\n\n\ndef main():\n    module = AnsibleModule(\n        argument_spec=dict(\n            dest=dict(required=True, aliases=['name', 'destfile']),\n            state=dict(default='present', choices=['absent', 'present']),\n            marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'),\n            block=dict(default='', type='str', aliases=['content']),\n            insertafter=dict(default=None),\n            insertbefore=dict(default=None),\n            create=dict(default=False, type='bool'),\n            backup=dict(default=False, type='bool'),\n            validate=dict(default=None, type='str'),\n        ),\n        mutually_exclusive=[['insertbefore', 'insertafter']],\n        add_file_common_args=True,\n        supports_check_mode=True\n    )\n\n    params = module.params\n    dest = os.path.expanduser(params['dest'])\n    if module.boolean(params.get('follow', None)):\n        dest = os.path.realpath(dest)\n\n    if os.path.isdir(dest):\n        module.fail_json(rc=256,\n                         msg='Destination %s is a directory !' % dest)\n\n    if not os.path.exists(dest):\n        if not module.boolean(params['create']):\n            module.fail_json(rc=257,\n                             msg='Destination %s does not exist !' % dest)\n        original = None\n        lines = []\n    else:\n        f = open(dest, 'rb')\n        original = f.read()\n        f.close()\n        lines = original.splitlines()\n\n    insertbefore = params['insertbefore']\n    insertafter = params['insertafter']\n    block = params['block']\n    marker = params['marker']\n    present = params['state'] == 'present'\n\n    if insertbefore is None and insertafter is None:\n        insertafter = 'EOF'\n\n    if insertafter not in (None, 'EOF'):\n        insertre = re.compile(insertafter)\n    elif insertbefore not in (None, 'BOF'):\n        insertre = re.compile(insertbefore)\n    else:\n        insertre = None\n\n    marker0 = re.sub(r'{mark}', 'BEGIN', marker)\n    marker1 = re.sub(r'{mark}', 'END', marker)\n    if present and block:\n        # Escape seqeuences like '\\n' need to be handled in Ansible 1.x\n        if ANSIBLE_VERSION.startswith('1.'):\n            block = re.sub('', block, '')\n        blocklines = [marker0] + block.splitlines() + [marker1]\n    else:\n        blocklines = []\n\n    n0 = n1 = None\n    for i, line in enumerate(lines):\n        if line.startswith(marker0):\n            n0 = i\n        if line.startswith(marker1):\n            n1 = i\n\n    if None in (n0, n1):\n        n0 = None\n        if insertre is not None:\n            for i, line in enumerate(lines):\n                if insertre.search(line):\n                    n0 = i\n            if n0 is None:\n                n0 = len(lines)\n            elif insertafter is not None:\n                n0 += 1\n        elif insertbefore is not None:\n            n0 = 0           # insertbefore=BOF\n        else:\n            n0 = len(lines)  # insertafter=EOF\n    elif n0 < n1:\n        lines[n0:n1+1] = []\n    else:\n        lines[n1:n0+1] = []\n        n0 = n1\n\n    lines[n0:n0] = blocklines\n\n    if lines:\n        result = '\\n'.join(lines)+'\\n'\n    else:\n        result = ''\n    if original == result:\n        msg = ''\n        changed = False\n    elif original is None:\n        msg = 'File created'\n        changed = True\n    elif not blocklines:\n        msg = 'Block removed'\n        changed = True\n    else:\n        msg = 'Block inserted'\n        changed = True\n\n    if changed and not module.check_mode:\n        if module.boolean(params['backup']) and os.path.exists(dest):\n            module.backup_local(dest)\n        write_changes(module, result, dest)\n\n    msg, changed = check_file_attrs(module, changed, msg)\n    module.exit_json(changed=changed, msg=msg)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.splitter import *\nif __name__ == '__main__':\n    main()\n", "path": "files/blockinfile.py"}]}