We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ini: Lock file when saving to prevent corruption
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
 - Bug Report / Feature Idea

##### COMPONENT NAME
ini_file

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.4.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/something/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC 4.8.4]
```

##### CONFIGURATION
DEFAULT_REMOTE_PORT(/etc/ansible/ansible.cfg) = 22
PERSISTENT_CONNECT_TIMEOUT(/etc/ansible/ansible.cfg) = 30

##### OS / ENVIRONMENT
Ubuntu Server 16.04 LTS, 64-bit

##### SUMMARY
When writing INI files from two instances to the same file and host it sometimes corrupts the file, resulting in wrong INI syntax (duplicates or a half section written to file).

##### STEPS TO REPRODUCE
Try to setup a test host and write to a INI file, execute the playbook/script few times at the same time.

##### EXPECTED RESULTS
I understand that it's a bad concept of potentially having the INI write at the same time, but with some deployment setup it could happen at any time you have scheduled tasks and manual tasks at the same time. It would be awesome to have a lock mechanism to the INI function.

##### ACTUAL RESULTS
Duplicates or weird syntax in the ini file.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/ansible/modules/files/ini_file.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # Copyright: (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
5 # Copyright: (c) 2015, Ales Nosek <anosek.nosek () gmail.com>
6 # Copyright: (c) 2017, Ansible Project
7 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8 
9 from __future__ import absolute_import, division, print_function
10 __metaclass__ = type
11 #
12 
13 ANSIBLE_METADATA = {'metadata_version': '1.1',
14                     'status': ['preview'],
15                     'supported_by': 'community'}
16 
17 DOCUMENTATION = '''
18 ---
19 module: ini_file
20 short_description: Tweak settings in INI files
21 extends_documentation_fragment: files
22 description:
23      - Manage (add, remove, change) individual settings in an INI-style file without having
24        to manage the file as a whole with, say, M(template) or M(assemble). Adds missing
25        sections if they don't exist.
26      - Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file.
27      - Since version 2.3, this module adds missing ending newlines to files to keep in line with the POSIX standard, even when
28        no other modifications need to be applied.
29 version_added: "0.9"
30 options:
31   path:
32     description:
33       - Path to the INI-style file; this file is created if required.
34       - Before 2.3 this option was only usable as I(dest).
35     aliases: [ dest ]
36     required: true
37   section:
38     description:
39       - Section name in INI file. This is added if C(state=present) automatically when
40         a single value is being set.
41       - If left empty or set to `null`, the I(option) will be placed before the first I(section).
42         Using `null` is also required if the config format does not support sections.
43     required: true
44   option:
45     description:
46       - If set (required for changing a I(value)), this is the name of the option.
47       - May be omitted if adding/removing a whole I(section).
48   value:
49     description:
50      - The string value to be associated with an I(option). May be omitted when removing an I(option).
51   backup:
52     description:
53       - Create a backup file including the timestamp information so you can get
54         the original file back if you somehow clobbered it incorrectly.
55     type: bool
56     default: 'no'
57   others:
58      description:
59        - All arguments accepted by the M(file) module also work here
60   state:
61      description:
62        - If set to C(absent) the option or section will be removed if present instead of created.
63      choices: [ absent, present ]
64      default: present
65   no_extra_spaces:
66      description:
67        - Do not insert spaces before and after '=' symbol
68      type: bool
69      default: 'no'
70      version_added: "2.1"
71   create:
72      description:
73        - If set to 'no', the module will fail if the file does not already exist.
74          By default it will create the file if it is missing.
75      type: bool
76      default: 'yes'
77      version_added: "2.2"
78 notes:
79    - While it is possible to add an I(option) without specifying a I(value), this makes
80      no sense.
81    - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but
82      I(dest) still works as well.
83 author:
84     - Jan-Piet Mens (@jpmens)
85     - Ales Nosek (@noseka1)
86 '''
87 
88 EXAMPLES = '''
89 # Before 2.3, option 'dest' was used instead of 'path'
90 - name: Ensure "fav=lemonade is in section "[drinks]" in specified file
91   ini_file:
92     path: /etc/conf
93     section: drinks
94     option: fav
95     value: lemonade
96     mode: 0600
97     backup: yes
98 
99 - ini_file:
100     path: /etc/anotherconf
101     section: drinks
102     option: temperature
103     value: cold
104     backup: yes
105 '''
106 
107 import os
108 import re
109 
110 from ansible.module_utils.basic import AnsibleModule
111 
112 
113 def match_opt(option, line):
114     option = re.escape(option)
115     return re.match('( |\t)*%s( |\t)*=' % option, line) \
116         or re.match('#( |\t)*%s( |\t)*=' % option, line) \
117         or re.match(';( |\t)*%s( |\t)*=' % option, line)
118 
119 
120 def match_active_opt(option, line):
121     option = re.escape(option)
122     return re.match('( |\t)*%s( |\t)*=' % option, line)
123 
124 
125 def do_ini(module, filename, section=None, option=None, value=None,
126            state='present', backup=False, no_extra_spaces=False, create=True):
127 
128     diff = dict(
129         before='',
130         after='',
131         before_header='%s (content)' % filename,
132         after_header='%s (content)' % filename,
133     )
134 
135     if not os.path.exists(filename):
136         if not create:
137             module.fail_json(rc=257, msg='Destination %s does not exist !' % filename)
138         destpath = os.path.dirname(filename)
139         if not os.path.exists(destpath) and not module.check_mode:
140             os.makedirs(destpath)
141         ini_lines = []
142     else:
143         ini_file = open(filename, 'r')
144         try:
145             ini_lines = ini_file.readlines()
146         finally:
147             ini_file.close()
148 
149     if module._diff:
150         diff['before'] = ''.join(ini_lines)
151 
152     changed = False
153 
154     # ini file could be empty
155     if not ini_lines:
156         ini_lines.append('\n')
157 
158     # last line of file may not contain a trailing newline
159     if ini_lines[-1] == "" or ini_lines[-1][-1] != '\n':
160         ini_lines[-1] += '\n'
161         changed = True
162 
163     # append a fake section line to simplify the logic
164     ini_lines.append('[')
165 
166     within_section = not section
167     section_start = 0
168     msg = 'OK'
169     if no_extra_spaces:
170         assignment_format = '%s=%s\n'
171     else:
172         assignment_format = '%s = %s\n'
173 
174     for index, line in enumerate(ini_lines):
175         if line.startswith('[%s]' % section):
176             within_section = True
177             section_start = index
178         elif line.startswith('['):
179             if within_section:
180                 if state == 'present':
181                     # insert missing option line at the end of the section
182                     for i in range(index, 0, -1):
183                         # search backwards for previous non-blank or non-comment line
184                         if not re.match(r'^[ \t]*([#;].*)?$', ini_lines[i - 1]):
185                             ini_lines.insert(i, assignment_format % (option, value))
186                             msg = 'option added'
187                             changed = True
188                             break
189                 elif state == 'absent' and not option:
190                     # remove the entire section
191                     del ini_lines[section_start:index]
192                     msg = 'section removed'
193                     changed = True
194                 break
195         else:
196             if within_section and option:
197                 if state == 'present':
198                     # change the existing option line
199                     if match_opt(option, line):
200                         newline = assignment_format % (option, value)
201                         option_changed = ini_lines[index] != newline
202                         changed = changed or option_changed
203                         if option_changed:
204                             msg = 'option changed'
205                         ini_lines[index] = newline
206                         if option_changed:
207                             # remove all possible option occurrences from the rest of the section
208                             index = index + 1
209                             while index < len(ini_lines):
210                                 line = ini_lines[index]
211                                 if line.startswith('['):
212                                     break
213                                 if match_active_opt(option, line):
214                                     del ini_lines[index]
215                                 else:
216                                     index = index + 1
217                         break
218                 elif state == 'absent':
219                     # delete the existing line
220                     if match_active_opt(option, line):
221                         del ini_lines[index]
222                         changed = True
223                         msg = 'option changed'
224                         break
225 
226     # remove the fake section line
227     del ini_lines[-1:]
228 
229     if not within_section and option and state == 'present':
230         ini_lines.append('[%s]\n' % section)
231         ini_lines.append(assignment_format % (option, value))
232         changed = True
233         msg = 'section and option added'
234 
235     if module._diff:
236         diff['after'] = ''.join(ini_lines)
237 
238     backup_file = None
239     if changed and not module.check_mode:
240         if backup:
241             backup_file = module.backup_local(filename)
242         ini_file = open(filename, 'w')
243         try:
244             ini_file.writelines(ini_lines)
245         finally:
246             ini_file.close()
247 
248     return (changed, backup_file, diff, msg)
249 
250 
251 def main():
252 
253     module = AnsibleModule(
254         argument_spec=dict(
255             path=dict(type='path', required=True, aliases=['dest']),
256             section=dict(type='str', required=True),
257             option=dict(type='str'),
258             value=dict(type='str'),
259             backup=dict(type='bool', default=False),
260             state=dict(type='str', default='present', choices=['absent', 'present']),
261             no_extra_spaces=dict(type='bool', default=False),
262             create=dict(type='bool', default=True)
263         ),
264         add_file_common_args=True,
265         supports_check_mode=True,
266     )
267 
268     path = module.params['path']
269     section = module.params['section']
270     option = module.params['option']
271     value = module.params['value']
272     state = module.params['state']
273     backup = module.params['backup']
274     no_extra_spaces = module.params['no_extra_spaces']
275     create = module.params['create']
276 
277     (changed, backup_file, diff, msg) = do_ini(module, path, section, option, value, state, backup, no_extra_spaces, create)
278 
279     if not module.check_mode and os.path.exists(path):
280         file_args = module.load_file_common_arguments(module.params)
281         changed = module.set_fs_attributes_if_different(file_args, changed)
282 
283     results = dict(
284         changed=changed,
285         diff=diff,
286         msg=msg,
287         path=path,
288     )
289     if backup_file is not None:
290         results['backup_file'] = backup_file
291 
292     # Mission complete
293     module.exit_json(**results)
294 
295 if __name__ == '__main__':
296     main()
297
```
--- 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/lib/ansible/modules/files/ini_file.py b/lib/ansible/modules/files/ini_file.py
--- a/lib/ansible/modules/files/ini_file.py
+++ b/lib/ansible/modules/files/ini_file.py
@@ -106,6 +106,8 @@
 
 import os
 import re
+import tempfile
+import traceback
 
 from ansible.module_utils.basic import AnsibleModule
 
@@ -239,11 +241,20 @@
     if changed and not module.check_mode:
         if backup:
             backup_file = module.backup_local(filename)
-        ini_file = open(filename, 'w')
+
         try:
-            ini_file.writelines(ini_lines)
-        finally:
-            ini_file.close()
+            tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
+            f = os.fdopen(tmpfd, 'w')
+            f.writelines(ini_lines)
+            f.close()
+        except IOError:
+            module.fail_json(msg="Unable to create temporary file %s", traceback=traceback.format_exc())
+
+        try:
+            module.atomic_move(tmpfile, filename)
+        except IOError:
+            module.ansible.fail_json(msg='Unable to move temporary \
+                                   file %s to %s, IOError' % (tmpfile, filename), traceback=traceback.format_exc())
 
     return (changed, backup_file, diff, msg)

{"golden_diff": "diff --git a/lib/ansible/modules/files/ini_file.py b/lib/ansible/modules/files/ini_file.py\n--- a/lib/ansible/modules/files/ini_file.py\n+++ b/lib/ansible/modules/files/ini_file.py\n@@ -106,6 +106,8 @@\n \n import os\n import re\n+import tempfile\n+import traceback\n \n from ansible.module_utils.basic import AnsibleModule\n \n@@ -239,11 +241,20 @@\n     if changed and not module.check_mode:\n         if backup:\n             backup_file = module.backup_local(filename)\n-        ini_file = open(filename, 'w')\n+\n         try:\n-            ini_file.writelines(ini_lines)\n-        finally:\n-            ini_file.close()\n+            tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)\n+            f = os.fdopen(tmpfd, 'w')\n+            f.writelines(ini_lines)\n+            f.close()\n+        except IOError:\n+            module.fail_json(msg=\"Unable to create temporary file %s\", traceback=traceback.format_exc())\n+\n+        try:\n+            module.atomic_move(tmpfile, filename)\n+        except IOError:\n+            module.ansible.fail_json(msg='Unable to move temporary \\\n+                                   file %s to %s, IOError' % (tmpfile, filename), traceback=traceback.format_exc())\n \n     return (changed, backup_file, diff, msg)\n", "issue": "ini: Lock file when saving to prevent corruption\n<!---\r\nVerify first that your issue/request is not already reported on GitHub.\r\nAlso test if the latest release, and master branch are affected too.\r\n-->\r\n\r\n##### ISSUE TYPE\r\n - Bug Report / Feature Idea\r\n\r\n##### COMPONENT NAME\r\nini_file\r\n\r\n##### ANSIBLE VERSION\r\n<!--- Paste verbatim output from \"ansible --version\" between quotes below -->\r\n```\r\nansible 2.4.2.0\r\n  config file = /etc/ansible/ansible.cfg\r\n  configured module search path = [u'/home/something/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']\r\n  ansible python module location = /usr/lib/python2.7/dist-packages/ansible\r\n  executable location = /usr/bin/ansible\r\n  python version = 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC 4.8.4]\r\n```\r\n\r\n##### CONFIGURATION\r\nDEFAULT_REMOTE_PORT(/etc/ansible/ansible.cfg) = 22\r\nPERSISTENT_CONNECT_TIMEOUT(/etc/ansible/ansible.cfg) = 30\r\n\r\n##### OS / ENVIRONMENT\r\nUbuntu Server 16.04 LTS, 64-bit\r\n\r\n##### SUMMARY\r\nWhen writing INI files from two instances to the same file and host it sometimes corrupts the file, resulting in wrong INI syntax (duplicates or a half section written to file).\r\n\r\n##### STEPS TO REPRODUCE\r\nTry to setup a test host and write to a INI file, execute the playbook/script few times at the same time.\r\n\r\n##### EXPECTED RESULTS\r\nI understand that it's a bad concept of potentially having the INI write at the same time, but with some deployment setup it could happen at any time you have scheduled tasks and manual tasks at the same time. It would be awesome to have a lock mechanism to the INI function.\r\n\r\n##### ACTUAL RESULTS\r\nDuplicates or weird syntax in the ini file.\r\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2012, Jan-Piet Mens <jpmens () gmail.com>\n# Copyright: (c) 2015, Ales Nosek <anosek.nosek () gmail.com>\n# Copyright: (c) 2017, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n#\n\nANSIBLE_METADATA = {'metadata_version': '1.1',\n                    'status': ['preview'],\n                    'supported_by': 'community'}\n\nDOCUMENTATION = '''\n---\nmodule: ini_file\nshort_description: Tweak settings in INI files\nextends_documentation_fragment: files\ndescription:\n     - Manage (add, remove, change) individual settings in an INI-style file without having\n       to manage the file as a whole with, say, M(template) or M(assemble). Adds missing\n       sections if they don't exist.\n     - Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file.\n     - Since version 2.3, this module adds missing ending newlines to files to keep in line with the POSIX standard, even when\n       no other modifications need to be applied.\nversion_added: \"0.9\"\noptions:\n  path:\n    description:\n      - Path to the INI-style file; this file is created if required.\n      - Before 2.3 this option was only usable as I(dest).\n    aliases: [ dest ]\n    required: true\n  section:\n    description:\n      - Section name in INI file. This is added if C(state=present) automatically when\n        a single value is being set.\n      - If left empty or set to `null`, the I(option) will be placed before the first I(section).\n        Using `null` is also required if the config format does not support sections.\n    required: true\n  option:\n    description:\n      - If set (required for changing a I(value)), this is the name of the option.\n      - May be omitted if adding/removing a whole I(section).\n  value:\n    description:\n     - The string value to be associated with an I(option). May be omitted when removing an I(option).\n  backup:\n    description:\n      - Create a backup file including the timestamp information so you can get\n        the original file back if you somehow clobbered it incorrectly.\n    type: bool\n    default: 'no'\n  others:\n     description:\n       - All arguments accepted by the M(file) module also work here\n  state:\n     description:\n       - If set to C(absent) the option or section will be removed if present instead of created.\n     choices: [ absent, present ]\n     default: present\n  no_extra_spaces:\n     description:\n       - Do not insert spaces before and after '=' symbol\n     type: bool\n     default: 'no'\n     version_added: \"2.1\"\n  create:\n     description:\n       - If set to 'no', the module will fail if the file does not already exist.\n         By default it will create the file if it is missing.\n     type: bool\n     default: 'yes'\n     version_added: \"2.2\"\nnotes:\n   - While it is possible to add an I(option) without specifying a I(value), this makes\n     no sense.\n   - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but\n     I(dest) still works as well.\nauthor:\n    - Jan-Piet Mens (@jpmens)\n    - Ales Nosek (@noseka1)\n'''\n\nEXAMPLES = '''\n# Before 2.3, option 'dest' was used instead of 'path'\n- name: Ensure \"fav=lemonade is in section \"[drinks]\" in specified file\n  ini_file:\n    path: /etc/conf\n    section: drinks\n    option: fav\n    value: lemonade\n    mode: 0600\n    backup: yes\n\n- ini_file:\n    path: /etc/anotherconf\n    section: drinks\n    option: temperature\n    value: cold\n    backup: yes\n'''\n\nimport os\nimport re\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\ndef match_opt(option, line):\n    option = re.escape(option)\n    return re.match('( |\\t)*%s( |\\t)*=' % option, line) \\\n        or re.match('#( |\\t)*%s( |\\t)*=' % option, line) \\\n        or re.match(';( |\\t)*%s( |\\t)*=' % option, line)\n\n\ndef match_active_opt(option, line):\n    option = re.escape(option)\n    return re.match('( |\\t)*%s( |\\t)*=' % option, line)\n\n\ndef do_ini(module, filename, section=None, option=None, value=None,\n           state='present', backup=False, no_extra_spaces=False, create=True):\n\n    diff = dict(\n        before='',\n        after='',\n        before_header='%s (content)' % filename,\n        after_header='%s (content)' % filename,\n    )\n\n    if not os.path.exists(filename):\n        if not create:\n            module.fail_json(rc=257, msg='Destination %s does not exist !' % filename)\n        destpath = os.path.dirname(filename)\n        if not os.path.exists(destpath) and not module.check_mode:\n            os.makedirs(destpath)\n        ini_lines = []\n    else:\n        ini_file = open(filename, 'r')\n        try:\n            ini_lines = ini_file.readlines()\n        finally:\n            ini_file.close()\n\n    if module._diff:\n        diff['before'] = ''.join(ini_lines)\n\n    changed = False\n\n    # ini file could be empty\n    if not ini_lines:\n        ini_lines.append('\\n')\n\n    # last line of file may not contain a trailing newline\n    if ini_lines[-1] == \"\" or ini_lines[-1][-1] != '\\n':\n        ini_lines[-1] += '\\n'\n        changed = True\n\n    # append a fake section line to simplify the logic\n    ini_lines.append('[')\n\n    within_section = not section\n    section_start = 0\n    msg = 'OK'\n    if no_extra_spaces:\n        assignment_format = '%s=%s\\n'\n    else:\n        assignment_format = '%s = %s\\n'\n\n    for index, line in enumerate(ini_lines):\n        if line.startswith('[%s]' % section):\n            within_section = True\n            section_start = index\n        elif line.startswith('['):\n            if within_section:\n                if state == 'present':\n                    # insert missing option line at the end of the section\n                    for i in range(index, 0, -1):\n                        # search backwards for previous non-blank or non-comment line\n                        if not re.match(r'^[ \\t]*([#;].*)?$', ini_lines[i - 1]):\n                            ini_lines.insert(i, assignment_format % (option, value))\n                            msg = 'option added'\n                            changed = True\n                            break\n                elif state == 'absent' and not option:\n                    # remove the entire section\n                    del ini_lines[section_start:index]\n                    msg = 'section removed'\n                    changed = True\n                break\n        else:\n            if within_section and option:\n                if state == 'present':\n                    # change the existing option line\n                    if match_opt(option, line):\n                        newline = assignment_format % (option, value)\n                        option_changed = ini_lines[index] != newline\n                        changed = changed or option_changed\n                        if option_changed:\n                            msg = 'option changed'\n                        ini_lines[index] = newline\n                        if option_changed:\n                            # remove all possible option occurrences from the rest of the section\n                            index = index + 1\n                            while index < len(ini_lines):\n                                line = ini_lines[index]\n                                if line.startswith('['):\n                                    break\n                                if match_active_opt(option, line):\n                                    del ini_lines[index]\n                                else:\n                                    index = index + 1\n                        break\n                elif state == 'absent':\n                    # delete the existing line\n                    if match_active_opt(option, line):\n                        del ini_lines[index]\n                        changed = True\n                        msg = 'option changed'\n                        break\n\n    # remove the fake section line\n    del ini_lines[-1:]\n\n    if not within_section and option and state == 'present':\n        ini_lines.append('[%s]\\n' % section)\n        ini_lines.append(assignment_format % (option, value))\n        changed = True\n        msg = 'section and option added'\n\n    if module._diff:\n        diff['after'] = ''.join(ini_lines)\n\n    backup_file = None\n    if changed and not module.check_mode:\n        if backup:\n            backup_file = module.backup_local(filename)\n        ini_file = open(filename, 'w')\n        try:\n            ini_file.writelines(ini_lines)\n        finally:\n            ini_file.close()\n\n    return (changed, backup_file, diff, msg)\n\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec=dict(\n            path=dict(type='path', required=True, aliases=['dest']),\n            section=dict(type='str', required=True),\n            option=dict(type='str'),\n            value=dict(type='str'),\n            backup=dict(type='bool', default=False),\n            state=dict(type='str', default='present', choices=['absent', 'present']),\n            no_extra_spaces=dict(type='bool', default=False),\n            create=dict(type='bool', default=True)\n        ),\n        add_file_common_args=True,\n        supports_check_mode=True,\n    )\n\n    path = module.params['path']\n    section = module.params['section']\n    option = module.params['option']\n    value = module.params['value']\n    state = module.params['state']\n    backup = module.params['backup']\n    no_extra_spaces = module.params['no_extra_spaces']\n    create = module.params['create']\n\n    (changed, backup_file, diff, msg) = do_ini(module, path, section, option, value, state, backup, no_extra_spaces, create)\n\n    if not module.check_mode and os.path.exists(path):\n        file_args = module.load_file_common_arguments(module.params)\n        changed = module.set_fs_attributes_if_different(file_args, changed)\n\n    results = dict(\n        changed=changed,\n        diff=diff,\n        msg=msg,\n        path=path,\n    )\n    if backup_file is not None:\n        results['backup_file'] = backup_file\n\n    # Mission complete\n    module.exit_json(**results)\n\nif __name__ == '__main__':\n    main()\n", "path": "lib/ansible/modules/files/ini_file.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2012, Jan-Piet Mens <jpmens () gmail.com>\n# Copyright: (c) 2015, Ales Nosek <anosek.nosek () gmail.com>\n# Copyright: (c) 2017, Ansible Project\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n#\n\nANSIBLE_METADATA = {'metadata_version': '1.1',\n                    'status': ['preview'],\n                    'supported_by': 'community'}\n\nDOCUMENTATION = '''\n---\nmodule: ini_file\nshort_description: Tweak settings in INI files\nextends_documentation_fragment: files\ndescription:\n     - Manage (add, remove, change) individual settings in an INI-style file without having\n       to manage the file as a whole with, say, M(template) or M(assemble). Adds missing\n       sections if they don't exist.\n     - Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file.\n     - Since version 2.3, this module adds missing ending newlines to files to keep in line with the POSIX standard, even when\n       no other modifications need to be applied.\nversion_added: \"0.9\"\noptions:\n  path:\n    description:\n      - Path to the INI-style file; this file is created if required.\n      - Before 2.3 this option was only usable as I(dest).\n    aliases: [ dest ]\n    required: true\n  section:\n    description:\n      - Section name in INI file. This is added if C(state=present) automatically when\n        a single value is being set.\n      - If left empty or set to `null`, the I(option) will be placed before the first I(section).\n        Using `null` is also required if the config format does not support sections.\n    required: true\n  option:\n    description:\n      - If set (required for changing a I(value)), this is the name of the option.\n      - May be omitted if adding/removing a whole I(section).\n  value:\n    description:\n     - The string value to be associated with an I(option). May be omitted when removing an I(option).\n  backup:\n    description:\n      - Create a backup file including the timestamp information so you can get\n        the original file back if you somehow clobbered it incorrectly.\n    type: bool\n    default: 'no'\n  others:\n     description:\n       - All arguments accepted by the M(file) module also work here\n  state:\n     description:\n       - If set to C(absent) the option or section will be removed if present instead of created.\n     choices: [ absent, present ]\n     default: present\n  no_extra_spaces:\n     description:\n       - Do not insert spaces before and after '=' symbol\n     type: bool\n     default: 'no'\n     version_added: \"2.1\"\n  create:\n     description:\n       - If set to 'no', the module will fail if the file does not already exist.\n         By default it will create the file if it is missing.\n     type: bool\n     default: 'yes'\n     version_added: \"2.2\"\nnotes:\n   - While it is possible to add an I(option) without specifying a I(value), this makes\n     no sense.\n   - As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but\n     I(dest) still works as well.\nauthor:\n    - Jan-Piet Mens (@jpmens)\n    - Ales Nosek (@noseka1)\n'''\n\nEXAMPLES = '''\n# Before 2.3, option 'dest' was used instead of 'path'\n- name: Ensure \"fav=lemonade is in section \"[drinks]\" in specified file\n  ini_file:\n    path: /etc/conf\n    section: drinks\n    option: fav\n    value: lemonade\n    mode: 0600\n    backup: yes\n\n- ini_file:\n    path: /etc/anotherconf\n    section: drinks\n    option: temperature\n    value: cold\n    backup: yes\n'''\n\nimport os\nimport re\nimport tempfile\nimport traceback\n\nfrom ansible.module_utils.basic import AnsibleModule\n\n\ndef match_opt(option, line):\n    option = re.escape(option)\n    return re.match('( |\\t)*%s( |\\t)*=' % option, line) \\\n        or re.match('#( |\\t)*%s( |\\t)*=' % option, line) \\\n        or re.match(';( |\\t)*%s( |\\t)*=' % option, line)\n\n\ndef match_active_opt(option, line):\n    option = re.escape(option)\n    return re.match('( |\\t)*%s( |\\t)*=' % option, line)\n\n\ndef do_ini(module, filename, section=None, option=None, value=None,\n           state='present', backup=False, no_extra_spaces=False, create=True):\n\n    diff = dict(\n        before='',\n        after='',\n        before_header='%s (content)' % filename,\n        after_header='%s (content)' % filename,\n    )\n\n    if not os.path.exists(filename):\n        if not create:\n            module.fail_json(rc=257, msg='Destination %s does not exist !' % filename)\n        destpath = os.path.dirname(filename)\n        if not os.path.exists(destpath) and not module.check_mode:\n            os.makedirs(destpath)\n        ini_lines = []\n    else:\n        ini_file = open(filename, 'r')\n        try:\n            ini_lines = ini_file.readlines()\n        finally:\n            ini_file.close()\n\n    if module._diff:\n        diff['before'] = ''.join(ini_lines)\n\n    changed = False\n\n    # ini file could be empty\n    if not ini_lines:\n        ini_lines.append('\\n')\n\n    # last line of file may not contain a trailing newline\n    if ini_lines[-1] == \"\" or ini_lines[-1][-1] != '\\n':\n        ini_lines[-1] += '\\n'\n        changed = True\n\n    # append a fake section line to simplify the logic\n    ini_lines.append('[')\n\n    within_section = not section\n    section_start = 0\n    msg = 'OK'\n    if no_extra_spaces:\n        assignment_format = '%s=%s\\n'\n    else:\n        assignment_format = '%s = %s\\n'\n\n    for index, line in enumerate(ini_lines):\n        if line.startswith('[%s]' % section):\n            within_section = True\n            section_start = index\n        elif line.startswith('['):\n            if within_section:\n                if state == 'present':\n                    # insert missing option line at the end of the section\n                    for i in range(index, 0, -1):\n                        # search backwards for previous non-blank or non-comment line\n                        if not re.match(r'^[ \\t]*([#;].*)?$', ini_lines[i - 1]):\n                            ini_lines.insert(i, assignment_format % (option, value))\n                            msg = 'option added'\n                            changed = True\n                            break\n                elif state == 'absent' and not option:\n                    # remove the entire section\n                    del ini_lines[section_start:index]\n                    msg = 'section removed'\n                    changed = True\n                break\n        else:\n            if within_section and option:\n                if state == 'present':\n                    # change the existing option line\n                    if match_opt(option, line):\n                        newline = assignment_format % (option, value)\n                        option_changed = ini_lines[index] != newline\n                        changed = changed or option_changed\n                        if option_changed:\n                            msg = 'option changed'\n                        ini_lines[index] = newline\n                        if option_changed:\n                            # remove all possible option occurrences from the rest of the section\n                            index = index + 1\n                            while index < len(ini_lines):\n                                line = ini_lines[index]\n                                if line.startswith('['):\n                                    break\n                                if match_active_opt(option, line):\n                                    del ini_lines[index]\n                                else:\n                                    index = index + 1\n                        break\n                elif state == 'absent':\n                    # delete the existing line\n                    if match_active_opt(option, line):\n                        del ini_lines[index]\n                        changed = True\n                        msg = 'option changed'\n                        break\n\n    # remove the fake section line\n    del ini_lines[-1:]\n\n    if not within_section and option and state == 'present':\n        ini_lines.append('[%s]\\n' % section)\n        ini_lines.append(assignment_format % (option, value))\n        changed = True\n        msg = 'section and option added'\n\n    if module._diff:\n        diff['after'] = ''.join(ini_lines)\n\n    backup_file = None\n    if changed and not module.check_mode:\n        if backup:\n            backup_file = module.backup_local(filename)\n\n        try:\n            tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)\n            f = os.fdopen(tmpfd, 'w')\n            f.writelines(ini_lines)\n            f.close()\n        except IOError:\n            module.fail_json(msg=\"Unable to create temporary file %s\", traceback=traceback.format_exc())\n\n        try:\n            module.atomic_move(tmpfile, filename)\n        except IOError:\n            module.ansible.fail_json(msg='Unable to move temporary \\\n                                   file %s to %s, IOError' % (tmpfile, filename), traceback=traceback.format_exc())\n\n    return (changed, backup_file, diff, msg)\n\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec=dict(\n            path=dict(type='path', required=True, aliases=['dest']),\n            section=dict(type='str', required=True),\n            option=dict(type='str'),\n            value=dict(type='str'),\n            backup=dict(type='bool', default=False),\n            state=dict(type='str', default='present', choices=['absent', 'present']),\n            no_extra_spaces=dict(type='bool', default=False),\n            create=dict(type='bool', default=True)\n        ),\n        add_file_common_args=True,\n        supports_check_mode=True,\n    )\n\n    path = module.params['path']\n    section = module.params['section']\n    option = module.params['option']\n    value = module.params['value']\n    state = module.params['state']\n    backup = module.params['backup']\n    no_extra_spaces = module.params['no_extra_spaces']\n    create = module.params['create']\n\n    (changed, backup_file, diff, msg) = do_ini(module, path, section, option, value, state, backup, no_extra_spaces, create)\n\n    if not module.check_mode and os.path.exists(path):\n        file_args = module.load_file_common_arguments(module.params)\n        changed = module.set_fs_attributes_if_different(file_args, changed)\n\n    results = dict(\n        changed=changed,\n        diff=diff,\n        msg=msg,\n        path=path,\n    )\n    if backup_file is not None:\n        results['backup_file'] = backup_file\n\n    # Mission complete\n    module.exit_json(**results)\n\nif __name__ == '__main__':\n    main()\n", "path": "lib/ansible/modules/files/ini_file.py"}]}