We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Slack does not escape/encode ampersand (&) correctly
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### COMPONENT NAME

Slack module
##### ANSIBLE VERSION

<!--- Paste verbatim output from “ansible --version” between quotes below -->

```
ansible 2.1.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

N/A
##### OS / ENVIRONMENT

<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->

Mac OS X 10.11.4 (but probably N/A)
##### SUMMARY

<!--- Explain the problem briefly -->

Whenever the `msg` or a `text` inside `attachments` contains an ampersand (`&`), the Slack module fails with `HTTP Error 400: Bad Request`.
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

Simply try to send a slack notification and make sure there is a `"&"` somewhere in the `msg` parameter (or inside a `attachments[].text`).

<!--- Paste example playbooks or commands between quotes below -->

```
- name: Send slack test message
  hosts: MyHost
  tasks:
    - slack:
        token: "xxx/yyy/zzz"
        channel: "#my-channel"
        username: "Ansible"
        msg: "one & two"
```

<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS

<!--- What did you expect to happen when running the steps above? -->

You'd expect to correctly receive the slack notification containing `"one & two"` as the message.
##### ACTUAL RESULTS

<!--- What actually happened? If possible run with high verbosity (-vvvv) -->

The module fails.

<!--- Paste verbatim command output between quotes below -->

```
fatal: [MyHost]: FAILED! => {"changed": false, "failed": true, "invocation": {"module_args": {"attachments": null, "channel": "#my-channel", "color": "normal", "domain": null, "icon_emoji": null, "icon_url": "http://www.ansible.com/favicon.ico", "link_names": 1, "msg": "one & two", "parse": null, "token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "username": "Ansible", "validate_certs": true}, "module_name": "slack"}, "msg": " failed to send payload={\"username\": \"Ansible\", \"text\": \"one & two\", \"link_names\": 1, \"channel\": \"#my-channel\", \"icon_url\": \"http://www.ansible.com/favicon.ico\"} to https://hooks.slack.com/services/[obscured]: HTTP Error 400: Bad Request"}
```
##### Temporary workaround

I found that when I manually replace all `&` with `%26` (URL escaped ampersand), I receive the correct notification. For people wanting a temporary fix: `- text: "{{ originalText | replace('&', '%26') }}"`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `notification/slack.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # (c) 2015, Stefan Berggren <nsg@nsg.cc>
5 # (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>
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 DOCUMENTATION = """
23 module: slack
24 short_description: Send Slack notifications
25 description:
26     - The M(slack) module sends notifications to U(http://slack.com) via the Incoming WebHook integration
27 version_added: "1.6"
28 author: "Ramon de la Fuente (@ramondelafuente)"
29 options:
30   domain:
31     description:
32       - Slack (sub)domain for your environment without protocol. (i.e.
33         C(future500.slack.com)) In 1.8 and beyond, this is deprecated and may
34         be ignored.  See token documentation for information.
35     required: false
36     default: None
37   token:
38     description:
39       - Slack integration token.  This authenticates you to the slack service.
40         Prior to 1.8, a token looked like C(3Ffe373sfhRE6y42Fg3rvf4GlK).  In
41         1.8 and above, ansible adapts to the new slack API where tokens look
42         like C(G922VJP24/D921DW937/3Ffe373sfhRE6y42Fg3rvf4GlK).  If tokens
43         are in the new format then slack will ignore any value of domain.  If
44         the token is in the old format the domain is required.  Ansible has no
45         control of when slack will get rid of the old API.  When slack does
46         that the old format will stop working.
47     required: true
48   msg:
49     description:
50       - Message to send.
51     required: false
52     default: None
53   channel:
54     description:
55       - Channel to send the message to. If absent, the message goes to the channel selected for the I(token).
56     required: false
57     default: None
58   username:
59     description:
60       - This is the sender of the message.
61     required: false
62     default: "Ansible"
63   icon_url:
64     description:
65       - Url for the message sender's icon (default C(https://www.ansible.com/favicon.ico))
66     required: false
67   icon_emoji:
68     description:
69       - Emoji for the message sender. See Slack documentation for options.
70         (if I(icon_emoji) is set, I(icon_url) will not be used)
71     required: false
72     default: None
73   link_names:
74     description:
75       - Automatically create links for channels and usernames in I(msg).
76     required: false
77     default: 1
78     choices:
79       - 1
80       - 0
81   parse:
82     description:
83       - Setting for the message parser at Slack
84     required: false
85     default: None
86     choices:
87       - 'full'
88       - 'none'
89   validate_certs:
90     description:
91       - If C(no), SSL certificates will not be validated. This should only be used
92         on personally controlled sites using self-signed certificates.
93     required: false
94     default: 'yes'
95     choices:
96       - 'yes'
97       - 'no'
98   color:
99     version_added: "2.0"
100     description:
101       - Allow text to use default colors - use the default of 'normal' to not send a custom color bar at the start of the message
102     required: false
103     default: 'normal'
104     choices:
105       - 'normal'
106       - 'good'
107       - 'warning'
108       - 'danger'
109   attachments:
110     description:
111       - Define a list of attachments. This list mirrors the Slack JSON API. For more information, see https://api.slack.com/docs/attachments
112     required: false
113     default: None
114 """
115 
116 EXAMPLES = """
117 - name: Send notification message via Slack
118   local_action:
119     module: slack
120     token: thetoken/generatedby/slack
121     msg: "{{ inventory_hostname }} completed"
122 
123 - name: Send notification message via Slack all options
124   local_action:
125     module: slack
126     token: thetoken/generatedby/slack
127     msg: "{{ inventory_hostname }} completed"
128     channel: "#ansible"
129     username: "Ansible on {{ inventory_hostname }}"
130     icon_url: "http://www.example.com/some-image-file.png"
131     link_names: 0
132     parse: 'none'
133 
134 - name: insert a color bar in front of the message for visibility purposes and use the default webhook icon and name configured in Slack
135   slack:
136     token: thetoken/generatedby/slack
137     msg: "{{ inventory_hostname }} is alive!"
138     color: good
139     username: ""
140     icon_url: ""
141 
142 - name: Use the attachments API
143   slack:
144     token: thetoken/generatedby/slack
145     attachments:
146       - text: "Display my system load on host A and B"
147         color: "#ff00dd"
148         title: "System load"
149         fields:
150           - title: "System A"
151             value: "load average: 0,74, 0,66, 0,63"
152             short: "true"
153           - title: "System B"
154             value: "load average: 5,16, 4,64, 2,43"
155             short: "true"
156 
157 - name: Send notification message via Slack (deprecated API using domian)
158   local_action:
159     module: slack
160     domain: future500.slack.com
161     token: thetokengeneratedbyslack
162     msg: "{{ inventory_hostname }} completed"
163 
164 """
165 
166 OLD_SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'
167 SLACK_INCOMING_WEBHOOK = 'https://hooks.slack.com/services/%s'
168 
169 def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):
170     payload = {}
171     if color == "normal" and text is not None:
172         payload = dict(text=text)
173     elif text is not None:
174         # With a custom color we have to set the message as attachment, and explicitely turn markdown parsing on for it.
175         payload = dict(attachments=[dict(text=text, color=color, mrkdwn_in=["text"])])
176     if channel is not None:
177         if (channel[0] == '#') or (channel[0] == '@'):
178             payload['channel'] = channel
179         else:
180             payload['channel'] = '#'+channel
181     if username is not None:
182         payload['username'] = username
183     if icon_emoji is not None:
184         payload['icon_emoji'] = icon_emoji
185     else:
186         payload['icon_url'] = icon_url
187     if link_names is not None:
188         payload['link_names'] = link_names
189     if parse is not None:
190         payload['parse'] = parse
191 
192     if attachments is not None:
193         if 'attachments' not in payload:
194             payload['attachments'] = []
195 
196     if attachments is not None:
197         for attachment in attachments:
198             if 'fallback' not in attachment:
199                 attachment['fallback'] = attachment['text']
200             payload['attachments'].append(attachment)
201 
202     payload="payload=" + module.jsonify(payload)
203     return payload
204 
205 def do_notify_slack(module, domain, token, payload):
206     if token.count('/') >= 2:
207         # New style token
208         slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (token)
209     else:
210         if not domain:
211             module.fail_json(msg="Slack has updated its webhook API.  You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook")
212         slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)
213 
214     response, info = fetch_url(module, slack_incoming_webhook, data=payload)
215     if info['status'] != 200:
216         obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')
217         module.fail_json(msg=" failed to send %s to %s: %s" % (payload, obscured_incoming_webhook, info['msg']))
218 
219 def main():
220     module = AnsibleModule(
221         argument_spec = dict(
222             domain      = dict(type='str', required=False, default=None),
223             token       = dict(type='str', required=True, no_log=True),
224             msg         = dict(type='str', required=False, default=None),
225             channel     = dict(type='str', default=None),
226             username    = dict(type='str', default='Ansible'),
227             icon_url    = dict(type='str', default='https://www.ansible.com/favicon.ico'),
228             icon_emoji  = dict(type='str', default=None),
229             link_names  = dict(type='int', default=1, choices=[0,1]),
230             parse       = dict(type='str', default=None, choices=['none', 'full']),
231             validate_certs = dict(default='yes', type='bool'),
232             color       = dict(type='str', default='normal', choices=['normal', 'good', 'warning', 'danger']),
233             attachments = dict(type='list', required=False, default=None)
234         )
235     )
236 
237     domain = module.params['domain']
238     token = module.params['token']
239     text = module.params['msg']
240     channel = module.params['channel']
241     username = module.params['username']
242     icon_url = module.params['icon_url']
243     icon_emoji = module.params['icon_emoji']
244     link_names = module.params['link_names']
245     parse = module.params['parse']
246     color = module.params['color']
247     attachments = module.params['attachments']
248 
249     payload = build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments)
250     do_notify_slack(module, domain, token, payload)
251 
252     module.exit_json(msg="OK")
253 
254 # import module snippets
255 from ansible.module_utils.basic import *
256 from ansible.module_utils.urls import *
257 
258 if __name__ == '__main__':
259     main()
260
```
--- 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/notification/slack.py b/notification/slack.py
--- a/notification/slack.py
+++ b/notification/slack.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
+# (c) 2016, René Moser <mail@renemoser.net>
 # (c) 2015, Stefan Berggren <nsg@nsg.cc>
 # (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>
 #
@@ -154,7 +155,7 @@
             value: "load average: 5,16, 4,64, 2,43"
             short: "true"
 
-- name: Send notification message via Slack (deprecated API using domian)
+- name: Send notification message via Slack (deprecated API using domain)
   local_action:
     module: slack
     domain: future500.slack.com
@@ -166,13 +167,27 @@
 OLD_SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'
 SLACK_INCOMING_WEBHOOK = 'https://hooks.slack.com/services/%s'
 
+# See https://api.slack.com/docs/message-formatting#how_to_escape_characters
+# Escaping quotes and apostrophe however is related to how Ansible handles them.
+html_escape_table = {
+    '&': "&amp;",
+    '>': "&gt;",
+    '<': "&lt;",
+    '"': "\"",
+    "'": "\'",
+}
+
+def html_escape(text):
+    '''Produce entities within text.'''
+    return "".join(html_escape_table.get(c,c) for c in text)
+
 def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):
     payload = {}
     if color == "normal" and text is not None:
-        payload = dict(text=text)
+        payload = dict(text=html_escape(text))
     elif text is not None:
         # With a custom color we have to set the message as attachment, and explicitely turn markdown parsing on for it.
-        payload = dict(attachments=[dict(text=text, color=color, mrkdwn_in=["text"])])
+        payload = dict(attachments=[dict(text=html_escape(text), color=color, mrkdwn_in=["text"])])
     if channel is not None:
         if (channel[0] == '#') or (channel[0] == '@'):
             payload['channel'] = channel
@@ -194,12 +209,24 @@
             payload['attachments'] = []
 
     if attachments is not None:
+        keys_to_escape = [
+            'title',
+            'text',
+            'author_name',
+            'pretext',
+            'fallback',
+        ]
         for attachment in attachments:
+            for key in keys_to_escape:
+                if key in attachment:
+                    attachment[key] = html_escape(attachment[key])
+
             if 'fallback' not in attachment:
                 attachment['fallback'] = attachment['text']
+
             payload['attachments'].append(attachment)
 
-    payload="payload=" + module.jsonify(payload)
+    payload=module.jsonify(payload)
     return payload
 
 def do_notify_slack(module, domain, token, payload):
@@ -211,7 +238,12 @@
             module.fail_json(msg="Slack has updated its webhook API.  You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook")
         slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)
 
-    response, info = fetch_url(module, slack_incoming_webhook, data=payload)
+    headers = {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+    }
+    response, info = fetch_url(module=module, url=slack_incoming_webhook, headers=headers, method='POST', data=payload)
+
     if info['status'] != 200:
         obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')
         module.fail_json(msg=" failed to send %s to %s: %s" % (payload, obscured_incoming_webhook, info['msg']))

{"golden_diff": "diff --git a/notification/slack.py b/notification/slack.py\n--- a/notification/slack.py\n+++ b/notification/slack.py\n@@ -1,6 +1,7 @@\n #!/usr/bin/python\n # -*- coding: utf-8 -*-\n \n+# (c) 2016, Ren\u00e9 Moser <mail@renemoser.net>\n # (c) 2015, Stefan Berggren <nsg@nsg.cc>\n # (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>\n #\n@@ -154,7 +155,7 @@\n             value: \"load average: 5,16, 4,64, 2,43\"\n             short: \"true\"\n \n-- name: Send notification message via Slack (deprecated API using domian)\n+- name: Send notification message via Slack (deprecated API using domain)\n   local_action:\n     module: slack\n     domain: future500.slack.com\n@@ -166,13 +167,27 @@\n OLD_SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'\n SLACK_INCOMING_WEBHOOK = 'https://hooks.slack.com/services/%s'\n \n+# See https://api.slack.com/docs/message-formatting#how_to_escape_characters\n+# Escaping quotes and apostrophe however is related to how Ansible handles them.\n+html_escape_table = {\n+    '&': \"&amp;\",\n+    '>': \"&gt;\",\n+    '<': \"&lt;\",\n+    '\"': \"\\\"\",\n+    \"'\": \"\\'\",\n+}\n+\n+def html_escape(text):\n+    '''Produce entities within text.'''\n+    return \"\".join(html_escape_table.get(c,c) for c in text)\n+\n def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):\n     payload = {}\n     if color == \"normal\" and text is not None:\n-        payload = dict(text=text)\n+        payload = dict(text=html_escape(text))\n     elif text is not None:\n         # With a custom color we have to set the message as attachment, and explicitely turn markdown parsing on for it.\n-        payload = dict(attachments=[dict(text=text, color=color, mrkdwn_in=[\"text\"])])\n+        payload = dict(attachments=[dict(text=html_escape(text), color=color, mrkdwn_in=[\"text\"])])\n     if channel is not None:\n         if (channel[0] == '#') or (channel[0] == '@'):\n             payload['channel'] = channel\n@@ -194,12 +209,24 @@\n             payload['attachments'] = []\n \n     if attachments is not None:\n+        keys_to_escape = [\n+            'title',\n+            'text',\n+            'author_name',\n+            'pretext',\n+            'fallback',\n+        ]\n         for attachment in attachments:\n+            for key in keys_to_escape:\n+                if key in attachment:\n+                    attachment[key] = html_escape(attachment[key])\n+\n             if 'fallback' not in attachment:\n                 attachment['fallback'] = attachment['text']\n+\n             payload['attachments'].append(attachment)\n \n-    payload=\"payload=\" + module.jsonify(payload)\n+    payload=module.jsonify(payload)\n     return payload\n \n def do_notify_slack(module, domain, token, payload):\n@@ -211,7 +238,12 @@\n             module.fail_json(msg=\"Slack has updated its webhook API.  You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook\")\n         slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)\n \n-    response, info = fetch_url(module, slack_incoming_webhook, data=payload)\n+    headers = {\n+        'Content-Type': 'application/json',\n+        'Accept': 'application/json',\n+    }\n+    response, info = fetch_url(module=module, url=slack_incoming_webhook, headers=headers, method='POST', data=payload)\n+\n     if info['status'] != 200:\n         obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')\n         module.fail_json(msg=\" failed to send %s to %s: %s\" % (payload, obscured_incoming_webhook, info['msg']))\n", "issue": "Slack does not escape/encode ampersand (&) correctly\n<!--- Verify first that your issue/request is not already reported in GitHub -->\n##### ISSUE TYPE\n\n<!--- Pick one below and delete the rest: -->\n- Bug Report\n##### COMPONENT NAME\n\nSlack module\n##### ANSIBLE VERSION\n\n<!--- Paste verbatim output from \u201cansible --version\u201d between quotes below -->\n\n```\nansible 2.1.1.0\n  config file = /etc/ansible/ansible.cfg\n  configured module search path = Default w/o overrides\n```\n##### CONFIGURATION\n\n<!---\nMention any settings you have changed/added/removed in ansible.cfg\n(or using the ANSIBLE_* environment variables).\n-->\n\nN/A\n##### OS / ENVIRONMENT\n\n<!---\nMention the OS you are running Ansible from, and the OS you are\nmanaging, or say \u201cN/A\u201d for anything that is not platform-specific.\n-->\n\nMac OS X 10.11.4 (but probably N/A)\n##### SUMMARY\n\n<!--- Explain the problem briefly -->\n\nWhenever the `msg` or a `text` inside `attachments` contains an ampersand (`&`), the Slack module fails with `HTTP Error 400: Bad Request`.\n##### STEPS TO REPRODUCE\n\n<!---\nFor bugs, show exactly how to reproduce the problem.\nFor new features, show how the feature would be used.\n-->\n\nSimply try to send a slack notification and make sure there is a `\"&\"` somewhere in the `msg` parameter (or inside a `attachments[].text`).\n\n<!--- Paste example playbooks or commands between quotes below -->\n\n```\n- name: Send slack test message\n  hosts: MyHost\n  tasks:\n    - slack:\n        token: \"xxx/yyy/zzz\"\n        channel: \"#my-channel\"\n        username: \"Ansible\"\n        msg: \"one & two\"\n```\n\n<!--- You can also paste gist.github.com links for larger files -->\n##### EXPECTED RESULTS\n\n<!--- What did you expect to happen when running the steps above? -->\n\nYou'd expect to correctly receive the slack notification containing `\"one & two\"` as the message.\n##### ACTUAL RESULTS\n\n<!--- What actually happened? If possible run with high verbosity (-vvvv) -->\n\nThe module fails.\n\n<!--- Paste verbatim command output between quotes below -->\n\n```\nfatal: [MyHost]: FAILED! => {\"changed\": false, \"failed\": true, \"invocation\": {\"module_args\": {\"attachments\": null, \"channel\": \"#my-channel\", \"color\": \"normal\", \"domain\": null, \"icon_emoji\": null, \"icon_url\": \"http://www.ansible.com/favicon.ico\", \"link_names\": 1, \"msg\": \"one & two\", \"parse\": null, \"token\": \"VALUE_SPECIFIED_IN_NO_LOG_PARAMETER\", \"username\": \"Ansible\", \"validate_certs\": true}, \"module_name\": \"slack\"}, \"msg\": \" failed to send payload={\\\"username\\\": \\\"Ansible\\\", \\\"text\\\": \\\"one & two\\\", \\\"link_names\\\": 1, \\\"channel\\\": \\\"#my-channel\\\", \\\"icon_url\\\": \\\"http://www.ansible.com/favicon.ico\\\"} to https://hooks.slack.com/services/[obscured]: HTTP Error 400: Bad Request\"}\n```\n##### Temporary workaround\n\nI found that when I manually replace all `&` with `%26` (URL escaped ampersand), I receive the correct notification. For people wanting a temporary fix: `- text: \"{{ originalText | replace('&', '%26') }}\"`\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2015, Stefan Berggren <nsg@nsg.cc>\n# (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>\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\nDOCUMENTATION = \"\"\"\nmodule: slack\nshort_description: Send Slack notifications\ndescription:\n    - The M(slack) module sends notifications to U(http://slack.com) via the Incoming WebHook integration\nversion_added: \"1.6\"\nauthor: \"Ramon de la Fuente (@ramondelafuente)\"\noptions:\n  domain:\n    description:\n      - Slack (sub)domain for your environment without protocol. (i.e.\n        C(future500.slack.com)) In 1.8 and beyond, this is deprecated and may\n        be ignored.  See token documentation for information.\n    required: false\n    default: None\n  token:\n    description:\n      - Slack integration token.  This authenticates you to the slack service.\n        Prior to 1.8, a token looked like C(3Ffe373sfhRE6y42Fg3rvf4GlK).  In\n        1.8 and above, ansible adapts to the new slack API where tokens look\n        like C(G922VJP24/D921DW937/3Ffe373sfhRE6y42Fg3rvf4GlK).  If tokens\n        are in the new format then slack will ignore any value of domain.  If\n        the token is in the old format the domain is required.  Ansible has no\n        control of when slack will get rid of the old API.  When slack does\n        that the old format will stop working.\n    required: true\n  msg:\n    description:\n      - Message to send.\n    required: false\n    default: None\n  channel:\n    description:\n      - Channel to send the message to. If absent, the message goes to the channel selected for the I(token).\n    required: false\n    default: None\n  username:\n    description:\n      - This is the sender of the message.\n    required: false\n    default: \"Ansible\"\n  icon_url:\n    description:\n      - Url for the message sender's icon (default C(https://www.ansible.com/favicon.ico))\n    required: false\n  icon_emoji:\n    description:\n      - Emoji for the message sender. See Slack documentation for options.\n        (if I(icon_emoji) is set, I(icon_url) will not be used)\n    required: false\n    default: None\n  link_names:\n    description:\n      - Automatically create links for channels and usernames in I(msg).\n    required: false\n    default: 1\n    choices:\n      - 1\n      - 0\n  parse:\n    description:\n      - Setting for the message parser at Slack\n    required: false\n    default: None\n    choices:\n      - 'full'\n      - 'none'\n  validate_certs:\n    description:\n      - If C(no), SSL certificates will not be validated. This should only be used\n        on personally controlled sites using self-signed certificates.\n    required: false\n    default: 'yes'\n    choices:\n      - 'yes'\n      - 'no'\n  color:\n    version_added: \"2.0\"\n    description:\n      - Allow text to use default colors - use the default of 'normal' to not send a custom color bar at the start of the message\n    required: false\n    default: 'normal'\n    choices:\n      - 'normal'\n      - 'good'\n      - 'warning'\n      - 'danger'\n  attachments:\n    description:\n      - Define a list of attachments. This list mirrors the Slack JSON API. For more information, see https://api.slack.com/docs/attachments\n    required: false\n    default: None\n\"\"\"\n\nEXAMPLES = \"\"\"\n- name: Send notification message via Slack\n  local_action:\n    module: slack\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} completed\"\n\n- name: Send notification message via Slack all options\n  local_action:\n    module: slack\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} completed\"\n    channel: \"#ansible\"\n    username: \"Ansible on {{ inventory_hostname }}\"\n    icon_url: \"http://www.example.com/some-image-file.png\"\n    link_names: 0\n    parse: 'none'\n\n- name: insert a color bar in front of the message for visibility purposes and use the default webhook icon and name configured in Slack\n  slack:\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} is alive!\"\n    color: good\n    username: \"\"\n    icon_url: \"\"\n\n- name: Use the attachments API\n  slack:\n    token: thetoken/generatedby/slack\n    attachments:\n      - text: \"Display my system load on host A and B\"\n        color: \"#ff00dd\"\n        title: \"System load\"\n        fields:\n          - title: \"System A\"\n            value: \"load average: 0,74, 0,66, 0,63\"\n            short: \"true\"\n          - title: \"System B\"\n            value: \"load average: 5,16, 4,64, 2,43\"\n            short: \"true\"\n\n- name: Send notification message via Slack (deprecated API using domian)\n  local_action:\n    module: slack\n    domain: future500.slack.com\n    token: thetokengeneratedbyslack\n    msg: \"{{ inventory_hostname }} completed\"\n\n\"\"\"\n\nOLD_SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'\nSLACK_INCOMING_WEBHOOK = 'https://hooks.slack.com/services/%s'\n\ndef build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):\n    payload = {}\n    if color == \"normal\" and text is not None:\n        payload = dict(text=text)\n    elif text is not None:\n        # With a custom color we have to set the message as attachment, and explicitely turn markdown parsing on for it.\n        payload = dict(attachments=[dict(text=text, color=color, mrkdwn_in=[\"text\"])])\n    if channel is not None:\n        if (channel[0] == '#') or (channel[0] == '@'):\n            payload['channel'] = channel\n        else:\n            payload['channel'] = '#'+channel\n    if username is not None:\n        payload['username'] = username\n    if icon_emoji is not None:\n        payload['icon_emoji'] = icon_emoji\n    else:\n        payload['icon_url'] = icon_url\n    if link_names is not None:\n        payload['link_names'] = link_names\n    if parse is not None:\n        payload['parse'] = parse\n\n    if attachments is not None:\n        if 'attachments' not in payload:\n            payload['attachments'] = []\n\n    if attachments is not None:\n        for attachment in attachments:\n            if 'fallback' not in attachment:\n                attachment['fallback'] = attachment['text']\n            payload['attachments'].append(attachment)\n\n    payload=\"payload=\" + module.jsonify(payload)\n    return payload\n\ndef do_notify_slack(module, domain, token, payload):\n    if token.count('/') >= 2:\n        # New style token\n        slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (token)\n    else:\n        if not domain:\n            module.fail_json(msg=\"Slack has updated its webhook API.  You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook\")\n        slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)\n\n    response, info = fetch_url(module, slack_incoming_webhook, data=payload)\n    if info['status'] != 200:\n        obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')\n        module.fail_json(msg=\" failed to send %s to %s: %s\" % (payload, obscured_incoming_webhook, info['msg']))\n\ndef main():\n    module = AnsibleModule(\n        argument_spec = dict(\n            domain      = dict(type='str', required=False, default=None),\n            token       = dict(type='str', required=True, no_log=True),\n            msg         = dict(type='str', required=False, default=None),\n            channel     = dict(type='str', default=None),\n            username    = dict(type='str', default='Ansible'),\n            icon_url    = dict(type='str', default='https://www.ansible.com/favicon.ico'),\n            icon_emoji  = dict(type='str', default=None),\n            link_names  = dict(type='int', default=1, choices=[0,1]),\n            parse       = dict(type='str', default=None, choices=['none', 'full']),\n            validate_certs = dict(default='yes', type='bool'),\n            color       = dict(type='str', default='normal', choices=['normal', 'good', 'warning', 'danger']),\n            attachments = dict(type='list', required=False, default=None)\n        )\n    )\n\n    domain = module.params['domain']\n    token = module.params['token']\n    text = module.params['msg']\n    channel = module.params['channel']\n    username = module.params['username']\n    icon_url = module.params['icon_url']\n    icon_emoji = module.params['icon_emoji']\n    link_names = module.params['link_names']\n    parse = module.params['parse']\n    color = module.params['color']\n    attachments = module.params['attachments']\n\n    payload = build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments)\n    do_notify_slack(module, domain, token, payload)\n\n    module.exit_json(msg=\"OK\")\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.urls import *\n\nif __name__ == '__main__':\n    main()\n", "path": "notification/slack.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2016, Ren\u00e9 Moser <mail@renemoser.net>\n# (c) 2015, Stefan Berggren <nsg@nsg.cc>\n# (c) 2014, Ramon de la Fuente <ramon@delafuente.nl>\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\nDOCUMENTATION = \"\"\"\nmodule: slack\nshort_description: Send Slack notifications\ndescription:\n    - The M(slack) module sends notifications to U(http://slack.com) via the Incoming WebHook integration\nversion_added: \"1.6\"\nauthor: \"Ramon de la Fuente (@ramondelafuente)\"\noptions:\n  domain:\n    description:\n      - Slack (sub)domain for your environment without protocol. (i.e.\n        C(future500.slack.com)) In 1.8 and beyond, this is deprecated and may\n        be ignored.  See token documentation for information.\n    required: false\n    default: None\n  token:\n    description:\n      - Slack integration token.  This authenticates you to the slack service.\n        Prior to 1.8, a token looked like C(3Ffe373sfhRE6y42Fg3rvf4GlK).  In\n        1.8 and above, ansible adapts to the new slack API where tokens look\n        like C(G922VJP24/D921DW937/3Ffe373sfhRE6y42Fg3rvf4GlK).  If tokens\n        are in the new format then slack will ignore any value of domain.  If\n        the token is in the old format the domain is required.  Ansible has no\n        control of when slack will get rid of the old API.  When slack does\n        that the old format will stop working.\n    required: true\n  msg:\n    description:\n      - Message to send.\n    required: false\n    default: None\n  channel:\n    description:\n      - Channel to send the message to. If absent, the message goes to the channel selected for the I(token).\n    required: false\n    default: None\n  username:\n    description:\n      - This is the sender of the message.\n    required: false\n    default: \"Ansible\"\n  icon_url:\n    description:\n      - Url for the message sender's icon (default C(https://www.ansible.com/favicon.ico))\n    required: false\n  icon_emoji:\n    description:\n      - Emoji for the message sender. See Slack documentation for options.\n        (if I(icon_emoji) is set, I(icon_url) will not be used)\n    required: false\n    default: None\n  link_names:\n    description:\n      - Automatically create links for channels and usernames in I(msg).\n    required: false\n    default: 1\n    choices:\n      - 1\n      - 0\n  parse:\n    description:\n      - Setting for the message parser at Slack\n    required: false\n    default: None\n    choices:\n      - 'full'\n      - 'none'\n  validate_certs:\n    description:\n      - If C(no), SSL certificates will not be validated. This should only be used\n        on personally controlled sites using self-signed certificates.\n    required: false\n    default: 'yes'\n    choices:\n      - 'yes'\n      - 'no'\n  color:\n    version_added: \"2.0\"\n    description:\n      - Allow text to use default colors - use the default of 'normal' to not send a custom color bar at the start of the message\n    required: false\n    default: 'normal'\n    choices:\n      - 'normal'\n      - 'good'\n      - 'warning'\n      - 'danger'\n  attachments:\n    description:\n      - Define a list of attachments. This list mirrors the Slack JSON API. For more information, see https://api.slack.com/docs/attachments\n    required: false\n    default: None\n\"\"\"\n\nEXAMPLES = \"\"\"\n- name: Send notification message via Slack\n  local_action:\n    module: slack\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} completed\"\n\n- name: Send notification message via Slack all options\n  local_action:\n    module: slack\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} completed\"\n    channel: \"#ansible\"\n    username: \"Ansible on {{ inventory_hostname }}\"\n    icon_url: \"http://www.example.com/some-image-file.png\"\n    link_names: 0\n    parse: 'none'\n\n- name: insert a color bar in front of the message for visibility purposes and use the default webhook icon and name configured in Slack\n  slack:\n    token: thetoken/generatedby/slack\n    msg: \"{{ inventory_hostname }} is alive!\"\n    color: good\n    username: \"\"\n    icon_url: \"\"\n\n- name: Use the attachments API\n  slack:\n    token: thetoken/generatedby/slack\n    attachments:\n      - text: \"Display my system load on host A and B\"\n        color: \"#ff00dd\"\n        title: \"System load\"\n        fields:\n          - title: \"System A\"\n            value: \"load average: 0,74, 0,66, 0,63\"\n            short: \"true\"\n          - title: \"System B\"\n            value: \"load average: 5,16, 4,64, 2,43\"\n            short: \"true\"\n\n- name: Send notification message via Slack (deprecated API using domain)\n  local_action:\n    module: slack\n    domain: future500.slack.com\n    token: thetokengeneratedbyslack\n    msg: \"{{ inventory_hostname }} completed\"\n\n\"\"\"\n\nOLD_SLACK_INCOMING_WEBHOOK = 'https://%s/services/hooks/incoming-webhook?token=%s'\nSLACK_INCOMING_WEBHOOK = 'https://hooks.slack.com/services/%s'\n\n# See https://api.slack.com/docs/message-formatting#how_to_escape_characters\n# Escaping quotes and apostrophe however is related to how Ansible handles them.\nhtml_escape_table = {\n    '&': \"&amp;\",\n    '>': \"&gt;\",\n    '<': \"&lt;\",\n    '\"': \"\\\"\",\n    \"'\": \"\\'\",\n}\n\ndef html_escape(text):\n    '''Produce entities within text.'''\n    return \"\".join(html_escape_table.get(c,c) for c in text)\n\ndef build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):\n    payload = {}\n    if color == \"normal\" and text is not None:\n        payload = dict(text=html_escape(text))\n    elif text is not None:\n        # With a custom color we have to set the message as attachment, and explicitely turn markdown parsing on for it.\n        payload = dict(attachments=[dict(text=html_escape(text), color=color, mrkdwn_in=[\"text\"])])\n    if channel is not None:\n        if (channel[0] == '#') or (channel[0] == '@'):\n            payload['channel'] = channel\n        else:\n            payload['channel'] = '#'+channel\n    if username is not None:\n        payload['username'] = username\n    if icon_emoji is not None:\n        payload['icon_emoji'] = icon_emoji\n    else:\n        payload['icon_url'] = icon_url\n    if link_names is not None:\n        payload['link_names'] = link_names\n    if parse is not None:\n        payload['parse'] = parse\n\n    if attachments is not None:\n        if 'attachments' not in payload:\n            payload['attachments'] = []\n\n    if attachments is not None:\n        keys_to_escape = [\n            'title',\n            'text',\n            'author_name',\n            'pretext',\n            'fallback',\n        ]\n        for attachment in attachments:\n            for key in keys_to_escape:\n                if key in attachment:\n                    attachment[key] = html_escape(attachment[key])\n\n            if 'fallback' not in attachment:\n                attachment['fallback'] = attachment['text']\n\n            payload['attachments'].append(attachment)\n\n    payload=module.jsonify(payload)\n    return payload\n\ndef do_notify_slack(module, domain, token, payload):\n    if token.count('/') >= 2:\n        # New style token\n        slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (token)\n    else:\n        if not domain:\n            module.fail_json(msg=\"Slack has updated its webhook API.  You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook\")\n        slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)\n\n    headers = {\n        'Content-Type': 'application/json',\n        'Accept': 'application/json',\n    }\n    response, info = fetch_url(module=module, url=slack_incoming_webhook, headers=headers, method='POST', data=payload)\n\n    if info['status'] != 200:\n        obscured_incoming_webhook = SLACK_INCOMING_WEBHOOK % ('[obscured]')\n        module.fail_json(msg=\" failed to send %s to %s: %s\" % (payload, obscured_incoming_webhook, info['msg']))\n\ndef main():\n    module = AnsibleModule(\n        argument_spec = dict(\n            domain      = dict(type='str', required=False, default=None),\n            token       = dict(type='str', required=True, no_log=True),\n            msg         = dict(type='str', required=False, default=None),\n            channel     = dict(type='str', default=None),\n            username    = dict(type='str', default='Ansible'),\n            icon_url    = dict(type='str', default='https://www.ansible.com/favicon.ico'),\n            icon_emoji  = dict(type='str', default=None),\n            link_names  = dict(type='int', default=1, choices=[0,1]),\n            parse       = dict(type='str', default=None, choices=['none', 'full']),\n            validate_certs = dict(default='yes', type='bool'),\n            color       = dict(type='str', default='normal', choices=['normal', 'good', 'warning', 'danger']),\n            attachments = dict(type='list', required=False, default=None)\n        )\n    )\n\n    domain = module.params['domain']\n    token = module.params['token']\n    text = module.params['msg']\n    channel = module.params['channel']\n    username = module.params['username']\n    icon_url = module.params['icon_url']\n    icon_emoji = module.params['icon_emoji']\n    link_names = module.params['link_names']\n    parse = module.params['parse']\n    color = module.params['color']\n    attachments = module.params['attachments']\n\n    payload = build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments)\n    do_notify_slack(module, domain, token, payload)\n\n    module.exit_json(msg=\"OK\")\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.urls import *\n\nif __name__ == '__main__':\n    main()\n", "path": "notification/slack.py"}]}