From 881afc8f4a35564caef6098d383f7213fb14693e Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 08:31:31 -0500 Subject: [PATCH 1/9] update RDS models to include CopyTagsToSnapshot --- AUTHORS.md | 1 + moto/rds/models.py | 6 +++++ moto/rds2/models.py | 8 +++++++ tests/test_rds2/test_rds2.py | 44 ++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 6b7c96291..0a152505a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -53,3 +53,4 @@ Moto is written by Steve Pulec with contributions from: * [Jim Shields](https://github.com/jimjshields) * [William Richard](https://github.com/william-richard) * [Alex Casalboni](https://github.com/alexcasalboni) +* [Jon Beilke](https://github.com/jrbeilke) diff --git a/moto/rds/models.py b/moto/rds/models.py index 77deff09d..feecefe0c 100644 --- a/moto/rds/models.py +++ b/moto/rds/models.py @@ -48,6 +48,10 @@ class Database(BaseModel): if self.publicly_accessible is None: self.publicly_accessible = True + self.copy_tags_to_snapshot = kwargs.get("copy_tags_to_snapshot") + if self.copy_tags_to_snapshot is None: + self.copy_tags_to_snapshot = False + self.backup_retention_period = kwargs.get("backup_retention_period") if self.backup_retention_period is None: self.backup_retention_period = 1 @@ -137,6 +141,7 @@ class Database(BaseModel): "multi_az": properties.get("MultiAZ"), "port": properties.get('Port', 3306), "publicly_accessible": properties.get("PubliclyAccessible"), + "copy_tags_to_snapshot": properties.get("CopyTagsToSnapshot"), "region": region_name, "security_groups": security_groups, "storage_encrypted": properties.get("StorageEncrypted"), @@ -217,6 +222,7 @@ class Database(BaseModel): {% endif %} {{ database.publicly_accessible }} + {{ database.copy_tags_to_snapshot }} {{ database.auto_minor_version_upgrade }} {{ database.allocated_storage }} {{ database.storage_encrypted }} diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 3fc4b6d65..c656f5ec3 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -73,6 +73,9 @@ class Database(BaseModel): self.publicly_accessible = kwargs.get("publicly_accessible") if self.publicly_accessible is None: self.publicly_accessible = True + self.copy_tags_to_snapshot = kwargs.get("copy_tags_to_snapshot") + if self.copy_tags_to_snapshot is None: + self.copy_tags_to_snapshot = False self.backup_retention_period = kwargs.get("backup_retention_period") if self.backup_retention_period is None: self.backup_retention_period = 1 @@ -208,6 +211,7 @@ class Database(BaseModel): {% endif %} {{ database.publicly_accessible }} + {{ database.copy_tags_to_snapshot }} {{ database.auto_minor_version_upgrade }} {{ database.allocated_storage }} {{ database.storage_encrypted }} @@ -304,6 +308,7 @@ class Database(BaseModel): "db_parameter_group_name": properties.get('DBParameterGroupName'), "port": properties.get('Port', 3306), "publicly_accessible": properties.get("PubliclyAccessible"), + "copy_tags_to_snapshot": properties.get("CopyTagsToSnapshot"), "region": region_name, "security_groups": security_groups, "storage_encrypted": properties.get("StorageEncrypted"), @@ -362,6 +367,7 @@ class Database(BaseModel): "PreferredBackupWindow": "{{ database.preferred_backup_window }}", "PreferredMaintenanceWindow": "{{ database.preferred_maintenance_window }}", "PubliclyAccessible": "{{ database.publicly_accessible }}", + "CopyTagsToSnapshot": "{{ database.copy_tags_to_snapshot }}", "AllocatedStorage": "{{ database.allocated_storage }}", "Endpoint": { "Address": "{{ database.address }}", @@ -691,6 +697,8 @@ class RDS2Backend(BaseBackend): raise DBSnapshotAlreadyExistsError(db_snapshot_identifier) if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): raise SnapshotQuotaExceededError() + if not database.copy_tags_to_snapshot: + tags = None snapshot = Snapshot(database, db_snapshot_identifier, tags) self.snapshots[db_snapshot_identifier] = snapshot return snapshot diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index 80dcd4f53..7fecfeca9 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -33,6 +33,7 @@ def test_create_database(): db_instance['DBInstanceIdentifier'].should.equal("db-master-1") db_instance['IAMDatabaseAuthenticationEnabled'].should.equal(False) db_instance['DbiResourceId'].should.contain("db-") + db_instance['CopyTagsToSnapshot'].should.equal(False) @mock_rds2 @@ -339,6 +340,49 @@ def test_create_db_snapshots(): snapshot.get('Engine').should.equal('postgres') snapshot.get('DBInstanceIdentifier').should.equal('db-primary-1') snapshot.get('DBSnapshotIdentifier').should.equal('g-1') + result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshot']['DBSnapshotArn']) + result['TagList'].should.equal([]) + + +@mock_rds2 +def test_create_db_snapshots_copy_tags(): + conn = boto3.client('rds', region_name='us-west-2') + conn.create_db_snapshot.when.called_with( + DBInstanceIdentifier='db-primary-1', + DBSnapshotIdentifier='snapshot-1').should.throw(ClientError) + + conn.create_db_instance(DBInstanceIdentifier='db-primary-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"], + CopyTagsToSnapshot=True, + Tags=[ + { + 'Key': 'foo', + 'Value': 'bar', + }, + { + 'Key': 'foo1', + 'Value': 'bar1', + }, + ]) + + snapshot = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1', + DBSnapshotIdentifier='g-1').get('DBSnapshot') + + snapshot.get('Engine').should.equal('postgres') + snapshot.get('DBInstanceIdentifier').should.equal('db-primary-1') + snapshot.get('DBSnapshotIdentifier').should.equal('g-1') + result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshot']['DBSnapshotArn']) + result['TagList'].should.equal([{'Value': 'bar', + 'Key': 'foo'}, + {'Value': 'bar1', + 'Key': 'foo1'}]) @mock_rds2 From bf9b37142e7d95102dfe11c506d5ae1a3f2779ed Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 08:49:45 -0500 Subject: [PATCH 2/9] no need for [DBSnapshot] with list_tags_for_resource as the retuned snapshot already handles it --- tests/test_rds2/test_rds2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index 7fecfeca9..0f802bb5c 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -340,7 +340,7 @@ def test_create_db_snapshots(): snapshot.get('Engine').should.equal('postgres') snapshot.get('DBInstanceIdentifier').should.equal('db-primary-1') snapshot.get('DBSnapshotIdentifier').should.equal('g-1') - result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshot']['DBSnapshotArn']) + result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshotArn']) result['TagList'].should.equal([]) @@ -378,7 +378,7 @@ def test_create_db_snapshots_copy_tags(): snapshot.get('Engine').should.equal('postgres') snapshot.get('DBInstanceIdentifier').should.equal('db-primary-1') snapshot.get('DBSnapshotIdentifier').should.equal('g-1') - result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshot']['DBSnapshotArn']) + result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshotArn']) result['TagList'].should.equal([{'Value': 'bar', 'Key': 'foo'}, {'Value': 'bar1', From 276da0616851183bd5a2cd6dd8f80882816aea4a Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 10:39:42 -0500 Subject: [PATCH 3/9] added new merge_taglists() to moto.core.utils for merging lists of tags with precedence (ie. during rds2.create_snapshot) --- moto/core/utils.py | 9 +++++++++ moto/rds2/models.py | 5 +++-- tests/test_core/test_utils.py | 27 ++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index 86e7632b0..2cdcd07f1 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -286,3 +286,12 @@ def amzn_request_id(f): return status, headers, body return _wrapper + + +def merge_taglists(taglist_a, taglist_b): + ''' Merges two tag lists into a single tag list with Keys in the second list taking precedence''' + tags_a = {t['Key']:t for t in taglist_a} + tags_b = {t['Key']:t for t in taglist_b} + merged_tags = tags_a.copy() + merged_tags.update(tags_b) + return merged_tags.values() diff --git a/moto/rds2/models.py b/moto/rds2/models.py index c656f5ec3..d564e93b1 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -13,6 +13,7 @@ from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.core.utils import get_random_hex from moto.core.utils import iso_8601_datetime_with_milliseconds +from moto.core.utils import merge_taglists from moto.ec2.models import ec2_backends from .exceptions import (RDSClientError, DBInstanceNotFoundError, @@ -697,8 +698,8 @@ class RDS2Backend(BaseBackend): raise DBSnapshotAlreadyExistsError(db_snapshot_identifier) if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): raise SnapshotQuotaExceededError() - if not database.copy_tags_to_snapshot: - tags = None + if database.copy_tags_to_snapshot: + tags = merge_taglists(database.tags, tags) snapshot = Snapshot(database, db_snapshot_identifier, tags) self.snapshots[db_snapshot_identifier] = snapshot return snapshot diff --git a/tests/test_core/test_utils.py b/tests/test_core/test_utils.py index 8dbf21716..0fa4f3c59 100644 --- a/tests/test_core/test_utils.py +++ b/tests/test_core/test_utils.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import sure # noqa from freezegun import freeze_time -from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase, unix_time +from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase, unix_time, merge_taglists def test_camelcase_to_underscores(): @@ -28,3 +28,28 @@ def test_underscores_to_camelcase(): @freeze_time("2015-01-01 12:00:00") def test_unix_time(): unix_time().should.equal(1420113600.0) + + +def test_merge_taglists(): + taglist_a = [ + { + 'Key': 'foo', + 'Value': 'bar', + }, + { + 'Key': 'foo1', + 'Value': 'bar1', + }, + ] + taglist_b = [ + { + 'Key': 'foo1', + 'Value': 'bar1b', + }, + ] + taglist_merged = merge_taglists(taglist_a, taglist_b) + len(taglist_merged).should.equal(2) + tag_foo = [t for t in taglist_merged if t['Key']=='foo'] + tag_foo1 = [t for t in taglist_merged if t['Key']=='foo1'] + tag_foo[0].should.equal({'Key': 'foo','Value': 'bar',}) + tag_foo1[0].should.equal({'Key': 'foo1','Value': 'bar1b',}) From 1729681106203f4b692d12283ec01ffadaca20c4 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 10:45:22 -0500 Subject: [PATCH 4/9] formatting fix for E231 missing whitespace after : --- moto/core/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index 2cdcd07f1..21ac8c2e8 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -290,8 +290,8 @@ def amzn_request_id(f): def merge_taglists(taglist_a, taglist_b): ''' Merges two tag lists into a single tag list with Keys in the second list taking precedence''' - tags_a = {t['Key']:t for t in taglist_a} - tags_b = {t['Key']:t for t in taglist_b} + tags_a = {t['Key']: t for t in taglist_a} + tags_b = {t['Key']: t for t in taglist_b} merged_tags = tags_a.copy() merged_tags.update(tags_b) return merged_tags.values() From 1b8b32a663dc52a3a138331bc79fce4ffa38d316 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 11:13:33 -0500 Subject: [PATCH 5/9] add CopyTagsToSnapshot to db_kwargs --- moto/rds2/responses.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moto/rds2/responses.py b/moto/rds2/responses.py index eddb0042b..6b1da103b 100644 --- a/moto/rds2/responses.py +++ b/moto/rds2/responses.py @@ -19,6 +19,7 @@ class RDS2Response(BaseResponse): "allocated_storage": self._get_int_param('AllocatedStorage'), "availability_zone": self._get_param("AvailabilityZone"), "backup_retention_period": self._get_param("BackupRetentionPeriod"), + "copy_tags_to_snapshot": self._get_param("CopyTagsToSnapshot"), "db_instance_class": self._get_param('DBInstanceClass'), "db_instance_identifier": self._get_param('DBInstanceIdentifier'), "db_name": self._get_param("DBName"), From 6eb490ac78d217a2c570db997c926ba08b5c9503 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 12:03:13 -0500 Subject: [PATCH 6/9] add support for tags to rds snapshots --- moto/rds2/models.py | 18 +++++- tests/test_rds2/test_rds2.py | 111 +++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index d564e93b1..b85fe8b05 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -463,6 +463,20 @@ class Snapshot(BaseModel): """) return template.render(snapshot=self, database=self.database) + def get_tags(self): + return self.tags + + def add_tags(self, tags): + new_keys = [tag_set['Key'] for tag_set in tags] + self.tags = [tag_set for tag_set in self.tags if tag_set[ + 'Key'] not in new_keys] + self.tags.extend(tags) + return self.tags + + def remove_tags(self, tag_keys): + self.tags = [tag_set for tag_set in self.tags if tag_set[ + 'Key'] not in tag_keys] + class SecurityGroup(BaseModel): @@ -1037,8 +1051,8 @@ class RDS2Backend(BaseBackend): if resource_name in self.security_groups: return self.security_groups[resource_name].get_tags() elif resource_type == 'snapshot': # DB Snapshot - # TODO: Complete call to tags on resource type DB Snapshot - return [] + if resource_name in self.snapshots: + return self.snapshots[resource_name].get_tags() elif resource_type == 'subgrp': # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].get_tags() diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index 0f802bb5c..cf9805444 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -700,6 +700,117 @@ def test_remove_tags_db(): len(result['TagList']).should.equal(1) +@mock_rds2 +def test_list_tags_snapshot(): + conn = boto3.client('rds', region_name='us-west-2') + result = conn.list_tags_for_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:foo') + result['TagList'].should.equal([]) + conn.create_db_instance(DBInstanceIdentifier='db-primary-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + snapshot = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1', + DBSnapshotIdentifier='snapshot-with-tags', + Tags=[ + { + 'Key': 'foo', + 'Value': 'bar', + }, + { + 'Key': 'foo1', + 'Value': 'bar1', + }, + ]) + result = conn.list_tags_for_resource(ResourceName=snapshot['DBSnapshot']['DBSnapshotArn']) + result['TagList'].should.equal([{'Value': 'bar', + 'Key': 'foo'}, + {'Value': 'bar1', + 'Key': 'foo1'}]) + + +@mock_rds2 +def test_add_tags_snapshot(): + conn = boto3.client('rds', region_name='us-west-2') + conn.create_db_instance(DBInstanceIdentifier='db-primary-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + snapshot = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1', + DBSnapshotIdentifier='snapshot-without-tags', + Tags=[ + { + 'Key': 'foo', + 'Value': 'bar', + }, + { + 'Key': 'foo1', + 'Value': 'bar1', + }, + ]) + result = conn.list_tags_for_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-without-tags') + list(result['TagList']).should.have.length_of(2) + conn.add_tags_to_resource(ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-without-tags', + Tags=[ + { + 'Key': 'foo', + 'Value': 'fish', + }, + { + 'Key': 'foo2', + 'Value': 'bar2', + }, + ]) + result = conn.list_tags_for_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-without-tags') + list(result['TagList']).should.have.length_of(3) + + +@mock_rds2 +def test_remove_tags_snapshot(): + conn = boto3.client('rds', region_name='us-west-2') + conn.create_db_instance(DBInstanceIdentifier='db-primary-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + snapshot = conn.create_db_snapshot(DBInstanceIdentifier='db-primary-1', + DBSnapshotIdentifier='snapshot-with-tags', + Tags=[ + { + 'Key': 'foo', + 'Value': 'bar', + }, + { + 'Key': 'foo1', + 'Value': 'bar1', + }, + ]) + result = conn.list_tags_for_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-with-tags') + list(result['TagList']).should.have.length_of(2) + conn.remove_tags_from_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-with-tags', TagKeys=['foo']) + result = conn.list_tags_for_resource( + ResourceName='arn:aws:rds:us-west-2:1234567890:snapshot:snapshot-with-tags') + len(result['TagList']).should.equal(1) + + @mock_rds2 def test_add_tags_option_group(): conn = boto3.client('rds', region_name='us-west-2') From 7daee905a5a62b534d3e098558ce29d40278751f Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 13:28:13 -0500 Subject: [PATCH 7/9] remove merge_taglists as AWS will only take submitted tags or tags from db but not both when creating snapshot --- moto/core/utils.py | 9 --------- moto/rds2/models.py | 15 ++++++++------- moto/rds2/responses.py | 2 +- tests/test_core/test_utils.py | 27 +-------------------------- 4 files changed, 10 insertions(+), 43 deletions(-) diff --git a/moto/core/utils.py b/moto/core/utils.py index 21ac8c2e8..86e7632b0 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -286,12 +286,3 @@ def amzn_request_id(f): return status, headers, body return _wrapper - - -def merge_taglists(taglist_a, taglist_b): - ''' Merges two tag lists into a single tag list with Keys in the second list taking precedence''' - tags_a = {t['Key']: t for t in taglist_a} - tags_b = {t['Key']: t for t in taglist_b} - merged_tags = tags_a.copy() - merged_tags.update(tags_b) - return merged_tags.values() diff --git a/moto/rds2/models.py b/moto/rds2/models.py index b85fe8b05..b37306b12 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -13,7 +13,6 @@ from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.core.utils import get_random_hex from moto.core.utils import iso_8601_datetime_with_milliseconds -from moto.core.utils import merge_taglists from moto.ec2.models import ec2_backends from .exceptions import (RDSClientError, DBInstanceNotFoundError, @@ -418,10 +417,10 @@ class Database(BaseModel): class Snapshot(BaseModel): - def __init__(self, database, snapshot_id, tags=None): + def __init__(self, database, snapshot_id, tags): self.database = database self.snapshot_id = snapshot_id - self.tags = tags or [] + self.tags = tags self.created_at = iso_8601_datetime_with_milliseconds(datetime.datetime.now()) @property @@ -712,8 +711,10 @@ class RDS2Backend(BaseBackend): raise DBSnapshotAlreadyExistsError(db_snapshot_identifier) if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): raise SnapshotQuotaExceededError() - if database.copy_tags_to_snapshot: - tags = merge_taglists(database.tags, tags) + if tags is None: + tags = list() + if database.copy_tags_to_snapshot and not tags: + tags = database.get_tags() snapshot = Snapshot(database, db_snapshot_identifier, tags) self.snapshots[db_snapshot_identifier] = snapshot return snapshot @@ -810,13 +811,13 @@ class RDS2Backend(BaseBackend): def delete_database(self, db_instance_identifier, db_snapshot_name=None): if db_instance_identifier in self.databases: + if db_snapshot_name: + self.create_snapshot(db_instance_identifier, db_snapshot_name) database = self.databases.pop(db_instance_identifier) if database.is_replica: primary = self.find_db_from_id(database.source_db_identifier) primary.remove_replica(database) database.status = 'deleting' - if db_snapshot_name: - self.snapshots[db_snapshot_name] = Snapshot(database, db_snapshot_name) return database else: raise DBInstanceNotFoundError(db_instance_identifier) diff --git a/moto/rds2/responses.py b/moto/rds2/responses.py index 6b1da103b..66d4e0c52 100644 --- a/moto/rds2/responses.py +++ b/moto/rds2/responses.py @@ -160,7 +160,7 @@ class RDS2Response(BaseResponse): def create_db_snapshot(self): db_instance_identifier = self._get_param('DBInstanceIdentifier') db_snapshot_identifier = self._get_param('DBSnapshotIdentifier') - tags = self._get_param('Tags', []) + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) snapshot = self.backend.create_snapshot(db_instance_identifier, db_snapshot_identifier, tags) template = self.response_template(CREATE_SNAPSHOT_TEMPLATE) return template.render(snapshot=snapshot) diff --git a/tests/test_core/test_utils.py b/tests/test_core/test_utils.py index 0fa4f3c59..8dbf21716 100644 --- a/tests/test_core/test_utils.py +++ b/tests/test_core/test_utils.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import sure # noqa from freezegun import freeze_time -from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase, unix_time, merge_taglists +from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase, unix_time def test_camelcase_to_underscores(): @@ -28,28 +28,3 @@ def test_underscores_to_camelcase(): @freeze_time("2015-01-01 12:00:00") def test_unix_time(): unix_time().should.equal(1420113600.0) - - -def test_merge_taglists(): - taglist_a = [ - { - 'Key': 'foo', - 'Value': 'bar', - }, - { - 'Key': 'foo1', - 'Value': 'bar1', - }, - ] - taglist_b = [ - { - 'Key': 'foo1', - 'Value': 'bar1b', - }, - ] - taglist_merged = merge_taglists(taglist_a, taglist_b) - len(taglist_merged).should.equal(2) - tag_foo = [t for t in taglist_merged if t['Key']=='foo'] - tag_foo1 = [t for t in taglist_merged if t['Key']=='foo1'] - tag_foo[0].should.equal({'Key': 'foo','Value': 'bar',}) - tag_foo1[0].should.equal({'Key': 'foo1','Value': 'bar1b',}) From 245e3a5f719bcf74846e789d3c3f1b4f9b56ca53 Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 13:33:10 -0500 Subject: [PATCH 8/9] formatting fix for E111 indentation is not a multiple of four --- moto/rds2/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index b37306b12..159a56ebd 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -712,7 +712,7 @@ class RDS2Backend(BaseBackend): if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): raise SnapshotQuotaExceededError() if tags is None: - tags = list() + tags = list() if database.copy_tags_to_snapshot and not tags: tags = database.get_tags() snapshot = Snapshot(database, db_snapshot_identifier, tags) From 67a0e06059101e90a0370bc1f7b10bbe1414821c Mon Sep 17 00:00:00 2001 From: Jon Beilke Date: Fri, 21 Sep 2018 13:54:07 -0500 Subject: [PATCH 9/9] allow for adding and removing tags on rds snapshots --- moto/rds2/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 159a56ebd..fee004f76 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -1083,7 +1083,8 @@ class RDS2Backend(BaseBackend): if resource_name in self.security_groups: return self.security_groups[resource_name].remove_tags(tag_keys) elif resource_type == 'snapshot': # DB Snapshot - return None + if resource_name in self.snapshots: + return self.snapshots[resource_name].remove_tags(tag_keys) elif resource_type == 'subgrp': # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].remove_tags(tag_keys) @@ -1112,7 +1113,8 @@ class RDS2Backend(BaseBackend): if resource_name in self.security_groups: return self.security_groups[resource_name].add_tags(tags) elif resource_type == 'snapshot': # DB Snapshot - return [] + if resource_name in self.snapshots: + return self.snapshots[resource_name].add_tags(tags) elif resource_type == 'subgrp': # DB subnet group if resource_name in self.subnet_groups: return self.subnet_groups[resource_name].add_tags(tags)