Add XML support for cloudformation commands that lacked it
This lets boto3's cloudformation API work with moto. fixes #444 Signed-off-by: Scott Greene <scott.greene@getbraintree.com>
This commit is contained in:
		
							parent
							
								
									06b173abe4
								
							
						
					
					
						commit
						ec10699c38
					
				| @ -47,14 +47,17 @@ class CloudFormationResponse(BaseResponse): | |||||||
|             notification_arns=stack_notification_arns, |             notification_arns=stack_notification_arns, | ||||||
|             tags=tags, |             tags=tags, | ||||||
|         ) |         ) | ||||||
|         stack_body = { |         if self.request_json: | ||||||
|  |             return json.dumps({ | ||||||
|                 'CreateStackResponse': { |                 'CreateStackResponse': { | ||||||
|                     'CreateStackResult': { |                     'CreateStackResult': { | ||||||
|                         'StackId': stack.stack_id, |                         'StackId': stack.stack_id, | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|         } |             }) | ||||||
|         return json.dumps(stack_body) |         else: | ||||||
|  |             template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE) | ||||||
|  |             return template.render(stack=stack) | ||||||
| 
 | 
 | ||||||
|     def describe_stacks(self): |     def describe_stacks(self): | ||||||
|         stack_name_or_id = None |         stack_name_or_id = None | ||||||
| @ -88,7 +91,8 @@ class CloudFormationResponse(BaseResponse): | |||||||
|         name_or_stack_id = self.querystring.get('StackName')[0] |         name_or_stack_id = self.querystring.get('StackName')[0] | ||||||
|         stack = self.cloudformation_backend.get_stack(name_or_stack_id) |         stack = self.cloudformation_backend.get_stack(name_or_stack_id) | ||||||
|          |          | ||||||
|         response = { |         if self.request_json: | ||||||
|  |             return json.dumps({ | ||||||
|                 "GetTemplateResponse": { |                 "GetTemplateResponse": { | ||||||
|                     "GetTemplateResult": { |                     "GetTemplateResult": { | ||||||
|                         "TemplateBody": stack.template, |                         "TemplateBody": stack.template, | ||||||
| @ -97,8 +101,10 @@ class CloudFormationResponse(BaseResponse): | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|         } |             }) | ||||||
|         return json.dumps(response) |         else: | ||||||
|  |             template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE) | ||||||
|  |             return template.render(stack=stack) | ||||||
| 
 | 
 | ||||||
|     def update_stack(self): |     def update_stack(self): | ||||||
|         stack_name = self._get_param('StackName') |         stack_name = self._get_param('StackName') | ||||||
| @ -121,14 +127,30 @@ class CloudFormationResponse(BaseResponse): | |||||||
|         name_or_stack_id = self.querystring.get('StackName')[0] |         name_or_stack_id = self.querystring.get('StackName')[0] | ||||||
| 
 | 
 | ||||||
|         self.cloudformation_backend.delete_stack(name_or_stack_id) |         self.cloudformation_backend.delete_stack(name_or_stack_id) | ||||||
|  |         if self.request_json: | ||||||
|             return json.dumps({ |             return json.dumps({ | ||||||
|                 'DeleteStackResponse': { |                 'DeleteStackResponse': { | ||||||
|                     'DeleteStackResult': {}, |                     'DeleteStackResult': {}, | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
|  |         else: | ||||||
|  |             template = self.response_template(DELETE_STACK_RESPONSE_TEMPLATE) | ||||||
|  |             return template.render() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResult> | CREATE_STACK_RESPONSE_TEMPLATE = """<CreateStackResponse> | ||||||
|  |   <CreateStackResult> | ||||||
|  |     <StackId>{{ stack.stack_id }}</StackId> | ||||||
|  |   </CreateStackResult> | ||||||
|  |   <ResponseMetadata> | ||||||
|  |     <RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId> | ||||||
|  |     </ResponseMetadata> | ||||||
|  | </CreateStackResponse> | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse> | ||||||
|  |   <DescribeStacksResult> | ||||||
|     <Stacks> |     <Stacks> | ||||||
|       {% for stack in stacks %} |       {% for stack in stacks %} | ||||||
|       <member> |       <member> | ||||||
| @ -173,24 +195,8 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResult> | |||||||
|       </member> |       </member> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
|     </Stacks> |     </Stacks> | ||||||
| </DescribeStacksResult>""" |   </DescribeStacksResult> | ||||||
| 
 | </DescribeStacksResponse>""" | ||||||
| 
 |  | ||||||
| LIST_STACKS_RESPONSE = """<ListStacksResponse> |  | ||||||
|  <ListStacksResult> |  | ||||||
|   <StackSummaries> |  | ||||||
|     {% for stack in stacks %} |  | ||||||
|     <member> |  | ||||||
|         <StackId>{{ stack.stack_id }}</StackId> |  | ||||||
|         <StackStatus>{{ stack.status }}</StackStatus> |  | ||||||
|         <StackName>{{ stack.name }}</StackName> |  | ||||||
|         <CreationTime>2011-05-23T15:47:44Z</CreationTime> |  | ||||||
|         <TemplateDescription>{{ stack.description }}</TemplateDescription> |  | ||||||
|     </member> |  | ||||||
|     {% endfor %} |  | ||||||
|   </StackSummaries> |  | ||||||
|  </ListStacksResult> |  | ||||||
| </ListStacksResponse>""" |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| DESCRIBE_STACKS_RESOURCES_RESPONSE = """<DescribeStackResourcesResult> | DESCRIBE_STACKS_RESOURCES_RESPONSE = """<DescribeStackResourcesResult> | ||||||
| @ -210,6 +216,23 @@ DESCRIBE_STACKS_RESOURCES_RESPONSE = """<DescribeStackResourcesResult> | |||||||
| </DescribeStackResourcesResult>""" | </DescribeStackResourcesResult>""" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | LIST_STACKS_RESPONSE = """<ListStacksResponse> | ||||||
|  |  <ListStacksResult> | ||||||
|  |   <StackSummaries> | ||||||
|  |     {% for stack in stacks %} | ||||||
|  |     <member> | ||||||
|  |         <StackId>{{ stack.stack_id }}</StackId> | ||||||
|  |         <StackStatus>{{ stack.status }}</StackStatus> | ||||||
|  |         <StackName>{{ stack.name }}</StackName> | ||||||
|  |         <CreationTime>2011-05-23T15:47:44Z</CreationTime> | ||||||
|  |         <TemplateDescription>{{ stack.description }}</TemplateDescription> | ||||||
|  |     </member> | ||||||
|  |     {% endfor %} | ||||||
|  |   </StackSummaries> | ||||||
|  |  </ListStacksResult> | ||||||
|  | </ListStacksResponse>""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse> | LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse> | ||||||
|   <ListStackResourcesResult> |   <ListStackResourcesResult> | ||||||
|     <StackResourceSummaries> |     <StackResourceSummaries> | ||||||
| @ -228,3 +251,22 @@ LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse> | |||||||
|     <RequestId>2d06e36c-ac1d-11e0-a958-f9382b6eb86b</RequestId> |     <RequestId>2d06e36c-ac1d-11e0-a958-f9382b6eb86b</RequestId> | ||||||
|   </ResponseMetadata> |   </ResponseMetadata> | ||||||
| </ListStackResourcesResponse>""" | </ListStackResourcesResponse>""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | GET_TEMPLATE_RESPONSE_TEMPLATE = """<GetTemplateResponse> | ||||||
|  |   <GetTemplateResult> | ||||||
|  |     <TemplateBody>{{ stack.template }} | ||||||
|  |     </TemplateBody> | ||||||
|  |   </GetTemplateResult> | ||||||
|  |   <ResponseMetadata> | ||||||
|  |     <RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId> | ||||||
|  |   </ResponseMetadata> | ||||||
|  | </GetTemplateResponse>""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | DELETE_STACK_RESPONSE_TEMPLATE = """<DeleteStackResponse> | ||||||
|  |   <ResponseMetadata> | ||||||
|  |     <RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId> | ||||||
|  |   </ResponseMetadata> | ||||||
|  | </DeleteStackResponse> | ||||||
|  | """ | ||||||
|  | |||||||
| @ -254,6 +254,10 @@ class BaseResponse(_TemplateEnvironmentMixin): | |||||||
|             param_index += 1 |             param_index += 1 | ||||||
|         return results |         return results | ||||||
| 
 | 
 | ||||||
|  |     @property | ||||||
|  |     def request_json(self): | ||||||
|  |         return 'JSON' in self.querystring.get('ContentType', []) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def metadata_response(request, full_url, headers): | def metadata_response(request, full_url, headers): | ||||||
|     """ |     """ | ||||||
|  | |||||||
| @ -12,10 +12,6 @@ class SNSResponse(BaseResponse): | |||||||
|     def backend(self): |     def backend(self): | ||||||
|         return sns_backends[self.region] |         return sns_backends[self.region] | ||||||
| 
 | 
 | ||||||
|     @property |  | ||||||
|     def request_json(self): |  | ||||||
|         return 'JSON' in self.querystring.get('ContentType', []) |  | ||||||
| 
 |  | ||||||
|     def _get_attributes(self): |     def _get_attributes(self): | ||||||
|         attributes = self._get_list_prefix('Attributes.entry') |         attributes = self._get_list_prefix('Attributes.entry') | ||||||
|         return dict( |         return dict( | ||||||
|  | |||||||
| @ -0,0 +1,231 @@ | |||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | import boto3 | ||||||
|  | import boto | ||||||
|  | import boto.s3 | ||||||
|  | import boto.s3.key | ||||||
|  | from botocore.exceptions import ClientError | ||||||
|  | from moto import mock_cloudformation, mock_s3 | ||||||
|  | 
 | ||||||
|  | import json | ||||||
|  | import sure  # noqa | ||||||
|  | # Ensure 'assert_raises' context manager support for Python 2.6 | ||||||
|  | import tests.backport_assert_raises  # noqa | ||||||
|  | from nose.tools import assert_raises | ||||||
|  | 
 | ||||||
|  | dummy_template = { | ||||||
|  |     "AWSTemplateFormatVersion": "2010-09-09", | ||||||
|  |     "Description": "Stack 1", | ||||||
|  |     "Resources": {}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | dummy_template_json = json.dumps(dummy_template) | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_boto3_create_stack(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(dummy_template) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_creating_stacks_across_regions(): | ||||||
|  |     west1_cf = boto3.resource('cloudformation', region_name='us-west-1') | ||||||
|  |     west2_cf = boto3.resource('cloudformation', region_name='us-west-2') | ||||||
|  |     west1_cf.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  |     west2_cf.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     list(west1_cf.stacks.all()).should.have.length_of(1) | ||||||
|  |     list(west2_cf.stacks.all()).should.have.length_of(1) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_create_stack_with_notification_arn(): | ||||||
|  |     cf = boto3.resource('cloudformation', region_name='us-east-1') | ||||||
|  |     cf.create_stack( | ||||||
|  |         StackName="test_stack_with_notifications", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |         NotificationARNs=['arn:aws:sns:us-east-1:123456789012:fake-queue'], | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack = list(cf.stacks.all())[0] | ||||||
|  |     stack.notification_arns.should.contain('arn:aws:sns:us-east-1:123456789012:fake-queue') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | @mock_s3 | ||||||
|  | def test_create_stack_from_s3_url(): | ||||||
|  |     s3_conn = boto.s3.connect_to_region('us-west-1') | ||||||
|  |     bucket = s3_conn.create_bucket("foobar") | ||||||
|  |     key = boto.s3.key.Key(bucket) | ||||||
|  |     key.key = "template-key" | ||||||
|  |     key.set_contents_from_string(dummy_template_json) | ||||||
|  |     key_url = key.generate_url(expires_in=0, query_auth=False) | ||||||
|  | 
 | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-west-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName='stack_from_url', | ||||||
|  |         TemplateURL=key_url, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     cf_conn.get_template(StackName="stack_from_url")['TemplateBody'].should.equal(dummy_template) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_describe_stack_by_name(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack = cf_conn.describe_stacks(StackName="test_stack")['Stacks'][0] | ||||||
|  |     stack['StackName'].should.equal('test_stack') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_describe_stack_by_stack_id(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack = cf_conn.describe_stacks(StackName="test_stack")['Stacks'][0] | ||||||
|  |     stack_by_id = cf_conn.describe_stacks(StackName=stack['StackId'])['Stacks'][0] | ||||||
|  | 
 | ||||||
|  |     stack_by_id['StackId'].should.equal(stack['StackId']) | ||||||
|  |     stack_by_id['StackName'].should.equal("test_stack") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_list_stacks(): | ||||||
|  |     cf = boto3.resource('cloudformation', region_name='us-east-1') | ||||||
|  |     cf.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  |     cf.create_stack( | ||||||
|  |         StackName="test_stack2", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stacks = list(cf.stacks.all()) | ||||||
|  |     stacks.should.have.length_of(2) | ||||||
|  |     stack_names = [stack.stack_name for stack in stacks] | ||||||
|  |     stack_names.should.contain("test_stack") | ||||||
|  |     stack_names.should.contain("test_stack2") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_delete_stack_from_resource(): | ||||||
|  |     cf = boto3.resource('cloudformation', region_name='us-east-1') | ||||||
|  |     stack = cf.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     list(cf.stacks.all()).should.have.length_of(1) | ||||||
|  |     stack.delete() | ||||||
|  |     list(cf.stacks.all()).should.have.length_of(0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_delete_stack_by_name(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     cf_conn.describe_stacks()['Stacks'].should.have.length_of(1) | ||||||
|  |     cf_conn.delete_stack(StackName="test_stack") | ||||||
|  |     cf_conn.describe_stacks()['Stacks'].should.have.length_of(0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_describe_deleted_stack(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     cf_conn.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack = cf_conn.describe_stacks(StackName="test_stack")['Stacks'][0] | ||||||
|  |     stack_id = stack['StackId'] | ||||||
|  |     cf_conn.delete_stack(StackName=stack['StackId']) | ||||||
|  |     stack_by_id = cf_conn.describe_stacks(StackName=stack_id)['Stacks'][0] | ||||||
|  |     stack_by_id['StackId'].should.equal(stack['StackId']) | ||||||
|  |     stack_by_id['StackName'].should.equal("test_stack") | ||||||
|  |     stack_by_id['StackStatus'].should.equal("DELETE_COMPLETE") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_bad_describe_stack(): | ||||||
|  |     cf_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     with assert_raises(ClientError): | ||||||
|  |         cf_conn.describe_stacks(StackName="non_existent_stack") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation() | ||||||
|  | def test_cloudformation_params(): | ||||||
|  |     dummy_template_with_params = { | ||||||
|  |         "AWSTemplateFormatVersion": "2010-09-09", | ||||||
|  |         "Description": "Stack 1", | ||||||
|  |         "Resources": {}, | ||||||
|  |         "Parameters": { | ||||||
|  |             "APPNAME": { | ||||||
|  |                 "Default": "app-name", | ||||||
|  |                 "Description": "The name of the app", | ||||||
|  |                 "Type": "String" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     dummy_template_with_params_json = json.dumps(dummy_template_with_params) | ||||||
|  | 
 | ||||||
|  |     cf = boto3.resource('cloudformation', region_name='us-east-1') | ||||||
|  |     stack = cf.create_stack( | ||||||
|  |         StackName='test_stack', | ||||||
|  |         TemplateBody=dummy_template_with_params_json, | ||||||
|  |         Parameters=[{ | ||||||
|  |             "ParameterKey": "APPNAME", | ||||||
|  |             "ParameterValue": "testing123", | ||||||
|  |         }], | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack.parameters.should.have.length_of(1) | ||||||
|  |     param = stack.parameters[0] | ||||||
|  |     param['ParameterKey'].should.equal('APPNAME') | ||||||
|  |     param['ParameterValue'].should.equal('testing123') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_stack_tags(): | ||||||
|  |     tags = [ | ||||||
|  |         { | ||||||
|  |             "Key": "foo", | ||||||
|  |             "Value": "bar" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "Key": "baz", | ||||||
|  |             "Value": "bleh" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  |     cf = boto3.resource('cloudformation', region_name='us-east-1') | ||||||
|  |     stack = cf.create_stack( | ||||||
|  |         StackName="test_stack", | ||||||
|  |         TemplateBody=dummy_template_json, | ||||||
|  |         Tags=tags, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     stack.tags.should.equal(tags) | ||||||
| @ -19,13 +19,12 @@ def test_cloudformation_server_get(): | |||||||
|     template_body = { |     template_body = { | ||||||
|         "Resources": {}, |         "Resources": {}, | ||||||
|     } |     } | ||||||
|     res = test_client.action_json("CreateStack", StackName=stack_name, |     create_stack_resp = test_client.action_data("CreateStack", StackName=stack_name, | ||||||
|         TemplateBody=json.dumps(template_body)) |         TemplateBody=json.dumps(template_body)) | ||||||
|     stack_id = res["CreateStackResponse"]["CreateStackResult"]["StackId"] |     create_stack_resp.should.match(r"<CreateStackResponse>.*<CreateStackResult>.*<StackId>.*</StackId>.*</CreateStackResult>.*</CreateStackResponse>", re.DOTALL) | ||||||
|  |     stack_id_from_create_response = re.search("<StackId>(.*)</StackId>", create_stack_resp).groups()[0] | ||||||
| 
 | 
 | ||||||
|     data = test_client.action_data("ListStacks") |     list_stacks_resp = test_client.action_data("ListStacks") | ||||||
|  |     stack_id_from_list_response = re.search("<StackId>(.*)</StackId>", list_stacks_resp).groups()[0] | ||||||
| 
 | 
 | ||||||
|     stacks = re.search("<StackId>(.*)</StackId>", data) |     stack_id_from_create_response.should.equal(stack_id_from_list_response) | ||||||
| 
 |  | ||||||
|     list_stack_id = stacks.groups()[0] |  | ||||||
|     assert stack_id == list_stack_id |  | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user