We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fix #4412: os_security_group_rule support numbered protocols
##### ISSUE TYPE
- Bugfix Pull Request
##### COMPONENT NAME

cloud/openstack/os_security_group_rule.py
##### ANSIBLE VERSION

```
ansible 2.1.1.0
  config file = 
  configured module search path = Default w/o overrides
```
##### SUMMARY

Bugfix correctly points out lack of VRRP support by way of numbered protocol.  This commit adds this functionality, including example usage in the documentation.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cloud/openstack/os_security_group_rule.py`
Content:
```
1 #!/usr/bin/python
2 
3 # Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
4 # Copyright (c) 2013, Benno Joy <benno@ansible.com>
5 #
6 # This module is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This software is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this software.  If not, see <http://www.gnu.org/licenses/>.
18 
19 try:
20     import shade
21     HAS_SHADE = True
22 except ImportError:
23     HAS_SHADE = False
24 
25 
26 DOCUMENTATION = '''
27 ---
28 module: os_security_group_rule
29 short_description: Add/Delete rule from an existing security group
30 extends_documentation_fragment: openstack
31 version_added: "2.0"
32 description:
33    - Add or Remove rule from an existing security group
34 options:
35    security_group:
36      description:
37         - Name or ID of the security group
38      required: true
39    protocol:
40       description:
41         - IP protocols TCP UDP ICMP 112 (VRRP)
42       choices: ['tcp', 'udp', 'icmp', 112, None]
43       default: None
44    port_range_min:
45       description:
46         - Starting port
47       required: false
48       default: None
49    port_range_max:
50       description:
51         - Ending port
52       required: false
53       default: None
54    remote_ip_prefix:
55       description:
56         - Source IP address(es) in CIDR notation (exclusive with remote_group)
57       required: false
58    remote_group:
59       description:
60         - Name or ID of the Security group to link (exclusive with
61           remote_ip_prefix)
62       required: false
63    ethertype:
64       description:
65         - Must be IPv4 or IPv6, and addresses represented in CIDR must
66           match the ingress or egress rules. Not all providers support IPv6.
67       choices: ['IPv4', 'IPv6']
68       default: IPv4
69    direction:
70      description:
71         - The direction in which the security group rule is applied. Not
72           all providers support egress.
73      choices: ['egress', 'ingress']
74      default: ingress
75    state:
76      description:
77        - Should the resource be present or absent.
78      choices: [present, absent]
79      default: present
80 requirements: ["shade"]
81 '''
82 
83 EXAMPLES = '''
84 # Create a security group rule
85 - os_security_group_rule:
86     cloud: mordred
87     security_group: foo
88     protocol: tcp
89     port_range_min: 80
90     port_range_max: 80
91     remote_ip_prefix: 0.0.0.0/0
92 
93 # Create a security group rule for ping
94 - os_security_group_rule:
95     cloud: mordred
96     security_group: foo
97     protocol: icmp
98     remote_ip_prefix: 0.0.0.0/0
99 
100 # Another way to create the ping rule
101 - os_security_group_rule:
102     cloud: mordred
103     security_group: foo
104     protocol: icmp
105     port_range_min: -1
106     port_range_max: -1
107     remote_ip_prefix: 0.0.0.0/0
108 
109 # Create a TCP rule covering all ports
110 - os_security_group_rule:
111     cloud: mordred
112     security_group: foo
113     protocol: tcp
114     port_range_min: 1
115     port_range_max: 65535
116     remote_ip_prefix: 0.0.0.0/0
117 
118 # Another way to create the TCP rule above (defaults to all ports)
119 - os_security_group_rule:
120     cloud: mordred
121     security_group: foo
122     protocol: tcp
123     remote_ip_prefix: 0.0.0.0/0
124 
125 # Create a rule for VRRP with numbered protocol 112
126 - os_security_group_rule:
127     security_group: loadbalancer_sg
128     protocol: 112
129     remote_group: loadbalancer-node_sg
130 '''
131 
132 RETURN = '''
133 id:
134   description: Unique rule UUID.
135   type: string
136 direction:
137   description: The direction in which the security group rule is applied.
138   type: string
139   sample: 'egress'
140 ethertype:
141   description: One of IPv4 or IPv6.
142   type: string
143   sample: 'IPv4'
144 port_range_min:
145   description: The minimum port number in the range that is matched by
146                the security group rule.
147   type: int
148   sample: 8000
149 port_range_max:
150   description: The maximum port number in the range that is matched by
151                the security group rule.
152   type: int
153   sample: 8000
154 protocol:
155   description: The protocol that is matched by the security group rule.
156   type: string
157   sample: 'tcp'
158 remote_ip_prefix:
159   description: The remote IP prefix to be associated with this security group rule.
160   type: string
161   sample: '0.0.0.0/0'
162 security_group_id:
163   description: The security group ID to associate with this security group rule.
164   type: string
165 '''
166 
167 
168 def _ports_match(protocol, module_min, module_max, rule_min, rule_max):
169     """
170     Capture the complex port matching logic.
171 
172     The port values coming in for the module might be -1 (for ICMP),
173     which will work only for Nova, but this is handled by shade. Likewise,
174     they might be None, which works for Neutron, but not Nova. This too is
175     handled by shade. Since shade will consistently return these port
176     values as None, we need to convert any -1 values input to the module
177     to None here for comparison.
178 
179     For TCP and UDP protocols, None values for both min and max are
180     represented as the range 1-65535 for Nova, but remain None for
181     Neutron. Shade returns the full range when Nova is the backend (since
182     that is how Nova stores them), and None values for Neutron. If None
183     values are input to the module for both values, then we need to adjust
184     for comparison.
185     """
186 
187     # Check if the user is supplying -1 for ICMP.
188     if protocol == 'icmp':
189         if module_min and int(module_min) == -1:
190             module_min = None
191         if module_max and int(module_max) == -1:
192             module_max = None
193 
194     # Check if user is supplying None values for full TCP/UDP port range.
195     if protocol in ['tcp', 'udp'] and module_min is None and module_max is None:
196         if (rule_min and int(rule_min) == 1
197                 and rule_max and int(rule_max) == 65535):
198             # (None, None) == (1, 65535)
199             return True
200 
201     # Sanity check to make sure we don't have type comparison issues.
202     if module_min:
203         module_min = int(module_min)
204     if module_max:
205         module_max = int(module_max)
206     if rule_min:
207         rule_min = int(rule_min)
208     if rule_max:
209         rule_max = int(rule_max)
210 
211     return module_min == rule_min and module_max == rule_max
212 
213 
214 def _find_matching_rule(module, secgroup, remotegroup):
215     """
216     Find a rule in the group that matches the module parameters.
217     :returns: The matching rule dict, or None if no matches.
218     """
219     protocol = module.params['protocol']
220     remote_ip_prefix = module.params['remote_ip_prefix']
221     ethertype = module.params['ethertype']
222     direction = module.params['direction']
223     remote_group_id = remotegroup['id']
224 
225     for rule in secgroup['security_group_rules']:
226         if (protocol == rule['protocol']
227                 and remote_ip_prefix == rule['remote_ip_prefix']
228                 and ethertype == rule['ethertype']
229                 and direction == rule['direction']
230                 and remote_group_id == rule['remote_group_id']
231                 and _ports_match(protocol,
232                                  module.params['port_range_min'],
233                                  module.params['port_range_max'],
234                                  rule['port_range_min'],
235                                  rule['port_range_max'])):
236             return rule
237     return None
238 
239 
240 def _system_state_change(module, secgroup, remotegroup):
241     state = module.params['state']
242     if secgroup:
243         rule_exists = _find_matching_rule(module, secgroup, remotegroup)
244     else:
245         return False
246 
247     if state == 'present' and not rule_exists:
248         return True
249     if state == 'absent' and rule_exists:
250         return True
251     return False
252 
253 
254 def main():
255     argument_spec = openstack_full_argument_spec(
256         security_group   = dict(required=True),
257         # NOTE(Shrews): None is an acceptable protocol value for
258         # Neutron, but Nova will balk at this.
259         protocol         = dict(default=None,
260                                 choices=[None, 'tcp', 'udp', 'icmp', 112]),
261         port_range_min   = dict(required=False, type='int'),
262         port_range_max   = dict(required=False, type='int'),
263         remote_ip_prefix = dict(required=False, default=None),
264         remote_group     = dict(required=False, default=None),
265         ethertype        = dict(default='IPv4',
266                                 choices=['IPv4', 'IPv6']),
267         direction        = dict(default='ingress',
268                                 choices=['egress', 'ingress']),
269         state            = dict(default='present',
270                                 choices=['absent', 'present']),
271     )
272 
273     module_kwargs = openstack_module_kwargs(
274         mutually_exclusive=[
275             ['remote_ip_prefix', 'remote_group'],
276         ]
277     )
278 
279     module = AnsibleModule(argument_spec,
280                            supports_check_mode=True,
281                            **module_kwargs)
282 
283     if not HAS_SHADE:
284         module.fail_json(msg='shade is required for this module')
285 
286     state = module.params['state']
287     security_group = module.params['security_group']
288     remote_group = module.params['remote_group']
289     changed = False
290 
291     try:
292         cloud = shade.openstack_cloud(**module.params)
293         secgroup = cloud.get_security_group(security_group)
294 
295         if remote_group:
296             remotegroup = cloud.get_security_group(remote_group)
297         else:
298             remotegroup = { 'id' : None }
299 
300         if module.check_mode:
301             module.exit_json(changed=_system_state_change(module, secgroup, remotegroup))
302 
303         if state == 'present':
304             if not secgroup:
305                 module.fail_json(msg='Could not find security group %s' %
306                                  security_group)
307 
308             rule = _find_matching_rule(module, secgroup, remotegroup)
309             if not rule:
310                 rule = cloud.create_security_group_rule(
311                     secgroup['id'],
312                     port_range_min=module.params['port_range_min'],
313                     port_range_max=module.params['port_range_max'],
314                     protocol=module.params['protocol'],
315                     remote_ip_prefix=module.params['remote_ip_prefix'],
316                     remote_group_id=remotegroup['id'],
317                     direction=module.params['direction'],
318                     ethertype=module.params['ethertype']
319                 )
320                 changed = True
321             module.exit_json(changed=changed, rule=rule, id=rule['id'])
322 
323         if state == 'absent' and secgroup:
324             rule = _find_matching_rule(module, secgroup, remotegroup)
325             if rule:
326                 cloud.delete_security_group_rule(rule['id'])
327                 changed = True
328 
329             module.exit_json(changed=changed)
330 
331     except shade.OpenStackCloudException as e:
332         module.fail_json(msg=str(e))
333 
334 # this is magic, see lib/ansible/module_common.py
335 from ansible.module_utils.basic import *
336 from ansible.module_utils.openstack import *
337 
338 if __name__ == '__main__':
339     main()
340
```
--- 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/cloud/openstack/os_security_group_rule.py b/cloud/openstack/os_security_group_rule.py
--- a/cloud/openstack/os_security_group_rule.py
+++ b/cloud/openstack/os_security_group_rule.py
@@ -39,7 +39,7 @@
    protocol:
       description:
         - IP protocols TCP UDP ICMP 112 (VRRP)
-      choices: ['tcp', 'udp', 'icmp', 112, None]
+      choices: ['tcp', 'udp', 'icmp', '112', None]
       default: None
    port_range_min:
       description:
@@ -257,7 +257,7 @@
         # NOTE(Shrews): None is an acceptable protocol value for
         # Neutron, but Nova will balk at this.
         protocol         = dict(default=None,
-                                choices=[None, 'tcp', 'udp', 'icmp', 112]),
+                                choices=[None, 'tcp', 'udp', 'icmp', '112']),
         port_range_min   = dict(required=False, type='int'),
         port_range_max   = dict(required=False, type='int'),
         remote_ip_prefix = dict(required=False, default=None),

{"golden_diff": "diff --git a/cloud/openstack/os_security_group_rule.py b/cloud/openstack/os_security_group_rule.py\n--- a/cloud/openstack/os_security_group_rule.py\n+++ b/cloud/openstack/os_security_group_rule.py\n@@ -39,7 +39,7 @@\n    protocol:\n       description:\n         - IP protocols TCP UDP ICMP 112 (VRRP)\n-      choices: ['tcp', 'udp', 'icmp', 112, None]\n+      choices: ['tcp', 'udp', 'icmp', '112', None]\n       default: None\n    port_range_min:\n       description:\n@@ -257,7 +257,7 @@\n         # NOTE(Shrews): None is an acceptable protocol value for\n         # Neutron, but Nova will balk at this.\n         protocol         = dict(default=None,\n-                                choices=[None, 'tcp', 'udp', 'icmp', 112]),\n+                                choices=[None, 'tcp', 'udp', 'icmp', '112']),\n         port_range_min   = dict(required=False, type='int'),\n         port_range_max   = dict(required=False, type='int'),\n         remote_ip_prefix = dict(required=False, default=None),\n", "issue": "Fix #4412: os_security_group_rule support numbered protocols\n##### ISSUE TYPE\n- Bugfix Pull Request\n##### COMPONENT NAME\n\ncloud/openstack/os_security_group_rule.py\n##### ANSIBLE VERSION\n\n```\nansible 2.1.1.0\n  config file = \n  configured module search path = Default w/o overrides\n```\n##### SUMMARY\n\nBugfix correctly points out lack of VRRP support by way of numbered protocol.  This commit adds this functionality, including example usage in the documentation.\n\n", "before_files": [{"content": "#!/usr/bin/python\n\n# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.\n# Copyright (c) 2013, Benno Joy <benno@ansible.com>\n#\n# This module 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# This software 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 this software.  If not, see <http://www.gnu.org/licenses/>.\n\ntry:\n    import shade\n    HAS_SHADE = True\nexcept ImportError:\n    HAS_SHADE = False\n\n\nDOCUMENTATION = '''\n---\nmodule: os_security_group_rule\nshort_description: Add/Delete rule from an existing security group\nextends_documentation_fragment: openstack\nversion_added: \"2.0\"\ndescription:\n   - Add or Remove rule from an existing security group\noptions:\n   security_group:\n     description:\n        - Name or ID of the security group\n     required: true\n   protocol:\n      description:\n        - IP protocols TCP UDP ICMP 112 (VRRP)\n      choices: ['tcp', 'udp', 'icmp', 112, None]\n      default: None\n   port_range_min:\n      description:\n        - Starting port\n      required: false\n      default: None\n   port_range_max:\n      description:\n        - Ending port\n      required: false\n      default: None\n   remote_ip_prefix:\n      description:\n        - Source IP address(es) in CIDR notation (exclusive with remote_group)\n      required: false\n   remote_group:\n      description:\n        - Name or ID of the Security group to link (exclusive with\n          remote_ip_prefix)\n      required: false\n   ethertype:\n      description:\n        - Must be IPv4 or IPv6, and addresses represented in CIDR must\n          match the ingress or egress rules. Not all providers support IPv6.\n      choices: ['IPv4', 'IPv6']\n      default: IPv4\n   direction:\n     description:\n        - The direction in which the security group rule is applied. Not\n          all providers support egress.\n     choices: ['egress', 'ingress']\n     default: ingress\n   state:\n     description:\n       - Should the resource be present or absent.\n     choices: [present, absent]\n     default: present\nrequirements: [\"shade\"]\n'''\n\nEXAMPLES = '''\n# Create a security group rule\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    port_range_min: 80\n    port_range_max: 80\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a security group rule for ping\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: icmp\n    remote_ip_prefix: 0.0.0.0/0\n\n# Another way to create the ping rule\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: icmp\n    port_range_min: -1\n    port_range_max: -1\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a TCP rule covering all ports\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    port_range_min: 1\n    port_range_max: 65535\n    remote_ip_prefix: 0.0.0.0/0\n\n# Another way to create the TCP rule above (defaults to all ports)\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a rule for VRRP with numbered protocol 112\n- os_security_group_rule:\n    security_group: loadbalancer_sg\n    protocol: 112\n    remote_group: loadbalancer-node_sg\n'''\n\nRETURN = '''\nid:\n  description: Unique rule UUID.\n  type: string\ndirection:\n  description: The direction in which the security group rule is applied.\n  type: string\n  sample: 'egress'\nethertype:\n  description: One of IPv4 or IPv6.\n  type: string\n  sample: 'IPv4'\nport_range_min:\n  description: The minimum port number in the range that is matched by\n               the security group rule.\n  type: int\n  sample: 8000\nport_range_max:\n  description: The maximum port number in the range that is matched by\n               the security group rule.\n  type: int\n  sample: 8000\nprotocol:\n  description: The protocol that is matched by the security group rule.\n  type: string\n  sample: 'tcp'\nremote_ip_prefix:\n  description: The remote IP prefix to be associated with this security group rule.\n  type: string\n  sample: '0.0.0.0/0'\nsecurity_group_id:\n  description: The security group ID to associate with this security group rule.\n  type: string\n'''\n\n\ndef _ports_match(protocol, module_min, module_max, rule_min, rule_max):\n    \"\"\"\n    Capture the complex port matching logic.\n\n    The port values coming in for the module might be -1 (for ICMP),\n    which will work only for Nova, but this is handled by shade. Likewise,\n    they might be None, which works for Neutron, but not Nova. This too is\n    handled by shade. Since shade will consistently return these port\n    values as None, we need to convert any -1 values input to the module\n    to None here for comparison.\n\n    For TCP and UDP protocols, None values for both min and max are\n    represented as the range 1-65535 for Nova, but remain None for\n    Neutron. Shade returns the full range when Nova is the backend (since\n    that is how Nova stores them), and None values for Neutron. If None\n    values are input to the module for both values, then we need to adjust\n    for comparison.\n    \"\"\"\n\n    # Check if the user is supplying -1 for ICMP.\n    if protocol == 'icmp':\n        if module_min and int(module_min) == -1:\n            module_min = None\n        if module_max and int(module_max) == -1:\n            module_max = None\n\n    # Check if user is supplying None values for full TCP/UDP port range.\n    if protocol in ['tcp', 'udp'] and module_min is None and module_max is None:\n        if (rule_min and int(rule_min) == 1\n                and rule_max and int(rule_max) == 65535):\n            # (None, None) == (1, 65535)\n            return True\n\n    # Sanity check to make sure we don't have type comparison issues.\n    if module_min:\n        module_min = int(module_min)\n    if module_max:\n        module_max = int(module_max)\n    if rule_min:\n        rule_min = int(rule_min)\n    if rule_max:\n        rule_max = int(rule_max)\n\n    return module_min == rule_min and module_max == rule_max\n\n\ndef _find_matching_rule(module, secgroup, remotegroup):\n    \"\"\"\n    Find a rule in the group that matches the module parameters.\n    :returns: The matching rule dict, or None if no matches.\n    \"\"\"\n    protocol = module.params['protocol']\n    remote_ip_prefix = module.params['remote_ip_prefix']\n    ethertype = module.params['ethertype']\n    direction = module.params['direction']\n    remote_group_id = remotegroup['id']\n\n    for rule in secgroup['security_group_rules']:\n        if (protocol == rule['protocol']\n                and remote_ip_prefix == rule['remote_ip_prefix']\n                and ethertype == rule['ethertype']\n                and direction == rule['direction']\n                and remote_group_id == rule['remote_group_id']\n                and _ports_match(protocol,\n                                 module.params['port_range_min'],\n                                 module.params['port_range_max'],\n                                 rule['port_range_min'],\n                                 rule['port_range_max'])):\n            return rule\n    return None\n\n\ndef _system_state_change(module, secgroup, remotegroup):\n    state = module.params['state']\n    if secgroup:\n        rule_exists = _find_matching_rule(module, secgroup, remotegroup)\n    else:\n        return False\n\n    if state == 'present' and not rule_exists:\n        return True\n    if state == 'absent' and rule_exists:\n        return True\n    return False\n\n\ndef main():\n    argument_spec = openstack_full_argument_spec(\n        security_group   = dict(required=True),\n        # NOTE(Shrews): None is an acceptable protocol value for\n        # Neutron, but Nova will balk at this.\n        protocol         = dict(default=None,\n                                choices=[None, 'tcp', 'udp', 'icmp', 112]),\n        port_range_min   = dict(required=False, type='int'),\n        port_range_max   = dict(required=False, type='int'),\n        remote_ip_prefix = dict(required=False, default=None),\n        remote_group     = dict(required=False, default=None),\n        ethertype        = dict(default='IPv4',\n                                choices=['IPv4', 'IPv6']),\n        direction        = dict(default='ingress',\n                                choices=['egress', 'ingress']),\n        state            = dict(default='present',\n                                choices=['absent', 'present']),\n    )\n\n    module_kwargs = openstack_module_kwargs(\n        mutually_exclusive=[\n            ['remote_ip_prefix', 'remote_group'],\n        ]\n    )\n\n    module = AnsibleModule(argument_spec,\n                           supports_check_mode=True,\n                           **module_kwargs)\n\n    if not HAS_SHADE:\n        module.fail_json(msg='shade is required for this module')\n\n    state = module.params['state']\n    security_group = module.params['security_group']\n    remote_group = module.params['remote_group']\n    changed = False\n\n    try:\n        cloud = shade.openstack_cloud(**module.params)\n        secgroup = cloud.get_security_group(security_group)\n\n        if remote_group:\n            remotegroup = cloud.get_security_group(remote_group)\n        else:\n            remotegroup = { 'id' : None }\n\n        if module.check_mode:\n            module.exit_json(changed=_system_state_change(module, secgroup, remotegroup))\n\n        if state == 'present':\n            if not secgroup:\n                module.fail_json(msg='Could not find security group %s' %\n                                 security_group)\n\n            rule = _find_matching_rule(module, secgroup, remotegroup)\n            if not rule:\n                rule = cloud.create_security_group_rule(\n                    secgroup['id'],\n                    port_range_min=module.params['port_range_min'],\n                    port_range_max=module.params['port_range_max'],\n                    protocol=module.params['protocol'],\n                    remote_ip_prefix=module.params['remote_ip_prefix'],\n                    remote_group_id=remotegroup['id'],\n                    direction=module.params['direction'],\n                    ethertype=module.params['ethertype']\n                )\n                changed = True\n            module.exit_json(changed=changed, rule=rule, id=rule['id'])\n\n        if state == 'absent' and secgroup:\n            rule = _find_matching_rule(module, secgroup, remotegroup)\n            if rule:\n                cloud.delete_security_group_rule(rule['id'])\n                changed = True\n\n            module.exit_json(changed=changed)\n\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\n# this is magic, see lib/ansible/module_common.py\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.openstack import *\n\nif __name__ == '__main__':\n    main()\n", "path": "cloud/openstack/os_security_group_rule.py"}], "after_files": [{"content": "#!/usr/bin/python\n\n# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.\n# Copyright (c) 2013, Benno Joy <benno@ansible.com>\n#\n# This module 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# This software 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 this software.  If not, see <http://www.gnu.org/licenses/>.\n\ntry:\n    import shade\n    HAS_SHADE = True\nexcept ImportError:\n    HAS_SHADE = False\n\n\nDOCUMENTATION = '''\n---\nmodule: os_security_group_rule\nshort_description: Add/Delete rule from an existing security group\nextends_documentation_fragment: openstack\nversion_added: \"2.0\"\ndescription:\n   - Add or Remove rule from an existing security group\noptions:\n   security_group:\n     description:\n        - Name or ID of the security group\n     required: true\n   protocol:\n      description:\n        - IP protocols TCP UDP ICMP 112 (VRRP)\n      choices: ['tcp', 'udp', 'icmp', '112', None]\n      default: None\n   port_range_min:\n      description:\n        - Starting port\n      required: false\n      default: None\n   port_range_max:\n      description:\n        - Ending port\n      required: false\n      default: None\n   remote_ip_prefix:\n      description:\n        - Source IP address(es) in CIDR notation (exclusive with remote_group)\n      required: false\n   remote_group:\n      description:\n        - Name or ID of the Security group to link (exclusive with\n          remote_ip_prefix)\n      required: false\n   ethertype:\n      description:\n        - Must be IPv4 or IPv6, and addresses represented in CIDR must\n          match the ingress or egress rules. Not all providers support IPv6.\n      choices: ['IPv4', 'IPv6']\n      default: IPv4\n   direction:\n     description:\n        - The direction in which the security group rule is applied. Not\n          all providers support egress.\n     choices: ['egress', 'ingress']\n     default: ingress\n   state:\n     description:\n       - Should the resource be present or absent.\n     choices: [present, absent]\n     default: present\nrequirements: [\"shade\"]\n'''\n\nEXAMPLES = '''\n# Create a security group rule\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    port_range_min: 80\n    port_range_max: 80\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a security group rule for ping\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: icmp\n    remote_ip_prefix: 0.0.0.0/0\n\n# Another way to create the ping rule\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: icmp\n    port_range_min: -1\n    port_range_max: -1\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a TCP rule covering all ports\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    port_range_min: 1\n    port_range_max: 65535\n    remote_ip_prefix: 0.0.0.0/0\n\n# Another way to create the TCP rule above (defaults to all ports)\n- os_security_group_rule:\n    cloud: mordred\n    security_group: foo\n    protocol: tcp\n    remote_ip_prefix: 0.0.0.0/0\n\n# Create a rule for VRRP with numbered protocol 112\n- os_security_group_rule:\n    security_group: loadbalancer_sg\n    protocol: 112\n    remote_group: loadbalancer-node_sg\n'''\n\nRETURN = '''\nid:\n  description: Unique rule UUID.\n  type: string\ndirection:\n  description: The direction in which the security group rule is applied.\n  type: string\n  sample: 'egress'\nethertype:\n  description: One of IPv4 or IPv6.\n  type: string\n  sample: 'IPv4'\nport_range_min:\n  description: The minimum port number in the range that is matched by\n               the security group rule.\n  type: int\n  sample: 8000\nport_range_max:\n  description: The maximum port number in the range that is matched by\n               the security group rule.\n  type: int\n  sample: 8000\nprotocol:\n  description: The protocol that is matched by the security group rule.\n  type: string\n  sample: 'tcp'\nremote_ip_prefix:\n  description: The remote IP prefix to be associated with this security group rule.\n  type: string\n  sample: '0.0.0.0/0'\nsecurity_group_id:\n  description: The security group ID to associate with this security group rule.\n  type: string\n'''\n\n\ndef _ports_match(protocol, module_min, module_max, rule_min, rule_max):\n    \"\"\"\n    Capture the complex port matching logic.\n\n    The port values coming in for the module might be -1 (for ICMP),\n    which will work only for Nova, but this is handled by shade. Likewise,\n    they might be None, which works for Neutron, but not Nova. This too is\n    handled by shade. Since shade will consistently return these port\n    values as None, we need to convert any -1 values input to the module\n    to None here for comparison.\n\n    For TCP and UDP protocols, None values for both min and max are\n    represented as the range 1-65535 for Nova, but remain None for\n    Neutron. Shade returns the full range when Nova is the backend (since\n    that is how Nova stores them), and None values for Neutron. If None\n    values are input to the module for both values, then we need to adjust\n    for comparison.\n    \"\"\"\n\n    # Check if the user is supplying -1 for ICMP.\n    if protocol == 'icmp':\n        if module_min and int(module_min) == -1:\n            module_min = None\n        if module_max and int(module_max) == -1:\n            module_max = None\n\n    # Check if user is supplying None values for full TCP/UDP port range.\n    if protocol in ['tcp', 'udp'] and module_min is None and module_max is None:\n        if (rule_min and int(rule_min) == 1\n                and rule_max and int(rule_max) == 65535):\n            # (None, None) == (1, 65535)\n            return True\n\n    # Sanity check to make sure we don't have type comparison issues.\n    if module_min:\n        module_min = int(module_min)\n    if module_max:\n        module_max = int(module_max)\n    if rule_min:\n        rule_min = int(rule_min)\n    if rule_max:\n        rule_max = int(rule_max)\n\n    return module_min == rule_min and module_max == rule_max\n\n\ndef _find_matching_rule(module, secgroup, remotegroup):\n    \"\"\"\n    Find a rule in the group that matches the module parameters.\n    :returns: The matching rule dict, or None if no matches.\n    \"\"\"\n    protocol = module.params['protocol']\n    remote_ip_prefix = module.params['remote_ip_prefix']\n    ethertype = module.params['ethertype']\n    direction = module.params['direction']\n    remote_group_id = remotegroup['id']\n\n    for rule in secgroup['security_group_rules']:\n        if (protocol == rule['protocol']\n                and remote_ip_prefix == rule['remote_ip_prefix']\n                and ethertype == rule['ethertype']\n                and direction == rule['direction']\n                and remote_group_id == rule['remote_group_id']\n                and _ports_match(protocol,\n                                 module.params['port_range_min'],\n                                 module.params['port_range_max'],\n                                 rule['port_range_min'],\n                                 rule['port_range_max'])):\n            return rule\n    return None\n\n\ndef _system_state_change(module, secgroup, remotegroup):\n    state = module.params['state']\n    if secgroup:\n        rule_exists = _find_matching_rule(module, secgroup, remotegroup)\n    else:\n        return False\n\n    if state == 'present' and not rule_exists:\n        return True\n    if state == 'absent' and rule_exists:\n        return True\n    return False\n\n\ndef main():\n    argument_spec = openstack_full_argument_spec(\n        security_group   = dict(required=True),\n        # NOTE(Shrews): None is an acceptable protocol value for\n        # Neutron, but Nova will balk at this.\n        protocol         = dict(default=None,\n                                choices=[None, 'tcp', 'udp', 'icmp', '112']),\n        port_range_min   = dict(required=False, type='int'),\n        port_range_max   = dict(required=False, type='int'),\n        remote_ip_prefix = dict(required=False, default=None),\n        remote_group     = dict(required=False, default=None),\n        ethertype        = dict(default='IPv4',\n                                choices=['IPv4', 'IPv6']),\n        direction        = dict(default='ingress',\n                                choices=['egress', 'ingress']),\n        state            = dict(default='present',\n                                choices=['absent', 'present']),\n    )\n\n    module_kwargs = openstack_module_kwargs(\n        mutually_exclusive=[\n            ['remote_ip_prefix', 'remote_group'],\n        ]\n    )\n\n    module = AnsibleModule(argument_spec,\n                           supports_check_mode=True,\n                           **module_kwargs)\n\n    if not HAS_SHADE:\n        module.fail_json(msg='shade is required for this module')\n\n    state = module.params['state']\n    security_group = module.params['security_group']\n    remote_group = module.params['remote_group']\n    changed = False\n\n    try:\n        cloud = shade.openstack_cloud(**module.params)\n        secgroup = cloud.get_security_group(security_group)\n\n        if remote_group:\n            remotegroup = cloud.get_security_group(remote_group)\n        else:\n            remotegroup = { 'id' : None }\n\n        if module.check_mode:\n            module.exit_json(changed=_system_state_change(module, secgroup, remotegroup))\n\n        if state == 'present':\n            if not secgroup:\n                module.fail_json(msg='Could not find security group %s' %\n                                 security_group)\n\n            rule = _find_matching_rule(module, secgroup, remotegroup)\n            if not rule:\n                rule = cloud.create_security_group_rule(\n                    secgroup['id'],\n                    port_range_min=module.params['port_range_min'],\n                    port_range_max=module.params['port_range_max'],\n                    protocol=module.params['protocol'],\n                    remote_ip_prefix=module.params['remote_ip_prefix'],\n                    remote_group_id=remotegroup['id'],\n                    direction=module.params['direction'],\n                    ethertype=module.params['ethertype']\n                )\n                changed = True\n            module.exit_json(changed=changed, rule=rule, id=rule['id'])\n\n        if state == 'absent' and secgroup:\n            rule = _find_matching_rule(module, secgroup, remotegroup)\n            if rule:\n                cloud.delete_security_group_rule(rule['id'])\n                changed = True\n\n            module.exit_json(changed=changed)\n\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\n# this is magic, see lib/ansible/module_common.py\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.openstack import *\n\nif __name__ == '__main__':\n    main()\n", "path": "cloud/openstack/os_security_group_rule.py"}]}