We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ini_file module: bracklets in key break idempotence
Issue Type: Bug Report
Ansible Version: ansible-playbook 2.0.0 (devel 5d645f3a8b) last updated 2015/11/13 13:27:13 (GMT +200)
Environment: Ubuntu 15.04
Summary: When using ini_file for modifying /etc/php5/fpm/pool.d/www.conf it will not recognize strings  which include "[" or "]" in key and append it again to the file. Using Ansible 1.9.4 this works just as expected.

Example: You want to set

```
php_admin_value[sendmail_path]: "/usr/sbin/sendmail -t -i -f an@onymo.us"
```

using this hash:

``` yaml
php_admin_value[sendmail_path]:
    value: '"/usr/sbin/sendmail -t -i -f an@onymo.us"'
```

On first run Ansible will append this string to the file just as expected. On second run it will append it again, even though it's already present. When removing the bracklets it will only be inserted once.

Removing the bracklets from value will work as expected (not changed one second run)

ini_file module: bracklets in key break idempotence
Issue Type: Bug Report
Ansible Version: ansible-playbook 2.0.0 (devel 5d645f3a8b) last updated 2015/11/13 13:27:13 (GMT +200)
Environment: Ubuntu 15.04
Summary: When using ini_file for modifying /etc/php5/fpm/pool.d/www.conf it will not recognize strings  which include "[" or "]" in key and append it again to the file. Using Ansible 1.9.4 this works just as expected.

Example: You want to set

```
php_admin_value[sendmail_path]: "/usr/sbin/sendmail -t -i -f an@onymo.us"
```

using this hash:

``` yaml
php_admin_value[sendmail_path]:
    value: '"/usr/sbin/sendmail -t -i -f an@onymo.us"'
```

On first run Ansible will append this string to the file just as expected. On second run it will append it again, even though it's already present. When removing the bracklets it will only be inserted once.

Removing the bracklets from value will work as expected (not changed one second run)


--- 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/ini_file.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
5 # (c) 2015, Ales Nosek <anosek.nosek () gmail.com>
6 #
7 # This file is part of Ansible
8 #
9 # Ansible is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # Ansible is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
21 #
22 
23 DOCUMENTATION = '''
24 ---
25 module: ini_file
26 short_description: Tweak settings in INI files
27 extends_documentation_fragment: files
28 description:
29      - Manage (add, remove, change) individual settings in an INI-style file without having
30        to manage the file as a whole with, say, M(template) or M(assemble). Adds missing
31        sections if they don't exist.
32      - Before version 2.0, comments are discarded when the source file is read, and therefore will not show up in the destination file.
33 version_added: "0.9"
34 options:
35   dest:
36     description:
37       - Path to the INI-style file; this file is created if required
38     required: true
39     default: null
40   section:
41     description:
42       - Section name in INI file. This is added if C(state=present) automatically when
43         a single value is being set.
44     required: true
45     default: null
46   option:
47     description:
48       - if set (required for changing a I(value)), this is the name of the option.
49       - May be omitted if adding/removing a whole I(section).
50     required: false
51     default: null
52   value:
53     description:
54      - the string value to be associated with an I(option). May be omitted when removing an I(option).
55     required: false
56     default: null
57   backup:
58     description:
59       - Create a backup file including the timestamp information so you can get
60         the original file back if you somehow clobbered it incorrectly.
61     required: false
62     default: "no"
63     choices: [ "yes", "no" ]
64   others:
65      description:
66        - all arguments accepted by the M(file) module also work here
67      required: false
68   state:
69      description:
70        - If set to C(absent) the option or section will be removed if present instead of created.
71      required: false
72      default: "present"
73      choices: [ "present", "absent" ]
74 notes:
75    - While it is possible to add an I(option) without specifying a I(value), this makes
76      no sense.
77    - A section named C(default) cannot be added by the module, but if it exists, individual
78      options within the section can be updated. (This is a limitation of Python's I(ConfigParser).)
79      Either use M(template) to create a base INI file with a C([default]) section, or use
80      M(lineinfile) to add the missing line.
81 requirements: [ ConfigParser ]
82 author:
83     - "Jan-Piet Mens (@jpmens)"
84     - "Ales Nosek (@noseka1)"
85 '''
86 
87 EXAMPLES = '''
88 # Ensure "fav=lemonade is in section "[drinks]" in specified file
89 - ini_file: dest=/etc/conf section=drinks option=fav value=lemonade mode=0600 backup=yes
90 
91 - ini_file: dest=/etc/anotherconf
92             section=drinks
93             option=temperature
94             value=cold
95             backup=yes
96 '''
97 
98 import ConfigParser
99 import sys
100 import os
101 
102 # ==============================================================
103 # do_ini
104 
105 def do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):
106 
107 
108     if not os.path.exists(filename):
109       try:
110         open(filename,'w').close()
111       except:
112         module.fail_json(msg="Destination file %s not writable" % filename)
113     ini_file = open(filename, 'r')
114     try:
115         ini_lines = ini_file.readlines()
116         # append a fake section line to simplify the logic
117         ini_lines.append('[')
118     finally:
119         ini_file.close()
120 
121     within_section = not section
122     section_start = 0
123     changed = False
124 
125     for index, line in enumerate(ini_lines):
126         if line.startswith('[%s]' % section):
127             within_section = True
128             section_start = index
129         elif line.startswith('['):
130             if within_section:
131                 if state == 'present':
132                     # insert missing option line at the end of the section
133                     ini_lines.insert(index, '%s = %s\n' % (option, value))
134                     changed = True
135                 elif state == 'absent' and not option:
136                     # remove the entire section
137                     del ini_lines[section_start:index]
138                     changed = True
139                 break
140         else:
141             if within_section and option:
142                 if state == 'present':
143                     # change the existing option line
144                     if re.match('%s *=' % option, line) \
145                             or re.match('# *%s *=' % option, line) \
146                             or re.match('; *%s *=' % option, line):
147                         newline = '%s = %s\n' % (option, value)
148                         changed = ini_lines[index] != newline
149                         ini_lines[index] = newline
150                         if changed:
151                             # remove all possible option occurences from the rest of the section
152                             index = index + 1
153                             while index < len(ini_lines):
154                                 line = ini_lines[index]
155                                 if line.startswith('['):
156                                     break
157                                 if re.match('%s *=' % option, line):
158                                     del ini_lines[index]
159                                 else:
160                                     index = index + 1
161                         break
162                 else:
163                     # comment out the existing option line
164                     if re.match('%s *=' % option, line):
165                         ini_lines[index] = '#%s' % ini_lines[index]
166                         changed = True
167                         break
168 
169     # remove the fake section line
170     del ini_lines[-1:]
171 
172     if not within_section and option and state == 'present':
173         ini_lines.append('[%s]\n' % section)
174         ini_lines.append('%s = %s\n' % (option, value))
175         changed = True
176 
177 
178     if changed and not module.check_mode:
179         if backup:
180             module.backup_local(filename)
181         ini_file = open(filename, 'w')
182         try:
183             ini_file.writelines(ini_lines)
184         finally:
185             ini_file.close()
186 
187     return changed
188 
189 # ==============================================================
190 # main
191 
192 def main():
193 
194     module = AnsibleModule(
195         argument_spec = dict(
196             dest = dict(required=True),
197             section = dict(required=True),
198             option = dict(required=False),
199             value = dict(required=False),
200             backup = dict(default='no', type='bool'),
201             state = dict(default='present', choices=['present', 'absent'])
202         ),
203         add_file_common_args = True,
204         supports_check_mode = True
205     )
206 
207     info = dict()
208 
209     dest = os.path.expanduser(module.params['dest'])
210     section = module.params['section']
211     option = module.params['option']
212     value = module.params['value']
213     state = module.params['state']
214     backup = module.params['backup']
215 
216     changed = do_ini(module, dest, section, option, value, state, backup)
217 
218     file_args = module.load_file_common_arguments(module.params)
219     changed = module.set_fs_attributes_if_different(file_args, changed)
220 
221     # Mission complete
222     module.exit_json(dest=dest, changed=changed, msg="OK")
223 
224 # import module snippets
225 from ansible.module_utils.basic import *
226 if __name__ == '__main__':
227     main()
228
```
--- 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/ini_file.py b/files/ini_file.py
--- a/files/ini_file.py
+++ b/files/ini_file.py
@@ -99,6 +99,22 @@
 import sys
 import os
 
+# ==============================================================
+# match_opt
+
+def match_opt(option, line):
+  option = re.escape(option)
+  return re.match('%s *=' % option, line) \
+    or re.match('# *%s *=' % option, line) \
+    or re.match('; *%s *=' % option, line)
+
+# ==============================================================
+# match_active_opt
+
+def match_active_opt(option, line):
+  option = re.escape(option)
+  return re.match('%s *=' % option, line)
+
 # ==============================================================
 # do_ini
 
@@ -141,9 +157,7 @@
             if within_section and option:
                 if state == 'present':
                     # change the existing option line
-                    if re.match('%s *=' % option, line) \
-                            or re.match('# *%s *=' % option, line) \
-                            or re.match('; *%s *=' % option, line):
+                    if match_opt(option, line):
                         newline = '%s = %s\n' % (option, value)
                         changed = ini_lines[index] != newline
                         ini_lines[index] = newline
@@ -154,14 +168,14 @@
                                 line = ini_lines[index]
                                 if line.startswith('['):
                                     break
-                                if re.match('%s *=' % option, line):
+                                if match_active_opt(option, line):
                                     del ini_lines[index]
                                 else:
                                     index = index + 1
                         break
                 else:
                     # comment out the existing option line
-                    if re.match('%s *=' % option, line):
+                    if match_active_opt(option, line):
                         ini_lines[index] = '#%s' % ini_lines[index]
                         changed = True
                         break

{"golden_diff": "diff --git a/files/ini_file.py b/files/ini_file.py\n--- a/files/ini_file.py\n+++ b/files/ini_file.py\n@@ -99,6 +99,22 @@\n import sys\n import os\n \n+# ==============================================================\n+# match_opt\n+\n+def match_opt(option, line):\n+  option = re.escape(option)\n+  return re.match('%s *=' % option, line) \\\n+    or re.match('# *%s *=' % option, line) \\\n+    or re.match('; *%s *=' % option, line)\n+\n+# ==============================================================\n+# match_active_opt\n+\n+def match_active_opt(option, line):\n+  option = re.escape(option)\n+  return re.match('%s *=' % option, line)\n+\n # ==============================================================\n # do_ini\n \n@@ -141,9 +157,7 @@\n             if within_section and option:\n                 if state == 'present':\n                     # change the existing option line\n-                    if re.match('%s *=' % option, line) \\\n-                            or re.match('# *%s *=' % option, line) \\\n-                            or re.match('; *%s *=' % option, line):\n+                    if match_opt(option, line):\n                         newline = '%s = %s\\n' % (option, value)\n                         changed = ini_lines[index] != newline\n                         ini_lines[index] = newline\n@@ -154,14 +168,14 @@\n                                 line = ini_lines[index]\n                                 if line.startswith('['):\n                                     break\n-                                if re.match('%s *=' % option, line):\n+                                if match_active_opt(option, line):\n                                     del ini_lines[index]\n                                 else:\n                                     index = index + 1\n                         break\n                 else:\n                     # comment out the existing option line\n-                    if re.match('%s *=' % option, line):\n+                    if match_active_opt(option, line):\n                         ini_lines[index] = '#%s' % ini_lines[index]\n                         changed = True\n                         break\n", "issue": "ini_file module: bracklets in key break idempotence\nIssue Type: Bug Report\nAnsible Version: ansible-playbook 2.0.0 (devel 5d645f3a8b) last updated 2015/11/13 13:27:13 (GMT +200)\nEnvironment: Ubuntu 15.04\nSummary: When using ini_file for modifying /etc/php5/fpm/pool.d/www.conf it will not recognize strings  which include \"[\" or \"]\" in key and append it again to the file. Using Ansible 1.9.4 this works just as expected.\n\nExample: You want to set\n\n```\nphp_admin_value[sendmail_path]: \"/usr/sbin/sendmail -t -i -f an@onymo.us\"\n```\n\nusing this hash:\n\n``` yaml\nphp_admin_value[sendmail_path]:\n    value: '\"/usr/sbin/sendmail -t -i -f an@onymo.us\"'\n```\n\nOn first run Ansible will append this string to the file just as expected. On second run it will append it again, even though it's already present. When removing the bracklets it will only be inserted once.\n\nRemoving the bracklets from value will work as expected (not changed one second run)\n\nini_file module: bracklets in key break idempotence\nIssue Type: Bug Report\nAnsible Version: ansible-playbook 2.0.0 (devel 5d645f3a8b) last updated 2015/11/13 13:27:13 (GMT +200)\nEnvironment: Ubuntu 15.04\nSummary: When using ini_file for modifying /etc/php5/fpm/pool.d/www.conf it will not recognize strings  which include \"[\" or \"]\" in key and append it again to the file. Using Ansible 1.9.4 this works just as expected.\n\nExample: You want to set\n\n```\nphp_admin_value[sendmail_path]: \"/usr/sbin/sendmail -t -i -f an@onymo.us\"\n```\n\nusing this hash:\n\n``` yaml\nphp_admin_value[sendmail_path]:\n    value: '\"/usr/sbin/sendmail -t -i -f an@onymo.us\"'\n```\n\nOn first run Ansible will append this string to the file just as expected. On second run it will append it again, even though it's already present. When removing the bracklets it will only be inserted once.\n\nRemoving the bracklets from value will work as expected (not changed one second run)\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>\n# (c) 2015, Ales Nosek <anosek.nosek () gmail.com>\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#\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.\nversion_added: \"0.9\"\noptions:\n  dest:\n    description:\n      - Path to the INI-style file; this file is created if required\n    required: true\n    default: null\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    required: true\n    default: null\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    required: false\n    default: null\n  value:\n    description:\n     - the string value to be associated with an I(option). May be omitted when removing an I(option).\n    required: false\n    default: null\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    required: false\n    default: \"no\"\n    choices: [ \"yes\", \"no\" ]\n  others:\n     description:\n       - all arguments accepted by the M(file) module also work here\n     required: false\n  state:\n     description:\n       - If set to C(absent) the option or section will be removed if present instead of created.\n     required: false\n     default: \"present\"\n     choices: [ \"present\", \"absent\" ]\nnotes:\n   - While it is possible to add an I(option) without specifying a I(value), this makes\n     no sense.\n   - A section named C(default) cannot be added by the module, but if it exists, individual\n     options within the section can be updated. (This is a limitation of Python's I(ConfigParser).)\n     Either use M(template) to create a base INI file with a C([default]) section, or use\n     M(lineinfile) to add the missing line.\nrequirements: [ ConfigParser ]\nauthor:\n    - \"Jan-Piet Mens (@jpmens)\"\n    - \"Ales Nosek (@noseka1)\"\n'''\n\nEXAMPLES = '''\n# Ensure \"fav=lemonade is in section \"[drinks]\" in specified file\n- ini_file: dest=/etc/conf section=drinks option=fav value=lemonade mode=0600 backup=yes\n\n- ini_file: dest=/etc/anotherconf\n            section=drinks\n            option=temperature\n            value=cold\n            backup=yes\n'''\n\nimport ConfigParser\nimport sys\nimport os\n\n# ==============================================================\n# do_ini\n\ndef do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):\n\n\n    if not os.path.exists(filename):\n      try:\n        open(filename,'w').close()\n      except:\n        module.fail_json(msg=\"Destination file %s not writable\" % filename)\n    ini_file = open(filename, 'r')\n    try:\n        ini_lines = ini_file.readlines()\n        # append a fake section line to simplify the logic\n        ini_lines.append('[')\n    finally:\n        ini_file.close()\n\n    within_section = not section\n    section_start = 0\n    changed = False\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                    ini_lines.insert(index, '%s = %s\\n' % (option, value))\n                    changed = True\n                elif state == 'absent' and not option:\n                    # remove the entire section\n                    del ini_lines[section_start:index]\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 re.match('%s *=' % option, line) \\\n                            or re.match('# *%s *=' % option, line) \\\n                            or re.match('; *%s *=' % option, line):\n                        newline = '%s = %s\\n' % (option, value)\n                        changed = ini_lines[index] != newline\n                        ini_lines[index] = newline\n                        if changed:\n                            # remove all possible option occurences 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 re.match('%s *=' % option, line):\n                                    del ini_lines[index]\n                                else:\n                                    index = index + 1\n                        break\n                else:\n                    # comment out the existing option line\n                    if re.match('%s *=' % option, line):\n                        ini_lines[index] = '#%s' % ini_lines[index]\n                        changed = True\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('%s = %s\\n' % (option, value))\n        changed = True\n\n\n    if changed and not module.check_mode:\n        if backup:\n            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\n\n# ==============================================================\n# main\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec = dict(\n            dest = dict(required=True),\n            section = dict(required=True),\n            option = dict(required=False),\n            value = dict(required=False),\n            backup = dict(default='no', type='bool'),\n            state = dict(default='present', choices=['present', 'absent'])\n        ),\n        add_file_common_args = True,\n        supports_check_mode = True\n    )\n\n    info = dict()\n\n    dest = os.path.expanduser(module.params['dest'])\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\n    changed = do_ini(module, dest, section, option, value, state, backup)\n\n    file_args = module.load_file_common_arguments(module.params)\n    changed = module.set_fs_attributes_if_different(file_args, changed)\n\n    # Mission complete\n    module.exit_json(dest=dest, changed=changed, msg=\"OK\")\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nif __name__ == '__main__':\n    main()\n", "path": "files/ini_file.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>\n# (c) 2015, Ales Nosek <anosek.nosek () gmail.com>\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#\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.\nversion_added: \"0.9\"\noptions:\n  dest:\n    description:\n      - Path to the INI-style file; this file is created if required\n    required: true\n    default: null\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    required: true\n    default: null\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    required: false\n    default: null\n  value:\n    description:\n     - the string value to be associated with an I(option). May be omitted when removing an I(option).\n    required: false\n    default: null\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    required: false\n    default: \"no\"\n    choices: [ \"yes\", \"no\" ]\n  others:\n     description:\n       - all arguments accepted by the M(file) module also work here\n     required: false\n  state:\n     description:\n       - If set to C(absent) the option or section will be removed if present instead of created.\n     required: false\n     default: \"present\"\n     choices: [ \"present\", \"absent\" ]\nnotes:\n   - While it is possible to add an I(option) without specifying a I(value), this makes\n     no sense.\n   - A section named C(default) cannot be added by the module, but if it exists, individual\n     options within the section can be updated. (This is a limitation of Python's I(ConfigParser).)\n     Either use M(template) to create a base INI file with a C([default]) section, or use\n     M(lineinfile) to add the missing line.\nrequirements: [ ConfigParser ]\nauthor:\n    - \"Jan-Piet Mens (@jpmens)\"\n    - \"Ales Nosek (@noseka1)\"\n'''\n\nEXAMPLES = '''\n# Ensure \"fav=lemonade is in section \"[drinks]\" in specified file\n- ini_file: dest=/etc/conf section=drinks option=fav value=lemonade mode=0600 backup=yes\n\n- ini_file: dest=/etc/anotherconf\n            section=drinks\n            option=temperature\n            value=cold\n            backup=yes\n'''\n\nimport ConfigParser\nimport sys\nimport os\n\n# ==============================================================\n# match_opt\n\ndef match_opt(option, line):\n  option = re.escape(option)\n  return re.match('%s *=' % option, line) \\\n    or re.match('# *%s *=' % option, line) \\\n    or re.match('; *%s *=' % option, line)\n\n# ==============================================================\n# match_active_opt\n\ndef match_active_opt(option, line):\n  option = re.escape(option)\n  return re.match('%s *=' % option, line)\n\n# ==============================================================\n# do_ini\n\ndef do_ini(module, filename, section=None, option=None, value=None, state='present', backup=False):\n\n\n    if not os.path.exists(filename):\n      try:\n        open(filename,'w').close()\n      except:\n        module.fail_json(msg=\"Destination file %s not writable\" % filename)\n    ini_file = open(filename, 'r')\n    try:\n        ini_lines = ini_file.readlines()\n        # append a fake section line to simplify the logic\n        ini_lines.append('[')\n    finally:\n        ini_file.close()\n\n    within_section = not section\n    section_start = 0\n    changed = False\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                    ini_lines.insert(index, '%s = %s\\n' % (option, value))\n                    changed = True\n                elif state == 'absent' and not option:\n                    # remove the entire section\n                    del ini_lines[section_start:index]\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 = '%s = %s\\n' % (option, value)\n                        changed = ini_lines[index] != newline\n                        ini_lines[index] = newline\n                        if changed:\n                            # remove all possible option occurences 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                else:\n                    # comment out the existing option line\n                    if match_active_opt(option, line):\n                        ini_lines[index] = '#%s' % ini_lines[index]\n                        changed = True\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('%s = %s\\n' % (option, value))\n        changed = True\n\n\n    if changed and not module.check_mode:\n        if backup:\n            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\n\n# ==============================================================\n# main\n\ndef main():\n\n    module = AnsibleModule(\n        argument_spec = dict(\n            dest = dict(required=True),\n            section = dict(required=True),\n            option = dict(required=False),\n            value = dict(required=False),\n            backup = dict(default='no', type='bool'),\n            state = dict(default='present', choices=['present', 'absent'])\n        ),\n        add_file_common_args = True,\n        supports_check_mode = True\n    )\n\n    info = dict()\n\n    dest = os.path.expanduser(module.params['dest'])\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\n    changed = do_ini(module, dest, section, option, value, state, backup)\n\n    file_args = module.load_file_common_arguments(module.params)\n    changed = module.set_fs_attributes_if_different(file_args, changed)\n\n    # Mission complete\n    module.exit_json(dest=dest, changed=changed, msg=\"OK\")\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nif __name__ == '__main__':\n    main()\n", "path": "files/ini_file.py"}]}