We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Authorize Security Group Ingress Only Works with Multi-Rule?
Near as I can tell, I should be able to use a simplified form of authorizing ingress when I only need a single rule; but this doesn't seem to work with Moto. The multi-rule syntax does work, on the other hand.

See these tests:

```
import boto3
import pytest

from moto import mock_ec2

@mock_ec2
def test_security_group_ingress_succeeds():
    ec2 = boto3.resource('ec2', 'ca-central-1')
    sg = ec2.create_security_group(Description='Test SG',GroupName='test-sg')

    assert len(sg.ip_permissions) == 0
    sg.authorize_ingress(IpPermissions=[
        {
            'FromPort': 22,
            'ToPort': 22,
            'IpProtocol': 'tcp',
            'IpRanges': [
                {
                    'CidrIp': '192.168.0.1/32'
                }
            ]
        }
    ])

    assert len(sg.ip_permissions) == 1

    sg_after = ec2.SecurityGroup(sg.id)
    assert len(sg_after.ip_permissions) == 1


@mock_ec2
def test_security_group_ingress_fails_without_multirule():
    ec2 = boto3.resource('ec2', 'ca-central-1')
    sg = ec2.create_security_group(Description='Test SG', GroupName='test-sg')

    assert len(sg.ip_permissions) == 0
    sg.authorize_ingress(CidrIp='192.168.0.1/32', FromPort=22, ToPort=22, IpProtocol='tcp')

    # Fails
    assert len(sg.ip_permissions) == 1


@mock_ec2
def test_security_group_ingress_fails_without_multirule_after_reload():
    ec2 = boto3.resource('ec2', 'ca-central-1')
    sg = ec2.create_security_group(Description='Test SG', GroupName='test-sg')

    assert len(sg.ip_permissions) == 0
    sg.authorize_ingress(CidrIp='192.168.0.1/32', FromPort=22, ToPort=22, IpProtocol='tcp')

    # Also Fails
    sg_after = ec2.SecurityGroup(sg.id)
    assert len(sg_after.ip_permissions) == 1
```

The first test, using the multi-rule syntax with the `IpPermission` array, works fine.

The second two tests fail. AFAIK, this syntax is valid, but doesn't work with moto.

I expected all three tests to pass, but they don't. Am I doing something wrong, or is this a Moto defect?

Using moto 1.2.0, installed with pipenv, using python mocks. Both version 1.6.6, installed the same way.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `moto/ec2/responses/security_groups.py`
Content:
```
1 from __future__ import unicode_literals
2 
3 from moto.core.responses import BaseResponse
4 from moto.ec2.utils import filters_from_querystring
5 
6 
7 def try_parse_int(value, default=None):
8     try:
9         return int(value)
10     except (TypeError, ValueError):
11         return default
12 
13 
14 class SecurityGroups(BaseResponse):
15 
16     def _process_rules_from_querystring(self):
17         group_name_or_id = (self._get_param('GroupName') or
18                             self._get_param('GroupId'))
19 
20         querytree = {}
21         for key, value in self.querystring.items():
22             key_splitted = key.split('.')
23             key_splitted = [try_parse_int(e, e) for e in key_splitted]
24 
25             d = querytree
26             for subkey in key_splitted[:-1]:
27                 if subkey not in d:
28                     d[subkey] = {}
29                 d = d[subkey]
30             d[key_splitted[-1]] = value
31 
32         ip_permissions = querytree.get('IpPermissions') or {}
33         for ip_permission_idx in sorted(ip_permissions.keys()):
34             ip_permission = ip_permissions[ip_permission_idx]
35 
36             ip_protocol = ip_permission.get('IpProtocol', [None])[0]
37             from_port = ip_permission.get('FromPort', [None])[0]
38             to_port = ip_permission.get('ToPort', [None])[0]
39 
40             ip_ranges = []
41             ip_ranges_tree = ip_permission.get('IpRanges') or {}
42             for ip_range_idx in sorted(ip_ranges_tree.keys()):
43                 ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])
44 
45             source_groups = []
46             source_group_ids = []
47             groups_tree = ip_permission.get('Groups') or {}
48             for group_idx in sorted(groups_tree.keys()):
49                 group_dict = groups_tree[group_idx]
50                 if 'GroupId' in group_dict:
51                     source_group_ids.append(group_dict['GroupId'][0])
52                 elif 'GroupName' in group_dict:
53                     source_groups.append(group_dict['GroupName'][0])
54 
55             yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,
56                    source_groups, source_group_ids)
57 
58     def authorize_security_group_egress(self):
59         if self.is_not_dryrun('GrantSecurityGroupEgress'):
60             for args in self._process_rules_from_querystring():
61                 self.ec2_backend.authorize_security_group_egress(*args)
62             return AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE
63 
64     def authorize_security_group_ingress(self):
65         if self.is_not_dryrun('GrantSecurityGroupIngress'):
66             for args in self._process_rules_from_querystring():
67                 self.ec2_backend.authorize_security_group_ingress(*args)
68             return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE
69 
70     def create_security_group(self):
71         name = self._get_param('GroupName')
72         description = self._get_param('GroupDescription')
73         vpc_id = self._get_param('VpcId')
74 
75         if self.is_not_dryrun('CreateSecurityGroup'):
76             group = self.ec2_backend.create_security_group(
77                 name, description, vpc_id=vpc_id)
78             template = self.response_template(CREATE_SECURITY_GROUP_RESPONSE)
79             return template.render(group=group)
80 
81     def delete_security_group(self):
82         # TODO this should raise an error if there are instances in the group.
83         # See
84         # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html
85 
86         name = self._get_param('GroupName')
87         sg_id = self._get_param('GroupId')
88 
89         if self.is_not_dryrun('DeleteSecurityGroup'):
90             if name:
91                 self.ec2_backend.delete_security_group(name)
92             elif sg_id:
93                 self.ec2_backend.delete_security_group(group_id=sg_id)
94 
95             return DELETE_GROUP_RESPONSE
96 
97     def describe_security_groups(self):
98         groupnames = self._get_multi_param("GroupName")
99         group_ids = self._get_multi_param("GroupId")
100         filters = filters_from_querystring(self.querystring)
101 
102         groups = self.ec2_backend.describe_security_groups(
103             group_ids=group_ids,
104             groupnames=groupnames,
105             filters=filters
106         )
107 
108         template = self.response_template(DESCRIBE_SECURITY_GROUPS_RESPONSE)
109         return template.render(groups=groups)
110 
111     def revoke_security_group_egress(self):
112         if self.is_not_dryrun('RevokeSecurityGroupEgress'):
113             for args in self._process_rules_from_querystring():
114                 success = self.ec2_backend.revoke_security_group_egress(*args)
115                 if not success:
116                     return "Could not find a matching egress rule", dict(status=404)
117             return REVOKE_SECURITY_GROUP_EGRESS_RESPONSE
118 
119     def revoke_security_group_ingress(self):
120         if self.is_not_dryrun('RevokeSecurityGroupIngress'):
121             for args in self._process_rules_from_querystring():
122                 self.ec2_backend.revoke_security_group_ingress(*args)
123             return REVOKE_SECURITY_GROUP_INGRESS_REPONSE
124 
125 
126 CREATE_SECURITY_GROUP_RESPONSE = """<CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
127    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
128    <return>true</return>
129    <groupId>{{ group.id }}</groupId>
130 </CreateSecurityGroupResponse>"""
131 
132 DELETE_GROUP_RESPONSE = """<DeleteSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
133   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
134   <return>true</return>
135 </DeleteSecurityGroupResponse>"""
136 
137 DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
138    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
139    <securityGroupInfo>
140       {% for group in groups %}
141           <item>
142              <ownerId>123456789012</ownerId>
143              <groupId>{{ group.id }}</groupId>
144              <groupName>{{ group.name }}</groupName>
145              <groupDescription>{{ group.description }}</groupDescription>
146              {% if group.vpc_id %}
147              <vpcId>{{ group.vpc_id }}</vpcId>
148              {% endif %}
149              <ipPermissions>
150                {% for rule in group.ingress_rules %}
151                     <item>
152                        <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
153                        {% if rule.from_port %}
154                        <fromPort>{{ rule.from_port }}</fromPort>
155                        {% endif %}
156                        {% if rule.to_port %}
157                        <toPort>{{ rule.to_port }}</toPort>
158                        {% endif %}
159                        <groups>
160                           {% for source_group in rule.source_groups %}
161                               <item>
162                                  <userId>123456789012</userId>
163                                  <groupId>{{ source_group.id }}</groupId>
164                                  <groupName>{{ source_group.name }}</groupName>
165                               </item>
166                           {% endfor %}
167                        </groups>
168                        <ipRanges>
169                           {% for ip_range in rule.ip_ranges %}
170                               <item>
171                                  <cidrIp>{{ ip_range }}</cidrIp>
172                               </item>
173                           {% endfor %}
174                        </ipRanges>
175                     </item>
176                 {% endfor %}
177              </ipPermissions>
178              <ipPermissionsEgress>
179                {% for rule in group.egress_rules %}
180                     <item>
181                        <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
182                        <fromPort>{{ rule.from_port }}</fromPort>
183                        <toPort>{{ rule.to_port }}</toPort>
184                        <groups>
185                           {% for source_group in rule.source_groups %}
186                               <item>
187                                  <userId>123456789012</userId>
188                                  <groupId>{{ source_group.id }}</groupId>
189                                  <groupName>{{ source_group.name }}</groupName>
190                               </item>
191                           {% endfor %}
192                        </groups>
193                        <ipRanges>
194                           {% for ip_range in rule.ip_ranges %}
195                               <item>
196                                  <cidrIp>{{ ip_range }}</cidrIp>
197                               </item>
198                           {% endfor %}
199                        </ipRanges>
200                     </item>
201                {% endfor %}
202              </ipPermissionsEgress>
203              <tagSet>
204                {% for tag in group.get_tags() %}
205                  <item>
206                    <resourceId>{{ tag.resource_id }}</resourceId>
207                    <resourceType>{{ tag.resource_type }}</resourceType>
208                    <key>{{ tag.key }}</key>
209                    <value>{{ tag.value }}</value>
210                  </item>
211                {% endfor %}
212              </tagSet>
213           </item>
214       {% endfor %}
215    </securityGroupInfo>
216 </DescribeSecurityGroupsResponse>"""
217 
218 AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = """<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
219   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
220   <return>true</return>
221 </AuthorizeSecurityGroupIngressResponse>"""
222 
223 REVOKE_SECURITY_GROUP_INGRESS_REPONSE = """<RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
224   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
225   <return>true</return>
226 </RevokeSecurityGroupIngressResponse>"""
227 
228 AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE = """
229 <AuthorizeSecurityGroupEgressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
230    <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
231    <return>true</return>
232 </AuthorizeSecurityGroupEgressResponse>"""
233 
234 REVOKE_SECURITY_GROUP_EGRESS_RESPONSE = """<RevokeSecurityGroupEgressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
235   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
236   <return>true</return>
237 </RevokeSecurityGroupEgressResponse>"""
238
```
--- 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/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py
--- a/moto/ec2/responses/security_groups.py
+++ b/moto/ec2/responses/security_groups.py
@@ -11,6 +11,29 @@
         return default
 
 
+def parse_sg_attributes_from_dict(sg_attributes):
+    ip_protocol = sg_attributes.get('IpProtocol', [None])[0]
+    from_port = sg_attributes.get('FromPort', [None])[0]
+    to_port = sg_attributes.get('ToPort', [None])[0]
+
+    ip_ranges = []
+    ip_ranges_tree = sg_attributes.get('IpRanges') or {}
+    for ip_range_idx in sorted(ip_ranges_tree.keys()):
+        ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])
+
+    source_groups = []
+    source_group_ids = []
+    groups_tree = sg_attributes.get('Groups') or {}
+    for group_idx in sorted(groups_tree.keys()):
+        group_dict = groups_tree[group_idx]
+        if 'GroupId' in group_dict:
+            source_group_ids.append(group_dict['GroupId'][0])
+        elif 'GroupName' in group_dict:
+            source_groups.append(group_dict['GroupName'][0])
+
+    return ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids
+
+
 class SecurityGroups(BaseResponse):
 
     def _process_rules_from_querystring(self):
@@ -29,28 +52,17 @@
                 d = d[subkey]
             d[key_splitted[-1]] = value
 
+        if 'IpPermissions' not in querytree:
+            # Handle single rule syntax
+            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(querytree)
+            yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,
+                   source_groups, source_group_ids)
+
         ip_permissions = querytree.get('IpPermissions') or {}
         for ip_permission_idx in sorted(ip_permissions.keys()):
             ip_permission = ip_permissions[ip_permission_idx]
 
-            ip_protocol = ip_permission.get('IpProtocol', [None])[0]
-            from_port = ip_permission.get('FromPort', [None])[0]
-            to_port = ip_permission.get('ToPort', [None])[0]
-
-            ip_ranges = []
-            ip_ranges_tree = ip_permission.get('IpRanges') or {}
-            for ip_range_idx in sorted(ip_ranges_tree.keys()):
-                ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])
-
-            source_groups = []
-            source_group_ids = []
-            groups_tree = ip_permission.get('Groups') or {}
-            for group_idx in sorted(groups_tree.keys()):
-                group_dict = groups_tree[group_idx]
-                if 'GroupId' in group_dict:
-                    source_group_ids.append(group_dict['GroupId'][0])
-                elif 'GroupName' in group_dict:
-                    source_groups.append(group_dict['GroupName'][0])
+            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(ip_permission)
 
             yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,
                    source_groups, source_group_ids)

{"golden_diff": "diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py\n--- a/moto/ec2/responses/security_groups.py\n+++ b/moto/ec2/responses/security_groups.py\n@@ -11,6 +11,29 @@\n         return default\n \n \n+def parse_sg_attributes_from_dict(sg_attributes):\n+    ip_protocol = sg_attributes.get('IpProtocol', [None])[0]\n+    from_port = sg_attributes.get('FromPort', [None])[0]\n+    to_port = sg_attributes.get('ToPort', [None])[0]\n+\n+    ip_ranges = []\n+    ip_ranges_tree = sg_attributes.get('IpRanges') or {}\n+    for ip_range_idx in sorted(ip_ranges_tree.keys()):\n+        ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])\n+\n+    source_groups = []\n+    source_group_ids = []\n+    groups_tree = sg_attributes.get('Groups') or {}\n+    for group_idx in sorted(groups_tree.keys()):\n+        group_dict = groups_tree[group_idx]\n+        if 'GroupId' in group_dict:\n+            source_group_ids.append(group_dict['GroupId'][0])\n+        elif 'GroupName' in group_dict:\n+            source_groups.append(group_dict['GroupName'][0])\n+\n+    return ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids\n+\n+\n class SecurityGroups(BaseResponse):\n \n     def _process_rules_from_querystring(self):\n@@ -29,28 +52,17 @@\n                 d = d[subkey]\n             d[key_splitted[-1]] = value\n \n+        if 'IpPermissions' not in querytree:\n+            # Handle single rule syntax\n+            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(querytree)\n+            yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,\n+                   source_groups, source_group_ids)\n+\n         ip_permissions = querytree.get('IpPermissions') or {}\n         for ip_permission_idx in sorted(ip_permissions.keys()):\n             ip_permission = ip_permissions[ip_permission_idx]\n \n-            ip_protocol = ip_permission.get('IpProtocol', [None])[0]\n-            from_port = ip_permission.get('FromPort', [None])[0]\n-            to_port = ip_permission.get('ToPort', [None])[0]\n-\n-            ip_ranges = []\n-            ip_ranges_tree = ip_permission.get('IpRanges') or {}\n-            for ip_range_idx in sorted(ip_ranges_tree.keys()):\n-                ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])\n-\n-            source_groups = []\n-            source_group_ids = []\n-            groups_tree = ip_permission.get('Groups') or {}\n-            for group_idx in sorted(groups_tree.keys()):\n-                group_dict = groups_tree[group_idx]\n-                if 'GroupId' in group_dict:\n-                    source_group_ids.append(group_dict['GroupId'][0])\n-                elif 'GroupName' in group_dict:\n-                    source_groups.append(group_dict['GroupName'][0])\n+            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(ip_permission)\n \n             yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,\n                    source_groups, source_group_ids)\n", "issue": "Authorize Security Group Ingress Only Works with Multi-Rule?\nNear as I can tell, I should be able to use a simplified form of authorizing ingress when I only need a single rule; but this doesn't seem to work with Moto. The multi-rule syntax does work, on the other hand.\r\n\r\nSee these tests:\r\n\r\n```\r\nimport boto3\r\nimport pytest\r\n\r\nfrom moto import mock_ec2\r\n\r\n@mock_ec2\r\ndef test_security_group_ingress_succeeds():\r\n    ec2 = boto3.resource('ec2', 'ca-central-1')\r\n    sg = ec2.create_security_group(Description='Test SG',GroupName='test-sg')\r\n\r\n    assert len(sg.ip_permissions) == 0\r\n    sg.authorize_ingress(IpPermissions=[\r\n        {\r\n            'FromPort': 22,\r\n            'ToPort': 22,\r\n            'IpProtocol': 'tcp',\r\n            'IpRanges': [\r\n                {\r\n                    'CidrIp': '192.168.0.1/32'\r\n                }\r\n            ]\r\n        }\r\n    ])\r\n\r\n    assert len(sg.ip_permissions) == 1\r\n\r\n    sg_after = ec2.SecurityGroup(sg.id)\r\n    assert len(sg_after.ip_permissions) == 1\r\n\r\n\r\n@mock_ec2\r\ndef test_security_group_ingress_fails_without_multirule():\r\n    ec2 = boto3.resource('ec2', 'ca-central-1')\r\n    sg = ec2.create_security_group(Description='Test SG', GroupName='test-sg')\r\n\r\n    assert len(sg.ip_permissions) == 0\r\n    sg.authorize_ingress(CidrIp='192.168.0.1/32', FromPort=22, ToPort=22, IpProtocol='tcp')\r\n\r\n    # Fails\r\n    assert len(sg.ip_permissions) == 1\r\n\r\n\r\n@mock_ec2\r\ndef test_security_group_ingress_fails_without_multirule_after_reload():\r\n    ec2 = boto3.resource('ec2', 'ca-central-1')\r\n    sg = ec2.create_security_group(Description='Test SG', GroupName='test-sg')\r\n\r\n    assert len(sg.ip_permissions) == 0\r\n    sg.authorize_ingress(CidrIp='192.168.0.1/32', FromPort=22, ToPort=22, IpProtocol='tcp')\r\n\r\n    # Also Fails\r\n    sg_after = ec2.SecurityGroup(sg.id)\r\n    assert len(sg_after.ip_permissions) == 1\r\n```\r\n\r\nThe first test, using the multi-rule syntax with the `IpPermission` array, works fine.\r\n\r\nThe second two tests fail. AFAIK, this syntax is valid, but doesn't work with moto.\r\n\r\nI expected all three tests to pass, but they don't. Am I doing something wrong, or is this a Moto defect?\r\n\r\nUsing moto 1.2.0, installed with pipenv, using python mocks. Both version 1.6.6, installed the same way.\r\n\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nfrom moto.core.responses import BaseResponse\nfrom moto.ec2.utils import filters_from_querystring\n\n\ndef try_parse_int(value, default=None):\n    try:\n        return int(value)\n    except (TypeError, ValueError):\n        return default\n\n\nclass SecurityGroups(BaseResponse):\n\n    def _process_rules_from_querystring(self):\n        group_name_or_id = (self._get_param('GroupName') or\n                            self._get_param('GroupId'))\n\n        querytree = {}\n        for key, value in self.querystring.items():\n            key_splitted = key.split('.')\n            key_splitted = [try_parse_int(e, e) for e in key_splitted]\n\n            d = querytree\n            for subkey in key_splitted[:-1]:\n                if subkey not in d:\n                    d[subkey] = {}\n                d = d[subkey]\n            d[key_splitted[-1]] = value\n\n        ip_permissions = querytree.get('IpPermissions') or {}\n        for ip_permission_idx in sorted(ip_permissions.keys()):\n            ip_permission = ip_permissions[ip_permission_idx]\n\n            ip_protocol = ip_permission.get('IpProtocol', [None])[0]\n            from_port = ip_permission.get('FromPort', [None])[0]\n            to_port = ip_permission.get('ToPort', [None])[0]\n\n            ip_ranges = []\n            ip_ranges_tree = ip_permission.get('IpRanges') or {}\n            for ip_range_idx in sorted(ip_ranges_tree.keys()):\n                ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])\n\n            source_groups = []\n            source_group_ids = []\n            groups_tree = ip_permission.get('Groups') or {}\n            for group_idx in sorted(groups_tree.keys()):\n                group_dict = groups_tree[group_idx]\n                if 'GroupId' in group_dict:\n                    source_group_ids.append(group_dict['GroupId'][0])\n                elif 'GroupName' in group_dict:\n                    source_groups.append(group_dict['GroupName'][0])\n\n            yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,\n                   source_groups, source_group_ids)\n\n    def authorize_security_group_egress(self):\n        if self.is_not_dryrun('GrantSecurityGroupEgress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.authorize_security_group_egress(*args)\n            return AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE\n\n    def authorize_security_group_ingress(self):\n        if self.is_not_dryrun('GrantSecurityGroupIngress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.authorize_security_group_ingress(*args)\n            return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE\n\n    def create_security_group(self):\n        name = self._get_param('GroupName')\n        description = self._get_param('GroupDescription')\n        vpc_id = self._get_param('VpcId')\n\n        if self.is_not_dryrun('CreateSecurityGroup'):\n            group = self.ec2_backend.create_security_group(\n                name, description, vpc_id=vpc_id)\n            template = self.response_template(CREATE_SECURITY_GROUP_RESPONSE)\n            return template.render(group=group)\n\n    def delete_security_group(self):\n        # TODO this should raise an error if there are instances in the group.\n        # See\n        # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html\n\n        name = self._get_param('GroupName')\n        sg_id = self._get_param('GroupId')\n\n        if self.is_not_dryrun('DeleteSecurityGroup'):\n            if name:\n                self.ec2_backend.delete_security_group(name)\n            elif sg_id:\n                self.ec2_backend.delete_security_group(group_id=sg_id)\n\n            return DELETE_GROUP_RESPONSE\n\n    def describe_security_groups(self):\n        groupnames = self._get_multi_param(\"GroupName\")\n        group_ids = self._get_multi_param(\"GroupId\")\n        filters = filters_from_querystring(self.querystring)\n\n        groups = self.ec2_backend.describe_security_groups(\n            group_ids=group_ids,\n            groupnames=groupnames,\n            filters=filters\n        )\n\n        template = self.response_template(DESCRIBE_SECURITY_GROUPS_RESPONSE)\n        return template.render(groups=groups)\n\n    def revoke_security_group_egress(self):\n        if self.is_not_dryrun('RevokeSecurityGroupEgress'):\n            for args in self._process_rules_from_querystring():\n                success = self.ec2_backend.revoke_security_group_egress(*args)\n                if not success:\n                    return \"Could not find a matching egress rule\", dict(status=404)\n            return REVOKE_SECURITY_GROUP_EGRESS_RESPONSE\n\n    def revoke_security_group_ingress(self):\n        if self.is_not_dryrun('RevokeSecurityGroupIngress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.revoke_security_group_ingress(*args)\n            return REVOKE_SECURITY_GROUP_INGRESS_REPONSE\n\n\nCREATE_SECURITY_GROUP_RESPONSE = \"\"\"<CreateSecurityGroupResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <return>true</return>\n   <groupId>{{ group.id }}</groupId>\n</CreateSecurityGroupResponse>\"\"\"\n\nDELETE_GROUP_RESPONSE = \"\"\"<DeleteSecurityGroupResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</DeleteSecurityGroupResponse>\"\"\"\n\nDESCRIBE_SECURITY_GROUPS_RESPONSE = \"\"\"<DescribeSecurityGroupsResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <securityGroupInfo>\n      {% for group in groups %}\n          <item>\n             <ownerId>123456789012</ownerId>\n             <groupId>{{ group.id }}</groupId>\n             <groupName>{{ group.name }}</groupName>\n             <groupDescription>{{ group.description }}</groupDescription>\n             {% if group.vpc_id %}\n             <vpcId>{{ group.vpc_id }}</vpcId>\n             {% endif %}\n             <ipPermissions>\n               {% for rule in group.ingress_rules %}\n                    <item>\n                       <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>\n                       {% if rule.from_port %}\n                       <fromPort>{{ rule.from_port }}</fromPort>\n                       {% endif %}\n                       {% if rule.to_port %}\n                       <toPort>{{ rule.to_port }}</toPort>\n                       {% endif %}\n                       <groups>\n                          {% for source_group in rule.source_groups %}\n                              <item>\n                                 <userId>123456789012</userId>\n                                 <groupId>{{ source_group.id }}</groupId>\n                                 <groupName>{{ source_group.name }}</groupName>\n                              </item>\n                          {% endfor %}\n                       </groups>\n                       <ipRanges>\n                          {% for ip_range in rule.ip_ranges %}\n                              <item>\n                                 <cidrIp>{{ ip_range }}</cidrIp>\n                              </item>\n                          {% endfor %}\n                       </ipRanges>\n                    </item>\n                {% endfor %}\n             </ipPermissions>\n             <ipPermissionsEgress>\n               {% for rule in group.egress_rules %}\n                    <item>\n                       <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>\n                       <fromPort>{{ rule.from_port }}</fromPort>\n                       <toPort>{{ rule.to_port }}</toPort>\n                       <groups>\n                          {% for source_group in rule.source_groups %}\n                              <item>\n                                 <userId>123456789012</userId>\n                                 <groupId>{{ source_group.id }}</groupId>\n                                 <groupName>{{ source_group.name }}</groupName>\n                              </item>\n                          {% endfor %}\n                       </groups>\n                       <ipRanges>\n                          {% for ip_range in rule.ip_ranges %}\n                              <item>\n                                 <cidrIp>{{ ip_range }}</cidrIp>\n                              </item>\n                          {% endfor %}\n                       </ipRanges>\n                    </item>\n               {% endfor %}\n             </ipPermissionsEgress>\n             <tagSet>\n               {% for tag in group.get_tags() %}\n                 <item>\n                   <resourceId>{{ tag.resource_id }}</resourceId>\n                   <resourceType>{{ tag.resource_type }}</resourceType>\n                   <key>{{ tag.key }}</key>\n                   <value>{{ tag.value }}</value>\n                 </item>\n               {% endfor %}\n             </tagSet>\n          </item>\n      {% endfor %}\n   </securityGroupInfo>\n</DescribeSecurityGroupsResponse>\"\"\"\n\nAUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = \"\"\"<AuthorizeSecurityGroupIngressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</AuthorizeSecurityGroupIngressResponse>\"\"\"\n\nREVOKE_SECURITY_GROUP_INGRESS_REPONSE = \"\"\"<RevokeSecurityGroupIngressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</RevokeSecurityGroupIngressResponse>\"\"\"\n\nAUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE = \"\"\"\n<AuthorizeSecurityGroupEgressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <return>true</return>\n</AuthorizeSecurityGroupEgressResponse>\"\"\"\n\nREVOKE_SECURITY_GROUP_EGRESS_RESPONSE = \"\"\"<RevokeSecurityGroupEgressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</RevokeSecurityGroupEgressResponse>\"\"\"\n", "path": "moto/ec2/responses/security_groups.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nfrom moto.core.responses import BaseResponse\nfrom moto.ec2.utils import filters_from_querystring\n\n\ndef try_parse_int(value, default=None):\n    try:\n        return int(value)\n    except (TypeError, ValueError):\n        return default\n\n\ndef parse_sg_attributes_from_dict(sg_attributes):\n    ip_protocol = sg_attributes.get('IpProtocol', [None])[0]\n    from_port = sg_attributes.get('FromPort', [None])[0]\n    to_port = sg_attributes.get('ToPort', [None])[0]\n\n    ip_ranges = []\n    ip_ranges_tree = sg_attributes.get('IpRanges') or {}\n    for ip_range_idx in sorted(ip_ranges_tree.keys()):\n        ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0])\n\n    source_groups = []\n    source_group_ids = []\n    groups_tree = sg_attributes.get('Groups') or {}\n    for group_idx in sorted(groups_tree.keys()):\n        group_dict = groups_tree[group_idx]\n        if 'GroupId' in group_dict:\n            source_group_ids.append(group_dict['GroupId'][0])\n        elif 'GroupName' in group_dict:\n            source_groups.append(group_dict['GroupName'][0])\n\n    return ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids\n\n\nclass SecurityGroups(BaseResponse):\n\n    def _process_rules_from_querystring(self):\n        group_name_or_id = (self._get_param('GroupName') or\n                            self._get_param('GroupId'))\n\n        querytree = {}\n        for key, value in self.querystring.items():\n            key_splitted = key.split('.')\n            key_splitted = [try_parse_int(e, e) for e in key_splitted]\n\n            d = querytree\n            for subkey in key_splitted[:-1]:\n                if subkey not in d:\n                    d[subkey] = {}\n                d = d[subkey]\n            d[key_splitted[-1]] = value\n\n        if 'IpPermissions' not in querytree:\n            # Handle single rule syntax\n            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(querytree)\n            yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,\n                   source_groups, source_group_ids)\n\n        ip_permissions = querytree.get('IpPermissions') or {}\n        for ip_permission_idx in sorted(ip_permissions.keys()):\n            ip_permission = ip_permissions[ip_permission_idx]\n\n            ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids = parse_sg_attributes_from_dict(ip_permission)\n\n            yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges,\n                   source_groups, source_group_ids)\n\n    def authorize_security_group_egress(self):\n        if self.is_not_dryrun('GrantSecurityGroupEgress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.authorize_security_group_egress(*args)\n            return AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE\n\n    def authorize_security_group_ingress(self):\n        if self.is_not_dryrun('GrantSecurityGroupIngress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.authorize_security_group_ingress(*args)\n            return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE\n\n    def create_security_group(self):\n        name = self._get_param('GroupName')\n        description = self._get_param('GroupDescription')\n        vpc_id = self._get_param('VpcId')\n\n        if self.is_not_dryrun('CreateSecurityGroup'):\n            group = self.ec2_backend.create_security_group(\n                name, description, vpc_id=vpc_id)\n            template = self.response_template(CREATE_SECURITY_GROUP_RESPONSE)\n            return template.render(group=group)\n\n    def delete_security_group(self):\n        # TODO this should raise an error if there are instances in the group.\n        # See\n        # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html\n\n        name = self._get_param('GroupName')\n        sg_id = self._get_param('GroupId')\n\n        if self.is_not_dryrun('DeleteSecurityGroup'):\n            if name:\n                self.ec2_backend.delete_security_group(name)\n            elif sg_id:\n                self.ec2_backend.delete_security_group(group_id=sg_id)\n\n            return DELETE_GROUP_RESPONSE\n\n    def describe_security_groups(self):\n        groupnames = self._get_multi_param(\"GroupName\")\n        group_ids = self._get_multi_param(\"GroupId\")\n        filters = filters_from_querystring(self.querystring)\n\n        groups = self.ec2_backend.describe_security_groups(\n            group_ids=group_ids,\n            groupnames=groupnames,\n            filters=filters\n        )\n\n        template = self.response_template(DESCRIBE_SECURITY_GROUPS_RESPONSE)\n        return template.render(groups=groups)\n\n    def revoke_security_group_egress(self):\n        if self.is_not_dryrun('RevokeSecurityGroupEgress'):\n            for args in self._process_rules_from_querystring():\n                success = self.ec2_backend.revoke_security_group_egress(*args)\n                if not success:\n                    return \"Could not find a matching egress rule\", dict(status=404)\n            return REVOKE_SECURITY_GROUP_EGRESS_RESPONSE\n\n    def revoke_security_group_ingress(self):\n        if self.is_not_dryrun('RevokeSecurityGroupIngress'):\n            for args in self._process_rules_from_querystring():\n                self.ec2_backend.revoke_security_group_ingress(*args)\n            return REVOKE_SECURITY_GROUP_INGRESS_REPONSE\n\n\nCREATE_SECURITY_GROUP_RESPONSE = \"\"\"<CreateSecurityGroupResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <return>true</return>\n   <groupId>{{ group.id }}</groupId>\n</CreateSecurityGroupResponse>\"\"\"\n\nDELETE_GROUP_RESPONSE = \"\"\"<DeleteSecurityGroupResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</DeleteSecurityGroupResponse>\"\"\"\n\nDESCRIBE_SECURITY_GROUPS_RESPONSE = \"\"\"<DescribeSecurityGroupsResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <securityGroupInfo>\n      {% for group in groups %}\n          <item>\n             <ownerId>123456789012</ownerId>\n             <groupId>{{ group.id }}</groupId>\n             <groupName>{{ group.name }}</groupName>\n             <groupDescription>{{ group.description }}</groupDescription>\n             {% if group.vpc_id %}\n             <vpcId>{{ group.vpc_id }}</vpcId>\n             {% endif %}\n             <ipPermissions>\n               {% for rule in group.ingress_rules %}\n                    <item>\n                       <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>\n                       {% if rule.from_port %}\n                       <fromPort>{{ rule.from_port }}</fromPort>\n                       {% endif %}\n                       {% if rule.to_port %}\n                       <toPort>{{ rule.to_port }}</toPort>\n                       {% endif %}\n                       <groups>\n                          {% for source_group in rule.source_groups %}\n                              <item>\n                                 <userId>123456789012</userId>\n                                 <groupId>{{ source_group.id }}</groupId>\n                                 <groupName>{{ source_group.name }}</groupName>\n                              </item>\n                          {% endfor %}\n                       </groups>\n                       <ipRanges>\n                          {% for ip_range in rule.ip_ranges %}\n                              <item>\n                                 <cidrIp>{{ ip_range }}</cidrIp>\n                              </item>\n                          {% endfor %}\n                       </ipRanges>\n                    </item>\n                {% endfor %}\n             </ipPermissions>\n             <ipPermissionsEgress>\n               {% for rule in group.egress_rules %}\n                    <item>\n                       <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>\n                       <fromPort>{{ rule.from_port }}</fromPort>\n                       <toPort>{{ rule.to_port }}</toPort>\n                       <groups>\n                          {% for source_group in rule.source_groups %}\n                              <item>\n                                 <userId>123456789012</userId>\n                                 <groupId>{{ source_group.id }}</groupId>\n                                 <groupName>{{ source_group.name }}</groupName>\n                              </item>\n                          {% endfor %}\n                       </groups>\n                       <ipRanges>\n                          {% for ip_range in rule.ip_ranges %}\n                              <item>\n                                 <cidrIp>{{ ip_range }}</cidrIp>\n                              </item>\n                          {% endfor %}\n                       </ipRanges>\n                    </item>\n               {% endfor %}\n             </ipPermissionsEgress>\n             <tagSet>\n               {% for tag in group.get_tags() %}\n                 <item>\n                   <resourceId>{{ tag.resource_id }}</resourceId>\n                   <resourceType>{{ tag.resource_type }}</resourceType>\n                   <key>{{ tag.key }}</key>\n                   <value>{{ tag.value }}</value>\n                 </item>\n               {% endfor %}\n             </tagSet>\n          </item>\n      {% endfor %}\n   </securityGroupInfo>\n</DescribeSecurityGroupsResponse>\"\"\"\n\nAUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = \"\"\"<AuthorizeSecurityGroupIngressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</AuthorizeSecurityGroupIngressResponse>\"\"\"\n\nREVOKE_SECURITY_GROUP_INGRESS_REPONSE = \"\"\"<RevokeSecurityGroupIngressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</RevokeSecurityGroupIngressResponse>\"\"\"\n\nAUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE = \"\"\"\n<AuthorizeSecurityGroupEgressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n   <return>true</return>\n</AuthorizeSecurityGroupEgressResponse>\"\"\"\n\nREVOKE_SECURITY_GROUP_EGRESS_RESPONSE = \"\"\"<RevokeSecurityGroupEgressResponse xmlns=\"http://ec2.amazonaws.com/doc/2013-10-15/\">\n  <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>\n  <return>true</return>\n</RevokeSecurityGroupEgressResponse>\"\"\"\n", "path": "moto/ec2/responses/security_groups.py"}]}