From 4c6f08d463afca69113a8d6b4beb6fd110fa42d0 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sat, 16 Feb 2019 09:27:23 -0600 Subject: [PATCH 01/22] Update kms and lambda to work with terraform --- moto/awslambda/models.py | 8 ++++ moto/awslambda/responses.py | 17 ++++++++- moto/kms/models.py | 16 +++++++- moto/kms/responses.py | 22 +++++++++++ tests/test_awslambda/test_lambda.py | 34 +++++++++++++++++ tests/test_kms/test_kms.py | 57 +++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 2 deletions(-) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index a37a15e27..9fc41c11e 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -500,6 +500,11 @@ class LambdaStorage(object): except ValueError: return self._functions[name]['latest'] + def list_versions_by_function(self, name): + if name not in self._functions: + return None + return [self._functions[name]['latest']] + def get_arn(self, arn): return self._arns.get(arn, None) @@ -607,6 +612,9 @@ class LambdaBackend(BaseBackend): def get_function(self, function_name, qualifier=None): return self._lambdas.get_function(function_name, qualifier) + def list_versions_by_function(self, function_name): + return self._lambdas.list_versions_by_function(function_name) + def get_function_by_arn(self, function_arn): return self._lambdas.get_arn(function_arn) diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index 1a9a4df83..bcd2da903 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -52,7 +52,11 @@ class LambdaResponse(BaseResponse): self.setup_class(request, full_url, headers) if request.method == 'GET': # This is ListVersionByFunction - raise ValueError("Cannot handle request") + + path = request.path if hasattr(request, 'path') else path_url(request.url) + function_name = path.split('/')[-2] + return self._list_versions_by_function(function_name) + elif request.method == 'POST': return self._publish_function(request, full_url, headers) else: @@ -151,6 +155,17 @@ class LambdaResponse(BaseResponse): return 200, {}, json.dumps(result) + def _list_versions_by_function(self, function_name): + result = { + 'Versions': [] + } + + for fn in self.lambda_backend.list_versions_by_function(function_name): + json_data = fn.get_configuration() + result['Versions'].append(json_data) + + return 200, {}, json.dumps(result) + def _create_function(self, request, full_url, headers): try: fn = self.lambda_backend.create_function(self.json_body) diff --git a/moto/kms/models.py b/moto/kms/models.py index bb39d1b24..fb4040617 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -21,6 +21,7 @@ class Key(BaseModel): self.account_id = "0123456789012" self.key_rotation_status = False self.deletion_date = None + self.tags = {} @property def physical_resource_id(self): @@ -35,7 +36,7 @@ class Key(BaseModel): "KeyMetadata": { "AWSAccountId": self.account_id, "Arn": self.arn, - "CreationDate": "2015-01-01 00:00:00", + "CreationDate": datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ"), "Description": self.description, "Enabled": self.enabled, "KeyId": self.id, @@ -84,6 +85,19 @@ class KmsBackend(BaseBackend): self.keys[key.id] = key return key + def update_key_description(self, key_id, description): + key = self.keys[self.get_key_id(key_id)] + key.description = description + + def tag_resource(self, key_id, tags): + key = self.keys[self.get_key_id(key_id)] + key.tags = tags + + + def list_resource_tags(self, key_id): + key = self.keys[self.get_key_id(key_id)] + return key.tags + def delete_key(self, key_id): if key_id in self.keys: if key_id in self.key_to_aliases: diff --git a/moto/kms/responses.py b/moto/kms/responses.py index 5883f51ec..b66ca267c 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -38,6 +38,28 @@ class KmsResponse(BaseResponse): policy, key_usage, description, self.region) return json.dumps(key.to_dict()) + def update_key_description(self): + key_id = self.parameters.get('KeyId') + description = self.parameters.get('Description') + + self.kms_backend.update_key_description(key_id,description) + return json.dumps(None) + + def tag_resource(self): + key_id = self.parameters.get('KeyId') + tags = self.parameters.get('Tags') + self.kms_backend.tag_resource(key_id,tags) + return json.dumps({}) + + def list_resource_tags(self): + key_id = self.parameters.get('KeyId') + tags = self.kms_backend.list_resource_tags(key_id) + return json.dumps({ + "Tags": tags, + "NextMarker": None, + "Truncated": False, + }) + def describe_key(self): key_id = self.parameters.get('KeyId') try: diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 8ea9cc6fd..9c5120f51 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -819,3 +819,37 @@ def get_function_policy(): assert isinstance(response['Policy'], str) res = json.loads(response['Policy']) assert res['Statement'][0]['Action'] == 'lambda:InvokeFunction' + + + +@mock_lambda +@mock_s3 +def test_list_versions_by_function(): + s3_conn = boto3.client('s3', 'us-west-2') + s3_conn.create_bucket(Bucket='test-bucket') + + zip_content = get_test_zip_file2() + s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) + conn = boto3.client('lambda', 'us-west-2') + + conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'S3Bucket': 'test-bucket', + 'S3Key': 'test.zip', + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + conn.publish_version(FunctionName='testFunction') + + versions = conn.list_versions_by_function(FunctionName='testFunction') + + assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' + diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 8bccae27a..520c7262c 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -717,3 +717,60 @@ def test_cancel_key_deletion(): assert result["KeyMetadata"]["Enabled"] == False assert result["KeyMetadata"]["KeyState"] == 'Disabled' assert 'DeletionDate' not in result["KeyMetadata"] + + +@mock_kms +def test_update_key_description(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(Description='old_description') + key_id = key['KeyMetadata']['KeyId'] + + result = client.update_key_description(KeyId=key_id, Description='new_description') + assert 'ResponseMetadata' in result + + +@mock_kms +def test_tag_resource(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(Description='cancel-key-deletion') + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + + keyid = response['KeyId'] + response = client.tag_resource( + KeyId=keyid, + Tags=[ + { + 'TagKey': 'string', + 'TagValue': 'string' + }, + ] + ) + + # Shouldn't have any data, just header + assert len(response.keys()) == 1 + + +@mock_kms +def test_list_resource_tags(): + client = boto3.client('kms', region_name='us-east-1') + key = client.create_key(Description='cancel-key-deletion') + response = client.schedule_key_deletion( + KeyId=key['KeyMetadata']['KeyId'] + ) + + keyid = response['KeyId'] + response = client.tag_resource( + KeyId=keyid, + Tags=[ + { + 'TagKey': 'string', + 'TagValue': 'string' + }, + ] + ) + + response = client.list_resource_tags(KeyId=keyid) + assert response['Tags'][0]['TagKey'] == 'string' + assert response['Tags'][0]['TagValue'] == 'string' From 0a3ff94e66da2bacb27e8a788034ae2c8d0e29be Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sat, 16 Feb 2019 09:37:27 -0600 Subject: [PATCH 02/22] Update kms and lambda to work with terraform --- moto/kms/models.py | 2 -- moto/kms/responses.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index fb4040617..9d13589c1 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -64,7 +64,6 @@ class Key(BaseModel): ) key.key_rotation_status = properties['EnableKeyRotation'] key.enabled = properties['Enabled'] - return key def get_cfn_attribute(self, attribute_name): @@ -93,7 +92,6 @@ class KmsBackend(BaseBackend): key = self.keys[self.get_key_id(key_id)] key.tags = tags - def list_resource_tags(self, key_id): key = self.keys[self.get_key_id(key_id)] return key.tags diff --git a/moto/kms/responses.py b/moto/kms/responses.py index b66ca267c..2674f765c 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -42,13 +42,13 @@ class KmsResponse(BaseResponse): key_id = self.parameters.get('KeyId') description = self.parameters.get('Description') - self.kms_backend.update_key_description(key_id,description) + self.kms_backend.update_key_description(key_id, description) return json.dumps(None) def tag_resource(self): key_id = self.parameters.get('KeyId') tags = self.parameters.get('Tags') - self.kms_backend.tag_resource(key_id,tags) + self.kms_backend.tag_resource(key_id, tags) return json.dumps({}) def list_resource_tags(self): From 5372e6840fabdd0394135e647dc07f8c13178a31 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sat, 16 Feb 2019 12:37:46 -0600 Subject: [PATCH 03/22] Increasing code coverage --- moto/awslambda/responses.py | 8 +++-- tests/test_awslambda/test_lambda.py | 53 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index bcd2da903..d4eb73bc3 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -160,9 +160,11 @@ class LambdaResponse(BaseResponse): 'Versions': [] } - for fn in self.lambda_backend.list_versions_by_function(function_name): - json_data = fn.get_configuration() - result['Versions'].append(json_data) + functions = self.lambda_backend.list_versions_by_function(function_name) + if functions: + for fn in functions: + json_data = fn.get_configuration() + result['Versions'].append(json_data) return 200, {}, json.dumps(result) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 9c5120f51..c05f9f0ac 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -853,3 +853,56 @@ def test_list_versions_by_function(): assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' + + +@mock_lambda +@mock_s3 +def test_create_function_with_already_exists(): + s3_conn = boto3.client('s3', 'us-west-2') + s3_conn.create_bucket(Bucket='test-bucket') + + zip_content = get_test_zip_file2() + s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) + conn = boto3.client('lambda', 'us-west-2') + + conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'S3Bucket': 'test-bucket', + 'S3Key': 'test.zip', + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + response = conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'S3Bucket': 'test-bucket', + 'S3Key': 'test.zip', + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + assert response['FunctionName'] == 'testFunction' + + +@mock_lambda +@mock_s3 +def test_list_versions_by_function_for_nonexistent_function(): + conn = boto3.client('lambda', 'us-west-2') + + versions = conn.list_versions_by_function(FunctionName='testFunction') + + assert len(versions['Versions']) == 0 \ No newline at end of file From 8ad28f8400b6deae46cb666c56758278035a2478 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sat, 16 Feb 2019 20:53:27 -0600 Subject: [PATCH 04/22] Adding additional tests to increase coverage --- file.tmp | 9 ++++ moto/packages/httpretty/core.py | 60 +++++++++++++++++++++++++++ moto/packages/httpretty/http.py | 3 +- tests/test_packages/__init__.py | 8 ++++ tests/test_packages/test_httpretty.py | 37 +++++++++++++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 file.tmp create mode 100644 tests/test_packages/__init__.py create mode 100644 tests/test_packages/test_httpretty.py diff --git a/file.tmp b/file.tmp new file mode 100644 index 000000000..80053c647 --- /dev/null +++ b/file.tmp @@ -0,0 +1,9 @@ + + AWSTemplateFormatVersion: '2010-09-09' + Description: Simple CloudFormation Test Template + Resources: + S3Bucket: + Type: AWS::S3::Bucket + Properties: + AccessControl: PublicRead + BucketName: cf-test-bucket-1 diff --git a/moto/packages/httpretty/core.py b/moto/packages/httpretty/core.py index 8ad9168a5..168f18431 100644 --- a/moto/packages/httpretty/core.py +++ b/moto/packages/httpretty/core.py @@ -72,6 +72,19 @@ from datetime import datetime from datetime import timedelta from errno import EAGAIN +import logging +from inspect import currentframe +import inspect + +logging.basicConfig(filename='/tmp/models.log',level=logging.DEBUG) + +DEBUG=0 + +def get_linenumber(): + cf = currentframe() + return " - "+str(cf.f_back.f_lineno) + + # Some versions of python internally shadowed the # SocketType variable incorrectly https://bugs.python.org/issue20386 BAD_SOCKET_SHADOW = socket.socket != socket.SocketType @@ -155,15 +168,34 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): """ def __init__(self, headers, body=''): + + if DEBUG: + logging.debug('__init__ - ' + ' -caller: ' + str( + inspect.stack()[1][3]) + "-" + get_linenumber()) + logging.debug('headers: '+str(headers)) + # first of all, lets make sure that if headers or body are # unicode strings, it must be converted into a utf-8 encoded # byte string self.raw_headers = utf8(headers.strip()) + + if DEBUG: + logging.debug('raw_headers: '+str(self.raw_headers)) + self.body = utf8(body) + if DEBUG: + logging.debug('body: '+str(self.body)) + # Now let's concatenate the headers with the body, and create # `rfile` based on it self.rfile = StringIO(b'\r\n\r\n'.join([self.raw_headers, self.body])) + + if DEBUG: + logging.debug('rfile: '+str(self.rfile)) + + self.wfile = StringIO() # Creating `wfile` as an empty # StringIO, just to avoid any real # I/O calls @@ -171,6 +203,10 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): # parsing the request line preemptively self.raw_requestline = self.rfile.readline() + if DEBUG: + logging.debug('raw_requestline: '+str(self.raw_requestline)) + + # initiating the error attributes with None self.error_code = None self.error_message = None @@ -182,6 +218,9 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): # making the HTTP method string available as the command self.method = self.command + if DEBUG: + logging.debug('method: '+str(self.method)) + # Now 2 convenient attributes for the HTTPretty API: # `querystring` holds a dictionary with the parsed query string @@ -207,8 +246,23 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): ) def parse_querystring(self, qs): + + if DEBUG: + logging.debug('parse_querystring - ' + ' -caller: ' + str( + inspect.stack()[1][3]) + "-" + get_linenumber()) + logging.debug('qs: '+str(qs)) + expanded = unquote_utf8(qs) + + if DEBUG: + logging.debug('expanded: '+str(expanded)) + parsed = parse_qs(expanded) + + if DEBUG: + logging.debug('parsed: '+str(parsed)) + result = {} for k in parsed: result[k] = list(map(decode_utf8, parsed[k])) @@ -218,6 +272,12 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): def parse_request_body(self, body): """ Attempt to parse the post based on the content-type passed. Return the regular body if not """ + if DEBUG: + logging.debug('parse_request_body - ' + ' -caller: ' + str( + inspect.stack()[1][3]) + "-" + get_linenumber()) + logging.debug('body: '+str(body)) + PARSING_FUNCTIONS = { 'application/json': json.loads, 'text/json': json.loads, diff --git a/moto/packages/httpretty/http.py b/moto/packages/httpretty/http.py index 7e9a56885..17d580f5f 100644 --- a/moto/packages/httpretty/http.py +++ b/moto/packages/httpretty/http.py @@ -29,7 +29,6 @@ import re from .compat import BaseClass from .utils import decode_utf8 - STATUSES = { 100: "Continue", 101: "Switching Protocols", @@ -134,6 +133,7 @@ def parse_requestline(s): ... ValueError: Not a Request-Line """ + methods = '|'.join(HttpBaseClass.METHODS) m = re.match(r'(' + methods + ')\s+(.*)\s+HTTP/(1.[0|1])', s, re.I) if m: @@ -146,6 +146,7 @@ def last_requestline(sent_data): """ Find the last line in sent_data that can be parsed with parse_requestline """ + for line in reversed(sent_data): try: parse_requestline(decode_utf8(line)) diff --git a/tests/test_packages/__init__.py b/tests/test_packages/__init__.py new file mode 100644 index 000000000..bf582e0b3 --- /dev/null +++ b/tests/test_packages/__init__.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals + +import logging +# Disable extra logging for tests +logging.getLogger('boto').setLevel(logging.CRITICAL) +logging.getLogger('boto3').setLevel(logging.CRITICAL) +logging.getLogger('botocore').setLevel(logging.CRITICAL) +logging.getLogger('nose').setLevel(logging.CRITICAL) diff --git a/tests/test_packages/test_httpretty.py b/tests/test_packages/test_httpretty.py new file mode 100644 index 000000000..48277a2de --- /dev/null +++ b/tests/test_packages/test_httpretty.py @@ -0,0 +1,37 @@ +# #!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import mock + +from moto.packages.httpretty.core import HTTPrettyRequest, fake_gethostname, fake_gethostbyname + + +def test_parse_querystring(): + + core = HTTPrettyRequest(headers='test test HTTP/1.1') + + qs = 'test test' + response = core.parse_querystring(qs) + + assert response == {} + +def test_parse_request_body(): + core = HTTPrettyRequest(headers='test test HTTP/1.1') + + qs = 'test' + response = core.parse_request_body(qs) + + assert response == 'test' + +def test_fake_gethostname(): + + response = fake_gethostname() + + assert response == 'localhost' + +def test_fake_gethostbyname(): + + host = 'test' + response = fake_gethostbyname(host=host) + + assert response == '127.0.0.1' \ No newline at end of file From b29fd4a997a105f6c3a3b19a87852a56e75db65b Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sat, 16 Feb 2019 20:53:54 -0600 Subject: [PATCH 05/22] Adding additional tests to increase coverage --- file.tmp | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 file.tmp diff --git a/file.tmp b/file.tmp deleted file mode 100644 index 80053c647..000000000 --- a/file.tmp +++ /dev/null @@ -1,9 +0,0 @@ - - AWSTemplateFormatVersion: '2010-09-09' - Description: Simple CloudFormation Test Template - Resources: - S3Bucket: - Type: AWS::S3::Bucket - Properties: - AccessControl: PublicRead - BucketName: cf-test-bucket-1 From e35d99ff09023e09b2e252db4ac1e1d58863dfb5 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 09:25:35 -0600 Subject: [PATCH 06/22] Trying to improve coverage --- file.tmp | 9 ++++++ tests/test_batch/test_batch.py | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 file.tmp diff --git a/file.tmp b/file.tmp new file mode 100644 index 000000000..80053c647 --- /dev/null +++ b/file.tmp @@ -0,0 +1,9 @@ + + AWSTemplateFormatVersion: '2010-09-09' + Description: Simple CloudFormation Test Template + Resources: + S3Bucket: + Type: AWS::S3::Bucket + Properties: + AccessControl: PublicRead + BucketName: cf-test-bucket-1 diff --git a/tests/test_batch/test_batch.py b/tests/test_batch/test_batch.py index ec24cd911..310ac0b48 100644 --- a/tests/test_batch/test_batch.py +++ b/tests/test_batch/test_batch.py @@ -323,6 +323,54 @@ def test_create_job_queue(): resp.should.contain('jobQueues') len(resp['jobQueues']).should.equal(0) + # Create job queue which already exists + try: + resp = batch_client.create_job_queue( + jobQueueName='test_job_queue', + state='ENABLED', + priority=123, + computeEnvironmentOrder=[ + { + 'order': 123, + 'computeEnvironment': arn + }, + ] + ) + + except ClientError as err: + err.response['Error']['Code'].should.equal('ClientException') + + + # Create job queue with incorrect state + try: + resp = batch_client.create_job_queue( + jobQueueName='test_job_queue2', + state='JUNK', + priority=123, + computeEnvironmentOrder=[ + { + 'order': 123, + 'computeEnvironment': arn + }, + ] + ) + + except ClientError as err: + err.response['Error']['Code'].should.equal('ClientException') + + # Create job queue with no compute env + try: + resp = batch_client.create_job_queue( + jobQueueName='test_job_queue3', + state='JUNK', + priority=123, + computeEnvironmentOrder=[ + + ] + ) + + except ClientError as err: + err.response['Error']['Code'].should.equal('ClientException') @mock_ec2 @mock_ecs @@ -397,6 +445,17 @@ def test_update_job_queue(): len(resp['jobQueues']).should.equal(1) resp['jobQueues'][0]['priority'].should.equal(5) + batch_client.update_job_queue( + jobQueue='test_job_queue', + priority=5 + ) + + resp = batch_client.describe_job_queues() + resp.should.contain('jobQueues') + len(resp['jobQueues']).should.equal(1) + resp['jobQueues'][0]['priority'].should.equal(5) + + @mock_ec2 @mock_ecs From 921a993330a96130b12331f29944d93578a8bd2c Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 14:30:43 -0600 Subject: [PATCH 07/22] cleaning up files --- file.tmp | 9 ---- moto/iam/models.py | 13 +++-- moto/iam/responses.py | 79 +++++++++++++++++++++++++++-- moto/packages/httpretty/core.py | 62 +--------------------- moto/packages/httpretty/http.py | 2 - tests/test_awslambda/test_lambda.py | 2 - tests/test_kms/test_kms.py | 1 - 7 files changed, 85 insertions(+), 83 deletions(-) delete mode 100644 file.tmp diff --git a/file.tmp b/file.tmp deleted file mode 100644 index 80053c647..000000000 --- a/file.tmp +++ /dev/null @@ -1,9 +0,0 @@ - - AWSTemplateFormatVersion: '2010-09-09' - Description: Simple CloudFormation Test Template - Resources: - S3Bucket: - Type: AWS::S3::Bucket - Properties: - AccessControl: PublicRead - BucketName: cf-test-bucket-1 diff --git a/moto/iam/models.py b/moto/iam/models.py index 86a5d7a32..a5f40b996 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -51,8 +51,8 @@ class Policy(BaseModel): self.default_version_id = default_version_id or 'v1' self.versions = [PolicyVersion(self.arn, document, True)] - self.create_datetime = datetime.now(pytz.utc) - self.update_datetime = datetime.now(pytz.utc) + self.create_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") + self.update_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") class SAMLProvider(BaseModel): @@ -76,7 +76,7 @@ class PolicyVersion(object): self.is_default = is_default self.version_id = 'v1' - self.create_datetime = datetime.now(pytz.utc) + self.create_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") class ManagedPolicy(Policy): @@ -132,8 +132,9 @@ class Role(BaseModel): self.path = path or '/' self.policies = {} self.managed_policies = {} - self.create_date = datetime.now(pytz.utc) + self.create_date = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") self.tags = {} + self.description = "" @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): @@ -473,6 +474,10 @@ class IAMBackend(BaseBackend): policy = arns[policy_arn] policy.attach_to(self.get_role(role_name)) + def update_role_description(self, role_name, role_description): + role = self.get_role(role_name) + role.description = role_description + def detach_role_policy(self, policy_arn, role_name): arns = dict((p.arn, p) for p in self.managed_policies.values()) try: diff --git a/moto/iam/responses.py b/moto/iam/responses.py index e3cc4b90b..e624c18c3 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -107,6 +107,10 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_POLICIES_TEMPLATE) return template.render(policies=policies, marker=marker) + def list_entities_for_policy(self): + template = self.response_template(LIST_ENTITIES_FOR_POLICY_TEMPLATE) + return template.render() + def create_role(self): role_name = self._get_param('RoleName') path = self._get_param('Path') @@ -169,6 +173,20 @@ class IamResponse(BaseResponse): template = self.response_template(GENERIC_EMPTY_TEMPLATE) return template.render(name="UpdateAssumeRolePolicyResponse") + def update_role_description(self): + role_name = self._get_param('RoleName') + description = self._get_param('Description') + role = iam_backend.update_role_description(role_name,description) + template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE) + return template.render(role=role) + + def update_role(self): + role_name = self._get_param('RoleName') + description = self._get_param('Description') + role = iam_backend.update_role_description(role_name,description) + template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE) + return template.render(role=role) + def create_policy_version(self): policy_arn = self._get_param('PolicyArn') policy_document = self._get_param('PolicyDocument') @@ -654,6 +672,33 @@ class IamResponse(BaseResponse): template = self.response_template(UNTAG_ROLE_TEMPLATE) return template.render() +LIST_ENTITIES_FOR_POLICY_TEMPLATE = """ + + + + DevRole + + + + + Dev + + + false + + + Alice + + + Bob + + + + + eb358e22-9d1f-11e4-93eb-190ecEXAMPLE + +""" + ATTACH_ROLE_POLICY_TEMPLATE = """ @@ -696,12 +741,12 @@ CREATE_POLICY_TEMPLATE = """ {{ policy.arn }} {{ policy.attachment_count }} - {{ policy.create_datetime.isoformat() }} + {{ policy.create_datetime }} {{ policy.default_version_id }} {{ policy.path }} {{ policy.id }} {{ policy.name }} - {{ policy.update_datetime.isoformat() }} + {{ policy.update_datetime }} @@ -719,8 +764,8 @@ GET_POLICY_TEMPLATE = """ {{ policy.path }} {{ policy.arn }} {{ policy.attachment_count }} - {{ policy.create_datetime.isoformat() }} - {{ policy.update_datetime.isoformat() }} + {{ policy.create_datetime }} + {{ policy.update_datetime }} @@ -898,6 +943,32 @@ GET_ROLE_POLICY_TEMPLATE = """ + + + {{ role.path }} + {{ role.arn }} + {{ role.name }} + {{ role.assume_role_policy_document }} + {{ role.create_date }} + {{ role.id }} + {% if role.tags %} + + {% for tag in role.get_tags() %} + + {{ tag['Key'] }} + {{ tag['Value'] }} + + {% endfor %} + + {% endif %} + + + + df37e965-9967-11e1-a4c3-270EXAMPLE04 + +""" + GET_ROLE_TEMPLATE = """ diff --git a/moto/packages/httpretty/core.py b/moto/packages/httpretty/core.py index 168f18431..4eb92108f 100644 --- a/moto/packages/httpretty/core.py +++ b/moto/packages/httpretty/core.py @@ -72,19 +72,6 @@ from datetime import datetime from datetime import timedelta from errno import EAGAIN -import logging -from inspect import currentframe -import inspect - -logging.basicConfig(filename='/tmp/models.log',level=logging.DEBUG) - -DEBUG=0 - -def get_linenumber(): - cf = currentframe() - return " - "+str(cf.f_back.f_lineno) - - # Some versions of python internally shadowed the # SocketType variable incorrectly https://bugs.python.org/issue20386 BAD_SOCKET_SHADOW = socket.socket != socket.SocketType @@ -168,34 +155,15 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): """ def __init__(self, headers, body=''): - - if DEBUG: - logging.debug('__init__ - ' - ' -caller: ' + str( - inspect.stack()[1][3]) + "-" + get_linenumber()) - logging.debug('headers: '+str(headers)) - # first of all, lets make sure that if headers or body are # unicode strings, it must be converted into a utf-8 encoded # byte string self.raw_headers = utf8(headers.strip()) - - if DEBUG: - logging.debug('raw_headers: '+str(self.raw_headers)) - self.body = utf8(body) - if DEBUG: - logging.debug('body: '+str(self.body)) - # Now let's concatenate the headers with the body, and create # `rfile` based on it self.rfile = StringIO(b'\r\n\r\n'.join([self.raw_headers, self.body])) - - if DEBUG: - logging.debug('rfile: '+str(self.rfile)) - - self.wfile = StringIO() # Creating `wfile` as an empty # StringIO, just to avoid any real # I/O calls @@ -203,10 +171,6 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): # parsing the request line preemptively self.raw_requestline = self.rfile.readline() - if DEBUG: - logging.debug('raw_requestline: '+str(self.raw_requestline)) - - # initiating the error attributes with None self.error_code = None self.error_message = None @@ -218,9 +182,6 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): # making the HTTP method string available as the command self.method = self.command - if DEBUG: - logging.debug('method: '+str(self.method)) - # Now 2 convenient attributes for the HTTPretty API: # `querystring` holds a dictionary with the parsed query string @@ -246,23 +207,8 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): ) def parse_querystring(self, qs): - - if DEBUG: - logging.debug('parse_querystring - ' - ' -caller: ' + str( - inspect.stack()[1][3]) + "-" + get_linenumber()) - logging.debug('qs: '+str(qs)) - expanded = unquote_utf8(qs) - - if DEBUG: - logging.debug('expanded: '+str(expanded)) - parsed = parse_qs(expanded) - - if DEBUG: - logging.debug('parsed: '+str(parsed)) - result = {} for k in parsed: result[k] = list(map(decode_utf8, parsed[k])) @@ -272,12 +218,6 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass): def parse_request_body(self, body): """ Attempt to parse the post based on the content-type passed. Return the regular body if not """ - if DEBUG: - logging.debug('parse_request_body - ' - ' -caller: ' + str( - inspect.stack()[1][3]) + "-" + get_linenumber()) - logging.debug('body: '+str(body)) - PARSING_FUNCTIONS = { 'application/json': json.loads, 'text/json': json.loads, @@ -1173,4 +1113,4 @@ def httprettified(test): if isinstance(test, ClassTypes): return decorate_class(test) - return decorate_callable(test) + return decorate_callable(test) \ No newline at end of file diff --git a/moto/packages/httpretty/http.py b/moto/packages/httpretty/http.py index 17d580f5f..ee1625905 100644 --- a/moto/packages/httpretty/http.py +++ b/moto/packages/httpretty/http.py @@ -133,7 +133,6 @@ def parse_requestline(s): ... ValueError: Not a Request-Line """ - methods = '|'.join(HttpBaseClass.METHODS) m = re.match(r'(' + methods + ')\s+(.*)\s+HTTP/(1.[0|1])', s, re.I) if m: @@ -146,7 +145,6 @@ def last_requestline(sent_data): """ Find the last line in sent_data that can be parsed with parse_requestline """ - for line in reversed(sent_data): try: parse_requestline(decode_utf8(line)) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index c05f9f0ac..71f6746a9 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -821,7 +821,6 @@ def get_function_policy(): assert res['Statement'][0]['Action'] == 'lambda:InvokeFunction' - @mock_lambda @mock_s3 def test_list_versions_by_function(): @@ -854,7 +853,6 @@ def test_list_versions_by_function(): assert versions['Versions'][0]['FunctionArn'] == 'arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST' - @mock_lambda @mock_s3 def test_create_function_with_already_exists(): diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 520c7262c..0f7bab4cd 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import os, re - import boto3 import boto.kms from boto.exception import JSONResponseError From 37cb5ab4e69f1ef977256aba07f38f546d40e918 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 14:36:53 -0600 Subject: [PATCH 08/22] Add test for roles --- moto/iam/responses.py | 5 +++-- tests/test_iam/test_iam.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index e624c18c3..c981f9b35 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -176,14 +176,14 @@ class IamResponse(BaseResponse): def update_role_description(self): role_name = self._get_param('RoleName') description = self._get_param('Description') - role = iam_backend.update_role_description(role_name,description) + role = iam_backend.update_role_description(role_name, description) template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE) return template.render(role=role) def update_role(self): role_name = self._get_param('RoleName') description = self._get_param('Description') - role = iam_backend.update_role_description(role_name,description) + role = iam_backend.update_role_description(role_name, description) template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE) return template.render(role=role) @@ -672,6 +672,7 @@ class IamResponse(BaseResponse): template = self.response_template(UNTAG_ROLE_TEMPLATE) return template.render() + LIST_ENTITIES_FOR_POLICY_TEMPLATE = """ diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index d5f1bb4f9..80a7fe99e 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1151,3 +1151,30 @@ def test_untag_role(): # With a role that doesn't exist: with assert_raises(ClientError): conn.untag_role(RoleName='notarole', TagKeys=['somevalue']) + + +@mock_iam() +def test_update_role_description(): + conn = boto3.client('iam', region_name='us-east-1') + + with assert_raises(ClientError): + conn.delete_role(RoleName="my-role") + + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + role = conn.get_role(RoleName="my-role") + response = conn.update_role_description(RoleName="my-role", Description="test") + + assert response['Role']['RoleName'] == 'my-role' + +@mock_iam() +def test_update_role(): + conn = boto3.client('iam', region_name='us-east-1') + + with assert_raises(ClientError): + conn.delete_role(RoleName="my-role") + + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + role = conn.get_role(RoleName="my-role") + response = conn.update_role_description(RoleName="my-role", Description="test") + + assert response['Role']['RoleName'] == 'my-role' \ No newline at end of file From 31258e9e9e35f254259ad20759a39a88423266fe Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 15:23:59 -0600 Subject: [PATCH 09/22] Add test for roles --- moto/iam/models.py | 8 ++++---- moto/iam/responses.py | 12 ++++++------ tests/test_iam/test_iam.py | 4 +--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/moto/iam/models.py b/moto/iam/models.py index a5f40b996..e0f725299 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -51,8 +51,8 @@ class Policy(BaseModel): self.default_version_id = default_version_id or 'v1' self.versions = [PolicyVersion(self.arn, document, True)] - self.create_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") - self.update_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") + self.create_datetime = datetime.now(pytz.utc) + self.update_datetime = datetime.now(pytz.utc) class SAMLProvider(BaseModel): @@ -76,7 +76,7 @@ class PolicyVersion(object): self.is_default = is_default self.version_id = 'v1' - self.create_datetime = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") + self.create_datetime = datetime.now(pytz.utc) class ManagedPolicy(Policy): @@ -132,7 +132,7 @@ class Role(BaseModel): self.path = path or '/' self.policies = {} self.managed_policies = {} - self.create_date = datetime.strftime(datetime.utcnow(), "%Y-%m-%dT%H:%M:%SZ") + self.create_date = datetime.now(pytz.utc) self.tags = {} self.description = "" diff --git a/moto/iam/responses.py b/moto/iam/responses.py index c981f9b35..5be07430f 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -742,12 +742,12 @@ CREATE_POLICY_TEMPLATE = """ {{ policy.arn }} {{ policy.attachment_count }} - {{ policy.create_datetime }} + {{ policy.create_datetime.isoformat() }} {{ policy.default_version_id }} {{ policy.path }} {{ policy.id }} {{ policy.name }} - {{ policy.update_datetime }} + {{ policy.update_datetime.isoformat() }} @@ -765,8 +765,8 @@ GET_POLICY_TEMPLATE = """ {{ policy.path }} {{ policy.arn }} {{ policy.attachment_count }} - {{ policy.create_datetime }} - {{ policy.update_datetime }} + {{ policy.create_datetime.isoformat() }} + {{ policy.update_datetime.isoformat() }} @@ -858,7 +858,7 @@ LIST_POLICIES_TEMPLATE = """ {{ policy.path }} {{ policy.id }} {{ policy.name }} - {{ policy.update_datetime.isoformat() }} + {{ policy.update_datetime }} {% endfor %} @@ -951,7 +951,7 @@ UPDATE_ROLE_DESCRIPTION_TEMPLATE = """ Date: Sun, 17 Feb 2019 15:35:49 -0600 Subject: [PATCH 10/22] Add test for roles --- moto/iam/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moto/iam/models.py b/moto/iam/models.py index e0f725299..f71ad5352 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -477,6 +477,7 @@ class IAMBackend(BaseBackend): def update_role_description(self, role_name, role_description): role = self.get_role(role_name) role.description = role_description + return role def detach_role_policy(self, policy_arn, role_name): arns = dict((p.arn, p) for p in self.managed_policies.values()) From 6e7bd088b30452bff0f1601cde089cdfd9a8ee4d Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 16:04:28 -0600 Subject: [PATCH 11/22] Add test for roles --- tests/test_iam/test_iam.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 85c51012f..f728f0dca 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1176,3 +1176,21 @@ def test_update_role(): response = conn.update_role_description(RoleName="my-role", Description="test") assert response['Role']['RoleName'] == 'my-role' + +@mock_iam() +def test_list_entities_for_policy(): + conn = boto3.client('iam', region_name='us-east-1') + + with assert_raises(ClientError): + conn.delete_role(RoleName="my-role") + + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + + role = conn.get_role(RoleName="my-role") + arn = role.get('Role').get('Arn') + + response = conn.list_entities_for_policy( + PolicyArn=arn + ) + + assert response['PolicyGroups'][0]['GroupName'] == 'Dev' From 0e73cddf2fb8039e056b4b67a6948f8c4eefcae9 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 16:25:49 -0600 Subject: [PATCH 12/22] Add test for roles --- tests/test_iam/test_iam.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index f728f0dca..be25c2e96 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1174,7 +1174,6 @@ def test_update_role(): conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") response = conn.update_role_description(RoleName="my-role", Description="test") - assert response['Role']['RoleName'] == 'my-role' @mock_iam() @@ -1192,5 +1191,4 @@ def test_list_entities_for_policy(): response = conn.list_entities_for_policy( PolicyArn=arn ) - assert response['PolicyGroups'][0]['GroupName'] == 'Dev' From 63b692356d07a65468ae0f507d5390397e07663c Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 16:49:54 -0600 Subject: [PATCH 13/22] Fix policy date --- moto/iam/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 5be07430f..7ee4b6345 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -858,7 +858,7 @@ LIST_POLICIES_TEMPLATE = """ {{ policy.path }} {{ policy.id }} {{ policy.name }} - {{ policy.update_datetime }} + {{ policy.update_datetime.isoformat() }} {% endfor %} From 37a765db8d13faa30d64240ff2dc7b098c0237d7 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 17:12:27 -0600 Subject: [PATCH 14/22] Fix policy date --- tests/test_iam/test_iam.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index be25c2e96..1114f72de 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1176,6 +1176,17 @@ def test_update_role(): response = conn.update_role_description(RoleName="my-role", Description="test") assert response['Role']['RoleName'] == 'my-role' +@mock_iam() +def test_update_role(): + conn = boto3.client('iam', region_name='us-east-1') + + with assert_raises(ClientError): + conn.delete_role(RoleName="my-role") + + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + response = conn.update_role(RoleName="my-role", Description="test") + assert response['Role']['RoleName'] == 'my-role' + @mock_iam() def test_list_entities_for_policy(): conn = boto3.client('iam', region_name='us-east-1') From ce7b5ebf54021f64cf855f17f60a77f4b09262ce Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 21:37:33 -0600 Subject: [PATCH 15/22] Fix policy date --- file.tmp | 9 +++++++++ moto/iam/models.py | 5 +++++ moto/iam/responses.py | 12 ++++++++++-- tests/test_iam/test_iam.py | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 file.tmp diff --git a/file.tmp b/file.tmp new file mode 100644 index 000000000..80053c647 --- /dev/null +++ b/file.tmp @@ -0,0 +1,9 @@ + + AWSTemplateFormatVersion: '2010-09-09' + Description: Simple CloudFormation Test Template + Resources: + S3Bucket: + Type: AWS::S3::Bucket + Properties: + AccessControl: PublicRead + BucketName: cf-test-bucket-1 diff --git a/moto/iam/models.py b/moto/iam/models.py index f71ad5352..80c7da29a 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -479,6 +479,11 @@ class IAMBackend(BaseBackend): role.description = role_description return role + def update_role(self, role_name, role_description): + role = self.get_role(role_name) + role.description = role_description + return role + def detach_role_policy(self, policy_arn, role_name): arns = dict((p.arn, p) for p in self.managed_policies.values()) try: diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 7ee4b6345..26781ab6f 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -183,8 +183,8 @@ class IamResponse(BaseResponse): def update_role(self): role_name = self._get_param('RoleName') description = self._get_param('Description') - role = iam_backend.update_role_description(role_name, description) - template = self.response_template(UPDATE_ROLE_DESCRIPTION_TEMPLATE) + role = iam_backend.update_role(role_name, description) + template = self.response_template(UPDATE_ROLE_TEMPLATE) return template.render(role=role) def create_policy_version(self): @@ -944,6 +944,14 @@ GET_ROLE_POLICY_TEMPLATE = """ + + + + df37e965-9967-11e1-a4c3-270EXAMPLE04 + +""" + UPDATE_ROLE_DESCRIPTION_TEMPLATE = """ diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 1114f72de..77ba17a5a 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1185,7 +1185,7 @@ def test_update_role(): conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") response = conn.update_role(RoleName="my-role", Description="test") - assert response['Role']['RoleName'] == 'my-role' + assert len(response.keys()) == 1 @mock_iam() def test_list_entities_for_policy(): From 8048e39dc0053d3772fc9d586b05f7fe7fa338be Mon Sep 17 00:00:00 2001 From: William Rubel Date: Sun, 17 Feb 2019 22:32:39 -0600 Subject: [PATCH 16/22] Fix policy date --- tests/test_awslambda/test_lambda.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 71f6746a9..171f6360a 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -12,6 +12,8 @@ import sure # noqa from freezegun import freeze_time from moto import mock_lambda, mock_s3, mock_ec2, mock_sns, mock_logs, settings +from nose.tools import assert_raises +from botocore.exceptions import ClientError _lambda_region = 'us-west-2' @@ -397,6 +399,11 @@ def test_get_function(): result = conn.get_function(FunctionName='testFunction', Qualifier='$LATEST') result['Configuration']['Version'].should.equal('$LATEST') + # Test get function when can't find function name + with assert_raises(ClientError): + conn.get_function(FunctionName='junk', Qualifier='$LATEST') + + @mock_lambda @mock_s3 From e229d248ad4d207a00c84ac441ab3517157b3444 Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 08:52:37 -0600 Subject: [PATCH 17/22] Trying to improve coverage --- file.tmp | 9 --------- tests/test_awslambda/test_lambda.py | 2 +- tests/test_events/test_events.py | 8 +++++++- 3 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 file.tmp diff --git a/file.tmp b/file.tmp deleted file mode 100644 index 80053c647..000000000 --- a/file.tmp +++ /dev/null @@ -1,9 +0,0 @@ - - AWSTemplateFormatVersion: '2010-09-09' - Description: Simple CloudFormation Test Template - Resources: - S3Bucket: - Type: AWS::S3::Bucket - Properties: - AccessControl: PublicRead - BucketName: cf-test-bucket-1 diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 171f6360a..435d394e3 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -910,4 +910,4 @@ def test_list_versions_by_function_for_nonexistent_function(): versions = conn.list_versions_by_function(FunctionName='testFunction') - assert len(versions['Versions']) == 0 \ No newline at end of file + assert len(versions['Versions']) == 0 diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 80630c5b8..810e0bbe5 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -7,7 +7,6 @@ from moto.events import mock_events from botocore.exceptions import ClientError from nose.tools import assert_raises - RULES = [ {'Name': 'test1', 'ScheduleExpression': 'rate(5 minutes)'}, {'Name': 'test2', 'ScheduleExpression': 'rate(1 minute)'}, @@ -109,6 +108,13 @@ def test_enable_disable_rule(): rule = client.describe_rule(Name=rule_name) assert(rule['State'] == 'ENABLED') + # Test invalid name + try: + client.enable_rule(Name='junk') + + except ClientError as ce: + assert ce.response['Error']['Code'] == 'ResourceNotFoundException' + @mock_events def test_list_rule_names_by_target(): From c46bc9ae83a180d8c451d907cfa430c97680ddab Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 09:15:07 -0600 Subject: [PATCH 18/22] Trying to improve coverage --- tests/test_events/test_events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 810e0bbe5..a9d90ec32 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -1,5 +1,4 @@ import random - import boto3 import json From 37845e41a687441815606e7bfe2412288488b32a Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 09:44:48 -0600 Subject: [PATCH 19/22] Trying to improve coverage --- tests/test_awslambda/test_lambda.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 435d394e3..7f3b44b79 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -907,7 +907,6 @@ def test_create_function_with_already_exists(): @mock_s3 def test_list_versions_by_function_for_nonexistent_function(): conn = boto3.client('lambda', 'us-west-2') - versions = conn.list_versions_by_function(FunctionName='testFunction') assert len(versions['Versions']) == 0 From e9d8021c86e15da9dc1e5f634ecf877d5fb147ff Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 21:20:29 -0600 Subject: [PATCH 20/22] Fixing list entities for policy --- moto/iam/models.py | 10 +++++ moto/iam/responses.py | 90 ++++++++++++++++++++++++++++++++------ tests/test_iam/test_iam.py | 79 +++++++++++++++++++++++++++++---- 3 files changed, 158 insertions(+), 21 deletions(-) diff --git a/moto/iam/models.py b/moto/iam/models.py index 80c7da29a..92ac19da7 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -892,6 +892,16 @@ class IAMBackend(BaseBackend): return users + def list_roles(self, path_prefix, marker, max_items): + roles = None + try: + roles = self.roles.values() + except KeyError: + raise IAMNotFoundException( + "Users {0}, {1}, {2} not found".format(path_prefix, marker, max_items)) + + return roles + def upload_signing_certificate(self, user_name, body): user = self.get_user(user_name) cert_id = random_resource_id(size=32) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 26781ab6f..278f13f2d 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -108,8 +108,69 @@ class IamResponse(BaseResponse): return template.render(policies=policies, marker=marker) def list_entities_for_policy(self): + policy_arn = self._get_param('PolicyArn') + + # Options 'User'|'Role'|'Group'|'LocalManagedPolicy'|'AWSManagedPolicy + entity = self._get_param('EntityFilter') + path_prefix = self._get_param('PathPrefix') + policy_usage_filter = self._get_param('PolicyUsageFilter') + marker = self._get_param('Marker') + max_items = self._get_param('MaxItems') + + entity_roles = [] + entity_groups = [] + entity_users = [] + + if entity == 'User': + users = iam_backend.list_users(path_prefix, marker, max_items) + if users: + for user in users: + for p in user.managed_policies: + if p == policy_arn: + entity_users.append(user.name) + + elif entity == 'Role': + roles = iam_backend.list_roles(path_prefix, marker, max_items) + if roles: + for role in roles: + for p in role.managed_policies: + if p == policy_arn: + entity_roles.append(role.name) + + elif entity == 'Group': + groups = iam_backend.list_groups() + if groups: + for group in groups: + for p in group.managed_policies: + if p == policy_arn: + entity_groups.append(group.name) + + elif entity == 'LocalManagedPolicy' or entity == 'AWSManagedPolicy': + users = iam_backend.list_users(path_prefix, marker, max_items) + if users: + for user in users: + for p in user.managed_policies: + if p == policy_arn: + entity_users.append(user.name) + + roles = iam_backend.list_roles(path_prefix, marker, max_items) + if roles: + for role in roles: + for p in role.managed_policies: + if p == policy_arn: + entity_roles.append(role.name) + + groups = iam_backend.list_groups() + if groups: + for group in groups: + for p in group.managed_policies: + if p == policy_arn: + entity_groups.append(group.name) + + template = self.response_template(LIST_ENTITIES_FOR_POLICY_TEMPLATE) - return template.render() + return template.render(roles=entity_roles, users=entity_users, groups=entity_groups) + def create_role(self): role_name = self._get_param('RoleName') @@ -676,23 +737,26 @@ class IamResponse(BaseResponse): LIST_ENTITIES_FOR_POLICY_TEMPLATE = """ - - DevRole - + {% for role in roles %} + + {{ role }} + + {% endfor %} - - Dev - + {% for group in groups %} + + {{ group }} + + {% endfor %} false - - Alice - - - Bob - + {% for user in users %} + + {{ user }} + + {% endfor %} diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 77ba17a5a..ceec5e06a 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1187,19 +1187,82 @@ def test_update_role(): response = conn.update_role(RoleName="my-role", Description="test") assert len(response.keys()) == 1 + @mock_iam() def test_list_entities_for_policy(): + import json + test_policy = json.dumps({ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "s3:ListBucket", + "Resource": "*", + "Effect": "Allow", + } + ] + }) + conn = boto3.client('iam', region_name='us-east-1') - - with assert_raises(ClientError): - conn.delete_role(RoleName="my-role") - conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + conn.create_user(Path='/', UserName='testUser') + conn.create_group(Path='/', GroupName='testGroup') + conn.create_policy( + PolicyName='testPolicy', + Path='/', + PolicyDocument=test_policy, + Description='Test Policy' + ) - role = conn.get_role(RoleName="my-role") - arn = role.get('Role').get('Arn') + # Attach things to the user and group: + conn.put_user_policy(UserName='testUser', PolicyName='testPolicy', PolicyDocument=test_policy) + conn.put_group_policy(GroupName='testGroup', PolicyName='testPolicy', PolicyDocument=test_policy) + + conn.attach_user_policy(UserName='testUser', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy') + conn.attach_group_policy(GroupName='testGroup', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy') + + conn.add_user_to_group(UserName='testUser', GroupName='testGroup') + + # Add things to the role: + conn.create_instance_profile(InstanceProfileName='ipn') + conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role') + conn.tag_role(RoleName='my-role', Tags=[ + { + 'Key': 'somekey', + 'Value': 'somevalue' + }, + { + 'Key': 'someotherkey', + 'Value': 'someothervalue' + } + ]) + conn.put_role_policy(RoleName='my-role', PolicyName='test-policy', PolicyDocument=test_policy) + conn.attach_role_policy(RoleName='my-role', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy') response = conn.list_entities_for_policy( - PolicyArn=arn + PolicyArn='arn:aws:iam::123456789012:policy/testPolicy', + EntityFilter='Role' ) - assert response['PolicyGroups'][0]['GroupName'] == 'Dev' + assert response['PolicyRoles'] == [{'RoleName': 'my-role'}] + + response = conn.list_entities_for_policy( + PolicyArn='arn:aws:iam::123456789012:policy/testPolicy', + EntityFilter='User', + ) + assert response['PolicyUsers'] == [{'UserName': 'testUser'}] + + response = conn.list_entities_for_policy( + PolicyArn='arn:aws:iam::123456789012:policy/testPolicy', + EntityFilter='Group', + ) + assert response['PolicyGroups'] == [{'GroupName': 'testGroup'}] + + response = conn.list_entities_for_policy( + PolicyArn='arn:aws:iam::123456789012:policy/testPolicy', + EntityFilter='LocalManagedPolicy', + ) + assert response['PolicyGroups'] == [{'GroupName': 'testGroup'}] + assert response['PolicyUsers'] == [{'UserName': 'testUser'}] + assert response['PolicyRoles'] == [{'RoleName': 'my-role'}] + + + From 59deb4d6c0d3199f8d21a881f87e33a18cc95b0b Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 21:25:29 -0600 Subject: [PATCH 21/22] Fixing list entities for policy --- moto/iam/responses.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 278f13f2d..72b2e464c 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -113,7 +113,7 @@ class IamResponse(BaseResponse): # Options 'User'|'Role'|'Group'|'LocalManagedPolicy'|'AWSManagedPolicy entity = self._get_param('EntityFilter') path_prefix = self._get_param('PathPrefix') - policy_usage_filter = self._get_param('PolicyUsageFilter') + #policy_usage_filter = self._get_param('PolicyUsageFilter') marker = self._get_param('Marker') max_items = self._get_param('MaxItems') @@ -167,11 +167,9 @@ class IamResponse(BaseResponse): if p == policy_arn: entity_groups.append(group.name) - template = self.response_template(LIST_ENTITIES_FOR_POLICY_TEMPLATE) return template.render(roles=entity_roles, users=entity_users, groups=entity_groups) - def create_role(self): role_name = self._get_param('RoleName') path = self._get_param('Path') From a5208222b4d017ed8d6f02fe0aed0e2da2dbaa3c Mon Sep 17 00:00:00 2001 From: William Rubel Date: Mon, 18 Feb 2019 21:29:09 -0600 Subject: [PATCH 22/22] Fixing list entities for policy --- moto/iam/responses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 72b2e464c..5b19c9cdc 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -113,7 +113,7 @@ class IamResponse(BaseResponse): # Options 'User'|'Role'|'Group'|'LocalManagedPolicy'|'AWSManagedPolicy entity = self._get_param('EntityFilter') path_prefix = self._get_param('PathPrefix') - #policy_usage_filter = self._get_param('PolicyUsageFilter') + # policy_usage_filter = self._get_param('PolicyUsageFilter') marker = self._get_param('Marker') max_items = self._get_param('MaxItems') @@ -739,7 +739,7 @@ LIST_ENTITIES_FOR_POLICY_TEMPLATE = """ {{ role }} - {% endfor %} + {% endfor %} {% for group in groups %}