diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 6be7375db..b3caf5be3 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -1906,7 +1906,7 @@ - [X] delete_role - [X] delete_role_policy - [ ] delete_saml_provider -- [ ] delete_server_certificate +- [X] delete_server_certificate - [ ] delete_service_linked_role - [ ] delete_service_specific_credential - [ ] delete_signing_certificate diff --git a/moto/acm/responses.py b/moto/acm/responses.py index 431a8cf60..38ebbaaa0 100644 --- a/moto/acm/responses.py +++ b/moto/acm/responses.py @@ -111,16 +111,16 @@ class AWSCertificateManagerResponse(BaseResponse): # actual data try: certificate = base64.standard_b64decode(certificate) - except: + except Exception: return AWSValidationException('The certificate is not PEM-encoded or is not valid.').response() try: private_key = base64.standard_b64decode(private_key) - except: + except Exception: return AWSValidationException('The private key is not PEM-encoded or is not valid.').response() if chain is not None: try: chain = base64.standard_b64decode(chain) - except: + except Exception: return AWSValidationException('The certificate chain is not PEM-encoded or is not valid.').response() try: diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 6f31a2611..6c4086fd5 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -265,14 +265,14 @@ class LambdaFunction(BaseModel): def convert(s): try: return str(s, encoding='utf-8') - except: + except Exception: return s @staticmethod def is_json(test_str): try: response = json.loads(test_str) - except: + except Exception: response = test_str return response diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index d934f4bbd..5676da1ca 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -4,7 +4,7 @@ import json try: from urllib import unquote -except: +except ImportError: from urllib.parse import unquote from moto.core.utils import amz_crc32, amzn_request_id diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 1c13c5058..e617fa9f8 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -453,8 +453,8 @@ class ResourceMap(collections.Mapping): resource_name, resource_json, self, self._region_name) self._parsed_resources[resource_name] = new_resource - removed_resource_nams = set(old_template) - set(new_template) - for resource_name in removed_resource_nams: + removed_resource_names = set(old_template) - set(new_template) + for resource_name in removed_resource_names: resource_json = old_template[resource_name] parse_and_delete_resource( resource_name, resource_json, self, self._region_name) diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index a5b251b89..3023fa114 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -161,11 +161,15 @@ class CloudFormationResponse(BaseResponse): def update_stack(self): stack_name = self._get_param('StackName') role_arn = self._get_param('RoleARN') + template_url = self._get_param('TemplateURL') if self._get_param('UsePreviousTemplate') == "true": stack_body = self.cloudformation_backend.get_stack( stack_name).template + elif template_url: + stack_body = self._get_stack_from_s3_url(template_url) else: stack_body = self._get_param('TemplateBody') + parameters = dict([ (parameter['parameter_key'], parameter['parameter_value']) for parameter diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index 74499f61e..395f4f0ba 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -10,6 +10,8 @@ from .utils import make_arn_for_dashboard DEFAULT_ACCOUNT_ID = 123456789012 +_EMPTY_LIST = tuple() + class Dimension(object): @@ -146,14 +148,14 @@ class Statistics: return sum(self.values) @property - def min(self): + def minimum(self): if 'Minimum' not in self.stats: return None return min(self.values) @property - def max(self): + def maximum(self): if 'Maximum' not in self.stats: return None @@ -228,7 +230,7 @@ class CloudWatchBackend(BaseBackend): def put_metric_data(self, namespace, metric_data): for metric_member in metric_data: self.metric_data.append(MetricDatum( - namespace, metric_member['MetricName'], float(metric_member['Value']), metric_member['Dimensions.member'], metric_member.get('Timestamp'))) + namespace, metric_member['MetricName'], float(metric_member['Value']), metric_member.get('Dimensions.member', _EMPTY_LIST), metric_member.get('Timestamp'))) def get_metric_statistics(self, namespace, metric_name, start_time, end_time, period, stats): period_delta = timedelta(seconds=period) diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py index ccb53337c..c080d4620 100644 --- a/moto/cloudwatch/responses.py +++ b/moto/cloudwatch/responses.py @@ -276,27 +276,27 @@ GET_METRIC_STATISTICS_TEMPLATE = """=2.45.0 diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index ed2ee8337..182603e2c 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -1,18 +1,13 @@ 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, mock_sqs - import json -import sure # noqa + +import boto3 +from botocore.exceptions import ClientError # Ensure 'assert_raises' context manager support for Python 2.6 -import tests.backport_assert_raises # noqa from nose.tools import assert_raises -import random + +from moto import mock_cloudformation, mock_s3, mock_sqs, mock_ec2 dummy_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -39,7 +34,6 @@ dummy_template = { } } - dummy_template_yaml = """--- AWSTemplateFormatVersion: 2010-09-09 Description: Stack1 with yaml template @@ -57,7 +51,6 @@ Resources: Value: Name tag for tests """ - dummy_template_yaml_with_short_form_func = """--- AWSTemplateFormatVersion: 2010-09-09 Description: Stack1 with yaml template @@ -75,7 +68,6 @@ Resources: Value: Name tag for tests """ - dummy_template_yaml_with_ref = """--- AWSTemplateFormatVersion: 2010-09-09 Description: Stack1 with yaml template @@ -100,7 +92,6 @@ Resources: Value: !Ref TagName """ - dummy_update_template = { "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { @@ -131,12 +122,12 @@ dummy_output_template = { } } }, - "Outputs" : { - "StackVPC" : { - "Description" : "The ID of the VPC", - "Value" : "VPCID", - "Export" : { - "Name" : "My VPC ID" + "Outputs": { + "StackVPC": { + "Description": "The ID of the VPC", + "Value": "VPCID", + "Export": { + "Name": "My VPC ID" } } } @@ -156,7 +147,7 @@ dummy_import_template = { } dummy_template_json = json.dumps(dummy_template) -dummy_update_template_json = json.dumps(dummy_template) +dummy_update_template_json = json.dumps(dummy_update_template) dummy_output_template_json = json.dumps(dummy_output_template) dummy_import_template_json = json.dumps(dummy_import_template) @@ -172,6 +163,7 @@ def test_boto3_create_stack(): cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal( dummy_template) + @mock_cloudformation def test_boto3_create_stack_with_yaml(): cf_conn = boto3.client('cloudformation', region_name='us-east-1') @@ -283,6 +275,41 @@ def test_create_stack_from_s3_url(): 'TemplateBody'].should.equal(dummy_template) +@mock_cloudformation +@mock_s3 +@mock_ec2 +def test_update_stack_from_s3_url(): + s3 = boto3.client('s3') + s3_conn = boto3.resource('s3') + + cf_conn = boto3.client('cloudformation', region_name='us-east-1') + cf_conn.create_stack( + StackName="update_stack_from_url", + TemplateBody=dummy_template_json, + Tags=[{'Key': 'foo', 'Value': 'bar'}], + ) + + s3_conn.create_bucket(Bucket="foobar") + + s3_conn.Object( + 'foobar', 'template-key').put(Body=dummy_update_template_json) + key_url = s3.generate_presigned_url( + ClientMethod='get_object', + Params={ + 'Bucket': 'foobar', + 'Key': 'template-key' + } + ) + + cf_conn.update_stack( + StackName="update_stack_from_url", + TemplateURL=key_url, + ) + + cf_conn.get_template(StackName="update_stack_from_url")[ + 'TemplateBody'].should.equal(dummy_update_template) + + @mock_cloudformation def test_describe_stack_pagination(): conn = boto3.client('cloudformation', region_name='us-east-1') @@ -382,6 +409,7 @@ def test_delete_stack_from_resource(): @mock_cloudformation +@mock_ec2 def test_delete_stack_by_name(): cf_conn = boto3.client('cloudformation', region_name='us-east-1') cf_conn.create_stack( @@ -412,6 +440,7 @@ def test_describe_deleted_stack(): @mock_cloudformation +@mock_ec2 def test_describe_updated_stack(): cf_conn = boto3.client('cloudformation', region_name='us-east-1') cf_conn.create_stack( @@ -502,6 +531,7 @@ def test_stack_tags(): @mock_cloudformation +@mock_ec2 def test_stack_events(): cf = boto3.resource('cloudformation', region_name='us-east-1') stack = cf.create_stack( @@ -617,6 +647,7 @@ def test_export_names_must_be_unique(): TemplateBody=dummy_output_template_json, ) + @mock_sqs @mock_cloudformation def test_stack_with_imports(): diff --git a/tests/test_cloudwatch/test_cloudwatch.py b/tests/test_cloudwatch/test_cloudwatch.py index 2f8528855..a0f3871c0 100644 --- a/tests/test_cloudwatch/test_cloudwatch.py +++ b/tests/test_cloudwatch/test_cloudwatch.py @@ -1,5 +1,8 @@ import boto from boto.ec2.cloudwatch.alarm import MetricAlarm +import boto3 +from datetime import datetime, timedelta +import pytz import sure # noqa from moto import mock_cloudwatch_deprecated diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py index e621a642a..b2c44cd51 100644 --- a/tests/test_cloudwatch/test_cloudwatch_boto3.py +++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals import boto3 from botocore.exceptions import ClientError +from datetime import datetime, timedelta +import pytz import sure # noqa from moto import mock_cloudwatch @@ -137,6 +139,52 @@ def test_alarm_state(): len(resp['MetricAlarms']).should.equal(2) +@mock_cloudwatch +def test_put_metric_data_no_dimensions(): + conn = boto3.client('cloudwatch', region_name='us-east-1') + + conn.put_metric_data( + Namespace='tester', + MetricData=[ + dict( + MetricName='metric', + Value=1.5, + ) + ] + ) + + metrics = conn.list_metrics()['Metrics'] + metrics.should.have.length_of(1) + metric = metrics[0] + metric['Namespace'].should.equal('tester') + metric['MetricName'].should.equal('metric') +@mock_cloudwatch +def test_get_metric_statistics(): + conn = boto3.client('cloudwatch', region_name='us-east-1') + utc_now = datetime.now(tz=pytz.utc) + conn.put_metric_data( + Namespace='tester', + MetricData=[ + dict( + MetricName='metric', + Value=1.5, + ) + ] + ) + + stats = conn.get_metric_statistics( + Namespace='tester', + MetricName='metric', + StartTime=utc_now, + EndTime=utc_now + timedelta(seconds=60), + Period=60, + Statistics=['SampleCount', 'Sum'] + ) + + stats['Datapoints'].should.have.length_of(1) + datapoint = stats['Datapoints'][0] + datapoint['SampleCount'].should.equal(1.0) + datapoint['Sum'].should.equal(1.5) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index a80768101..d50f6999e 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -58,6 +58,19 @@ def test_upload_server_cert(): "arn:aws:iam::123456789012:server-certificate/certname") +@mock_iam_deprecated() +def test_delete_server_cert(): + conn = boto.connect_iam() + + conn.upload_server_cert("certname", "certbody", "privatekey") + conn.get_server_certificate("certname") + conn.delete_server_cert("certname") + with assert_raises(BotoServerError): + conn.get_server_certificate("certname") + with assert_raises(BotoServerError): + conn.delete_server_cert("certname") + + @mock_iam_deprecated() @raises(BotoServerError) def test_get_role__should_throw__when_role_does_not_exist(): diff --git a/tests/test_kinesis/test_kinesis.py b/tests/test_kinesis/test_kinesis.py index 26a87f35a..e3d350023 100644 --- a/tests/test_kinesis/test_kinesis.py +++ b/tests/test_kinesis/test_kinesis.py @@ -4,6 +4,8 @@ import boto.kinesis from boto.kinesis.exceptions import ResourceNotFoundException, InvalidArgumentException import boto3 import sure # noqa +import datetime +import time from moto import mock_kinesis, mock_kinesis_deprecated @@ -262,6 +264,129 @@ def test_get_records_latest(): response['Records'][0]['Data'].should.equal('last_record') +@mock_kinesis +def test_get_records_at_timestamp(): + # AT_TIMESTAMP - Read the first record at or after the specified timestamp + conn = boto3.client('kinesis', region_name="us-west-2") + stream_name = "my_stream" + conn.create_stream(StreamName=stream_name, ShardCount=1) + + # Create some data + for index in range(1, 5): + conn.put_record(StreamName=stream_name, + Data=str(index), + PartitionKey=str(index)) + + # When boto3 floors the timestamp that we pass to get_shard_iterator to + # second precision even though AWS supports ms precision: + # http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html + # To test around this limitation we wait until we well into the next second + # before capturing the time and storing the records we expect to retrieve. + time.sleep(1.0) + timestamp = datetime.datetime.utcnow() + + keys = [str(i) for i in range(5, 10)] + for k in keys: + conn.put_record(StreamName=stream_name, + Data=k, + PartitionKey=k) + + # Get a shard iterator + response = conn.describe_stream(StreamName=stream_name) + shard_id = response['StreamDescription']['Shards'][0]['ShardId'] + response = conn.get_shard_iterator(StreamName=stream_name, + ShardId=shard_id, + ShardIteratorType='AT_TIMESTAMP', + Timestamp=timestamp) + shard_iterator = response['ShardIterator'] + + response = conn.get_records(ShardIterator=shard_iterator) + + response['Records'].should.have.length_of(len(keys)) + partition_keys = [r['PartitionKey'] for r in response['Records']] + partition_keys.should.equal(keys) + + +@mock_kinesis +def test_get_records_at_very_old_timestamp(): + conn = boto3.client('kinesis', region_name="us-west-2") + stream_name = "my_stream" + conn.create_stream(StreamName=stream_name, ShardCount=1) + + # Create some data + keys = [str(i) for i in range(1, 5)] + for k in keys: + conn.put_record(StreamName=stream_name, + Data=k, + PartitionKey=k) + + # Get a shard iterator + response = conn.describe_stream(StreamName=stream_name) + shard_id = response['StreamDescription']['Shards'][0]['ShardId'] + response = conn.get_shard_iterator(StreamName=stream_name, + ShardId=shard_id, + ShardIteratorType='AT_TIMESTAMP', + Timestamp=1) + shard_iterator = response['ShardIterator'] + + response = conn.get_records(ShardIterator=shard_iterator) + + response['Records'].should.have.length_of(len(keys)) + partition_keys = [r['PartitionKey'] for r in response['Records']] + partition_keys.should.equal(keys) + + +@mock_kinesis +def test_get_records_at_very_new_timestamp(): + conn = boto3.client('kinesis', region_name="us-west-2") + stream_name = "my_stream" + conn.create_stream(StreamName=stream_name, ShardCount=1) + + # Create some data + keys = [str(i) for i in range(1, 5)] + for k in keys: + conn.put_record(StreamName=stream_name, + Data=k, + PartitionKey=k) + + timestamp = datetime.datetime.utcnow() + datetime.timedelta(seconds=1) + + # Get a shard iterator + response = conn.describe_stream(StreamName=stream_name) + shard_id = response['StreamDescription']['Shards'][0]['ShardId'] + response = conn.get_shard_iterator(StreamName=stream_name, + ShardId=shard_id, + ShardIteratorType='AT_TIMESTAMP', + Timestamp=timestamp) + shard_iterator = response['ShardIterator'] + + response = conn.get_records(ShardIterator=shard_iterator) + + response['Records'].should.have.length_of(0) + + +@mock_kinesis +def test_get_records_from_empty_stream_at_timestamp(): + conn = boto3.client('kinesis', region_name="us-west-2") + stream_name = "my_stream" + conn.create_stream(StreamName=stream_name, ShardCount=1) + + timestamp = datetime.datetime.utcnow() + + # Get a shard iterator + response = conn.describe_stream(StreamName=stream_name) + shard_id = response['StreamDescription']['Shards'][0]['ShardId'] + response = conn.get_shard_iterator(StreamName=stream_name, + ShardId=shard_id, + ShardIteratorType='AT_TIMESTAMP', + Timestamp=timestamp) + shard_iterator = response['ShardIterator'] + + response = conn.get_records(ShardIterator=shard_iterator) + + response['Records'].should.have.length_of(0) + + @mock_kinesis_deprecated def test_invalid_shard_iterator_type(): conn = boto.kinesis.connect_to_region("us-west-2")