From 6cb0428d20871d7a8931664fb496932629f332d1 Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Wed, 15 Jan 2020 10:41:54 -0600 Subject: [PATCH 1/9] adds tagging support for cloudwatch events service --- moto/events/models.py | 27 ++++++++++ moto/events/responses.py | 23 ++++++++ moto/utilities/tagging_service.py | 56 ++++++++++++++++++++ tests/test_events/test_events.py | 50 ++++++++++++++--- tests/test_utilities/test_tagging_service.py | 53 ++++++++++++++++++ 5 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 moto/utilities/tagging_service.py create mode 100644 tests/test_utilities/test_tagging_service.py diff --git a/moto/events/models.py b/moto/events/models.py index 548d41393..84a663b6d 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -6,6 +6,7 @@ from boto3 import Session from moto.core.exceptions import JsonRESTError from moto.core import BaseBackend, BaseModel from moto.sts.models import ACCOUNT_ID +from moto.utilities.tagging_service import TaggingService class Rule(BaseModel): @@ -104,6 +105,7 @@ class EventsBackend(BaseBackend): self.region_name = region_name self.event_buses = {} self.event_sources = {} + self.tagger = TaggingService() self._add_default_event_bus() @@ -360,7 +362,32 @@ class EventsBackend(BaseBackend): ) self.event_buses.pop(name, None) + + def list_tags_for_resource(self, arn): + name = arn.split('/')[-1] + if name in self.rules: + return self.tagger.list_tags_for_resource(self.rules[name].arn) + raise JsonRESTError( + "ResourceNotFoundException", "An entity that you specified does not exist." + ) + def tag_resource(self, arn, tags): + name = arn.split('/')[-1] + if name in self.rules: + self.tagger.tag_resource(self.rules[name].arn, tags) + return {} + raise JsonRESTError( + "ResourceNotFoundException", "An entity that you specified does not exist." + ) + + def untag_resource(self, arn, tag_names): + name = arn.split('/')[-1] + if name in self.rules: + self.tagger.untag_resource_using_names(self.rules[name].arn, tag_names) + return {} + raise JsonRESTError( + "ResourceNotFoundException", "An entity that you specified does not exist." + ) events_backends = {} for region in Session().get_available_regions("events"): diff --git a/moto/events/responses.py b/moto/events/responses.py index b415564f8..68c2114a6 100644 --- a/moto/events/responses.py +++ b/moto/events/responses.py @@ -297,3 +297,26 @@ class EventsHandler(BaseResponse): self.events_backend.delete_event_bus(name) return "", self.response_headers + + def list_tags_for_resource(self): + arn = self._get_param("ResourceARN") + + result = self.events_backend.list_tags_for_resource(arn) + + return json.dumps(result), self.response_headers + + def tag_resource(self): + arn = self._get_param("ResourceARN") + tags = self._get_param("Tags") + + result = self.events_backend.tag_resource(arn, tags) + + return json.dumps(result), self.response_headers + + def untag_resource(self): + arn = self._get_param("ResourceARN") + tags = self._get_param("TagKeys") + + result = self.events_backend.untag_resource(arn, tags) + + return json.dumps(result), self.response_headers diff --git a/moto/utilities/tagging_service.py b/moto/utilities/tagging_service.py new file mode 100644 index 000000000..5eae095ec --- /dev/null +++ b/moto/utilities/tagging_service.py @@ -0,0 +1,56 @@ +class TaggingService: + def __init__(self, tagName='Tags', keyName='Key', valueName='Value'): + self.tagName = tagName + self.keyName = keyName + self.valueName = valueName + self.tags = {} + + def list_tags_for_resource(self, arn): + result = [] + if arn in self.tags: + for k, v in self.tags[arn].items(): + result.append({self.keyName: k, self.valueName: v}) + return {self.tagName: result} + + def tag_resource(self, arn, tags): + if arn not in self.tags: + self.tags[arn] = {} + for t in tags: + if self.valueName in t: + self.tags[arn][t[self.keyName]] = t[self.valueName] + else: + self.tags[arn][t[self.keyName]] = None + + def untag_resource_using_names(self, arn, tag_names): + for name in tag_names: + if name in self.tags.get(arn, {}): + del self.tags[arn][name] + + def untag_resource_using_tags(self, arn, tags): + m = self.tags.get(arn, {}) + for t in tags: + if self.keyName in t: + if t[self.keyName] in m: + if self.valueName in t: + if m[t[self.keyName]] != t[self.valueName]: + continue + # If both key and value are provided, match both before deletion + del m[t[self.keyName]] + + def extract_tag_names(self, tags): + results = [] + if len(tags) == 0: + return results + for tag in tags: + if self.keyName in tag: + results.append(tag[self.keyName]) + return results + + def flatten_tag_list(self, tags): + result = {} + for t in tags: + if self.valueName in t: + result[t[self.keyName]] = t[self.valueName] + else: + result[t[self.keyName]] = None + return result diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 14d872806..6e9ca3a03 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -5,8 +5,10 @@ import sure # noqa from moto.events import mock_events from botocore.exceptions import ClientError +from moto.core.exceptions import JsonRESTError from nose.tools import assert_raises from moto.core import ACCOUNT_ID +from moto.events.models import EventsBackend RULES = [ {"Name": "test1", "ScheduleExpression": "rate(5 minutes)"}, @@ -136,14 +138,6 @@ def test_list_rule_names_by_target(): assert rule in test_2_target["Rules"] -@mock_events -def test_list_rules(): - client = generate_environment() - - rules = client.list_rules() - assert len(rules["Rules"]) == len(RULES) - - @mock_events def test_delete_rule(): client = generate_environment() @@ -461,3 +455,43 @@ def test_delete_event_bus_errors(): client.delete_event_bus.when.called_with(Name="default").should.throw( ClientError, "Cannot delete event bus default." ) + +@mock_events +def test_rule_tagging_happy(): + client = generate_environment() + rule_name = get_random_rule()["Name"] + rule_arn = client.describe_rule(Name=rule_name).get("Arn") + + tags = [{"Key": "key1", "Value": "value1"}, {"Key": "key2", "Value": "value2"}] + client.tag_resource(ResourceARN=rule_arn, Tags=tags) + + actual = client.list_tags_for_resource(ResourceARN=rule_arn).get("Tags") + assert tags == actual + + client.untag_resource(ResourceARN=rule_arn, TagKeys=["key1"]) + + actual = client.list_tags_for_resource(ResourceARN=rule_arn).get("Tags") + expected = [{"Key": "key2", "Value": "value2"}] + assert expected == actual + +@mock_events +def test_rule_tagging_sad(): + b = EventsBackend("us-west-2") + + try: + b.tag_resource('unknown', []) + raise 'tag_resource should fail if ResourceARN is not known' + except JsonRESTError: + pass + + try: + b.untag_resource('unknown', []) + raise 'untag_resource should fail if ResourceARN is not known' + except JsonRESTError: + pass + + try: + b.list_tags_for_resource('unknown') + raise 'list_tags_for_resource should fail if ResourceARN is not known' + except JsonRESTError: + pass \ No newline at end of file diff --git a/tests/test_utilities/test_tagging_service.py b/tests/test_utilities/test_tagging_service.py new file mode 100644 index 000000000..94415cb2a --- /dev/null +++ b/tests/test_utilities/test_tagging_service.py @@ -0,0 +1,53 @@ +import unittest + +from moto.utilities.tagging_service import TaggingService + + +class TestTaggingService(unittest.TestCase): + def test_list_empty(self): + svc = TaggingService() + result = svc.list_tags_for_resource('test') + self.assertEqual(result, {'Tags': []}) + + def test_create_tag(self): + svc = TaggingService('TheTags', 'TagKey', 'TagValue') + tags = [{'TagKey': 'key_key', 'TagValue': 'value_value'}] + svc.tag_resource('arn', tags) + actual = svc.list_tags_for_resource('arn') + expected = {'TheTags': [{'TagKey': 'key_key', 'TagValue': 'value_value'}]} + self.assertDictEqual(expected, actual) + + def test_create_tag_without_value(self): + svc = TaggingService() + tags = [{'Key': 'key_key'}] + svc.tag_resource('arn', tags) + actual = svc.list_tags_for_resource('arn') + expected = {'Tags': [{'Key': 'key_key', 'Value': ''}]} + self.assertDictEqual(expected, actual) + + def test_delete_tag(self): + svc = TaggingService() + tags = [{'Key': 'key_key', 'Value': 'value_value'}] + svc.tag_resource('arn', tags) + svc.untag_resource('arn', ['key_key']) + result = svc.list_tags_for_resource('arn') + self.assertEqual( + result, {'Tags': []}) + + def test_list_empty_delete(self): + svc = TaggingService() + svc.untag_resource('arn', ['key_key']) + result = svc.list_tags_for_resource('arn') + self.assertEqual( + result, {'Tags': []}) + + def test_extract_tag_names(self): + svc = TaggingService() + tags = [{'Key': 'key1', 'Value': 'value1'}, {'Key': 'key2', 'Value': 'value2'}] + actual = svc.extract_tag_names(tags) + expected = ['key1', 'key2'] + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() From 85207b885b69ec0fb217cc074bd471a8bde98e05 Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Thu, 16 Jan 2020 12:10:38 -0600 Subject: [PATCH 2/9] updates KMS service to use TaggingService --- moto/kms/models.py | 45 ++++++++++++++++++-------- moto/kms/responses.py | 18 ++++++++--- tests/test_kms/test_kms.py | 63 +++++++++++++++++++++--------------- tests/test_kms/test_utils.py | 8 ++--- 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 22f0039b2..32fcd23ae 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -7,13 +7,14 @@ from datetime import datetime, timedelta from boto3 import Session from moto.core import BaseBackend, BaseModel +from moto.core.exceptions import JsonRESTError from moto.core.utils import iso_8601_datetime_without_milliseconds - +from moto.utilities.tagging_service import TaggingService from .utils import decrypt, encrypt, generate_key_id, generate_master_key class Key(BaseModel): - def __init__(self, policy, key_usage, description, tags, region): + def __init__(self, policy, key_usage, description, region): self.id = generate_key_id() self.policy = policy self.key_usage = key_usage @@ -24,7 +25,6 @@ class Key(BaseModel): self.account_id = "012345678912" self.key_rotation_status = False self.deletion_date = None - self.tags = tags or {} self.key_material = generate_master_key() @property @@ -70,11 +70,12 @@ class Key(BaseModel): policy=properties["KeyPolicy"], key_usage="ENCRYPT_DECRYPT", description=properties["Description"], - tags=properties.get("Tags"), region=region_name, ) key.key_rotation_status = properties["EnableKeyRotation"] key.enabled = properties["Enabled"] + kms_backend.tag_resource(key.id, properties.get("Tags")) + return key def get_cfn_attribute(self, attribute_name): @@ -89,24 +90,19 @@ class KmsBackend(BaseBackend): def __init__(self): self.keys = {} self.key_to_aliases = defaultdict(set) + self.tagger = TaggingService(keyName='TagKey', valueName='TagValue') def create_key(self, policy, key_usage, description, tags, region): - key = Key(policy, key_usage, description, tags, region) + key = Key(policy, key_usage, description, region) self.keys[key.id] = key + if tags != None and len(tags) > 0: + self.tag_resource(key.id, tags) 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: @@ -282,6 +278,29 @@ class KmsBackend(BaseBackend): return plaintext, ciphertext_blob, arn + def list_resource_tags(self, key_id): + if key_id in self.keys: + return self.tagger.list_tags_for_resource(key_id) + raise JsonRESTError( + "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + ) + + def tag_resource(self, key_id, tags): + if key_id in self.keys: + self.tagger.tag_resource(key_id, tags) + return {} + raise JsonRESTError( + "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + ) + + def untag_resource(self, key_id, tag_names): + if key_id in self.keys: + self.tagger.untag_resource_using_names(key_id, tag_names) + return {} + raise JsonRESTError( + "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + ) + kms_backends = {} for region in Session().get_available_regions("kms"): diff --git a/moto/kms/responses.py b/moto/kms/responses.py index d3a9726e1..3658f0d37 100644 --- a/moto/kms/responses.py +++ b/moto/kms/responses.py @@ -143,17 +143,27 @@ class KmsResponse(BaseResponse): self._validate_cmk_id(key_id) - self.kms_backend.tag_resource(key_id, tags) - return json.dumps({}) + result = self.kms_backend.tag_resource(key_id, tags) + return json.dumps(result) + + def untag_resource(self): + """https://docs.aws.amazon.com/kms/latest/APIReference/API_UntagResource.html""" + key_id = self.parameters.get("KeyId") + tag_names = self.parameters.get("TagKeys") + + self._validate_cmk_id(key_id) + + result = self.kms_backend.untag_resource(key_id, tag_names) + return json.dumps(result) def list_resource_tags(self): """https://docs.aws.amazon.com/kms/latest/APIReference/API_ListResourceTags.html""" key_id = self.parameters.get("KeyId") - self._validate_cmk_id(key_id) tags = self.kms_backend.list_resource_tags(key_id) - return json.dumps({"Tags": tags, "NextMarker": None, "Truncated": False}) + tags.update({"NextMarker": None, "Truncated": False}) + return json.dumps(tags) def describe_key(self): """https://docs.aws.amazon.com/kms/latest/APIReference/API_DescribeKey.html""" diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 70fa68787..6a35ee2c8 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -17,7 +17,8 @@ from boto.kms.exceptions import AlreadyExistsException, NotFoundException from freezegun import freeze_time from nose.tools import assert_raises from parameterized import parameterized - +from moto.core.exceptions import JsonRESTError +from moto.kms.models import KmsBackend from moto.kms.exceptions import NotFoundException as MotoNotFoundException from moto import mock_kms, mock_kms_deprecated @@ -910,36 +911,46 @@ def test_update_key_description(): result = client.update_key_description(KeyId=key_id, Description="new_description") assert "ResponseMetadata" in result +@mock_kms +def test_key_tagging_happy(): + client = boto3.client("kms", region_name="us-east-1") + key = client.create_key(Description="test-key-tagging") + key_id = key["KeyMetadata"]["KeyId"] + + tags = [{"TagKey": "key1", "TagValue": "value1"}, {"TagKey": "key2", "TagValue": "value2"}] + client.tag_resource(KeyId=key_id, Tags=tags) + + result = client.list_resource_tags(KeyId=key_id) + actual = result.get("Tags", []) + assert tags == actual + + client.untag_resource(KeyId=key_id, TagKeys=["key1"]) + + actual = client.list_resource_tags(KeyId=key_id).get("Tags", []) + expected = [{"TagKey": "key2", "TagValue": "value2"}] + assert expected == actual @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"]) +def test_key_tagging_sad(): + b = KmsBackend() - keyid = response["KeyId"] - response = client.tag_resource( - KeyId=keyid, Tags=[{"TagKey": "string", "TagValue": "string"}] - ) + try: + b.tag_resource('unknown', []) + raise 'tag_resource should fail if KeyId is not known' + except JsonRESTError: + pass - # Shouldn't have any data, just header - assert len(response.keys()) == 1 + try: + b.untag_resource('unknown', []) + raise 'untag_resource should fail if KeyId is not known' + except JsonRESTError: + pass - -@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" + try: + b.list_resource_tags('unknown') + raise 'list_resource_tags should fail if KeyId is not known' + except JsonRESTError: + pass @parameterized( diff --git a/tests/test_kms/test_utils.py b/tests/test_kms/test_utils.py index f5478e0ef..29ea969b5 100644 --- a/tests/test_kms/test_utils.py +++ b/tests/test_kms/test_utils.py @@ -102,7 +102,7 @@ def test_deserialize_ciphertext_blob(raw, serialized): @parameterized(((ec[0],) for ec in ENCRYPTION_CONTEXT_VECTORS)) def test_encrypt_decrypt_cycle(encryption_context): plaintext = b"some secret plaintext" - master_key = Key("nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = encrypt( @@ -133,7 +133,7 @@ def test_encrypt_unknown_key_id(): def test_decrypt_invalid_ciphertext_format(): - master_key = Key("nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} with assert_raises(InvalidCiphertextException): @@ -153,7 +153,7 @@ def test_decrypt_unknwown_key_id(): def test_decrypt_invalid_ciphertext(): - master_key = Key("nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = ( master_key.id.encode("utf-8") + b"123456789012" @@ -171,7 +171,7 @@ def test_decrypt_invalid_ciphertext(): def test_decrypt_invalid_encryption_context(): plaintext = b"some secret plaintext" - master_key = Key("nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = encrypt( From b64a571a37453c36e565f09637a4ae2638a4f910 Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Tue, 18 Feb 2020 10:33:27 -0600 Subject: [PATCH 3/9] adds utilities init --- moto/utilities/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 moto/utilities/__init__.py diff --git a/moto/utilities/__init__.py b/moto/utilities/__init__.py new file mode 100644 index 000000000..e69de29bb From d1efedec2952a8597624d821ecaaa718597612a9 Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Tue, 18 Feb 2020 13:40:34 -0600 Subject: [PATCH 4/9] updates kms to use tagging service and support untag_resource --- moto/kms/models.py | 25 +++++---------------- tests/test_kms/test_kms.py | 43 ++++++++++++++++++++++++++++++++++++ tests/test_kms/test_utils.py | 8 +++---- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 9f61b275f..3d0da036e 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -7,27 +7,18 @@ from datetime import datetime, timedelta from boto3 import Session from moto.core import BaseBackend, BaseModel -<<<<<<< HEAD -from moto.core.exceptions import JsonRESTError -from moto.core.utils import iso_8601_datetime_without_milliseconds -from moto.utilities.tagging_service import TaggingService -======= from moto.core.utils import unix_time - +from moto.utilities.tagging_service import TaggingService +from moto.core.exceptions import JsonRESTError from moto.iam.models import ACCOUNT_ID ->>>>>>> 100dbd529f174f18d579a1dcc066d55409f2e38f from .utils import decrypt, encrypt, generate_key_id, generate_master_key class Key(BaseModel): -<<<<<<< HEAD - def __init__(self, policy, key_usage, description, region): -======= def __init__( - self, policy, key_usage, customer_master_key_spec, description, tags, region + self, policy, key_usage, customer_master_key_spec, description, region ): ->>>>>>> 100dbd529f174f18d579a1dcc066d55409f2e38f self.id = generate_key_id() self.creation_date = unix_time() self.policy = policy @@ -142,19 +133,14 @@ class KmsBackend(BaseBackend): self.key_to_aliases = defaultdict(set) self.tagger = TaggingService(keyName='TagKey', valueName='TagValue') -<<<<<<< HEAD - def create_key(self, policy, key_usage, description, tags, region): - key = Key(policy, key_usage, description, region) -======= def create_key( self, policy, key_usage, customer_master_key_spec, description, tags, region ): key = Key( - policy, key_usage, customer_master_key_spec, description, tags, region + policy, key_usage, customer_master_key_spec, description, region ) ->>>>>>> 100dbd529f174f18d579a1dcc066d55409f2e38f self.keys[key.id] = key - if tags != None and len(tags) > 0: + if tags is not None and len(tags) > 0: self.tag_resource(key.id, tags) return key @@ -166,6 +152,7 @@ class KmsBackend(BaseBackend): if key_id in self.keys: if key_id in self.key_to_aliases: self.key_to_aliases.pop(key_id) + self.tagger.delete_all_tags_for_resource(key_id) return self.keys.pop(key_id) diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index aaf09a6be..d2dca6786 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -680,3 +680,46 @@ def test__assert_default_policy(): _assert_default_policy.when.called_with("default").should_not.throw( MotoNotFoundException ) + + +@mock_kms +def test_key_tagging_happy(): + client = boto3.client("kms", region_name="us-east-1") + key = client.create_key(Description="test-key-tagging") + key_id = key["KeyMetadata"]["KeyId"] + + tags = [{"TagKey": "key1", "TagValue": "value1"}, {"TagKey": "key2", "TagValue": "value2"}] + client.tag_resource(KeyId=key_id, Tags=tags) + + result = client.list_resource_tags(KeyId=key_id) + actual = result.get("Tags", []) + assert tags == actual + + client.untag_resource(KeyId=key_id, TagKeys=["key1"]) + + actual = client.list_resource_tags(KeyId=key_id).get("Tags", []) + expected = [{"TagKey": "key2", "TagValue": "value2"}] + assert expected == actual + + +@mock_kms +def test_key_tagging_sad(): + b = KmsBackend() + + try: + b.tag_resource('unknown', []) + raise 'tag_resource should fail if KeyId is not known' + except JsonRESTError: + pass + + try: + b.untag_resource('unknown', []) + raise 'untag_resource should fail if KeyId is not known' + except JsonRESTError: + pass + + try: + b.list_resource_tags('unknown') + raise 'list_resource_tags should fail if KeyId is not known' + except JsonRESTError: + pass diff --git a/tests/test_kms/test_utils.py b/tests/test_kms/test_utils.py index 4c84ed127..4446635f3 100644 --- a/tests/test_kms/test_utils.py +++ b/tests/test_kms/test_utils.py @@ -102,7 +102,7 @@ def test_deserialize_ciphertext_blob(raw, serialized): @parameterized(((ec[0],) for ec in ENCRYPTION_CONTEXT_VECTORS)) def test_encrypt_decrypt_cycle(encryption_context): plaintext = b"some secret plaintext" - master_key = Key("nop", "nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = encrypt( @@ -133,7 +133,7 @@ def test_encrypt_unknown_key_id(): def test_decrypt_invalid_ciphertext_format(): - master_key = Key("nop", "nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} with assert_raises(InvalidCiphertextException): @@ -153,7 +153,7 @@ def test_decrypt_unknwown_key_id(): def test_decrypt_invalid_ciphertext(): - master_key = Key("nop", "nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = ( master_key.id.encode("utf-8") + b"123456789012" @@ -171,7 +171,7 @@ def test_decrypt_invalid_ciphertext(): def test_decrypt_invalid_encryption_context(): plaintext = b"some secret plaintext" - master_key = Key("nop", "nop", "nop", "nop", [], "nop") + master_key = Key("nop", "nop", "nop", "nop", "nop") master_key_map = {master_key.id: master_key} ciphertext_blob = encrypt( From 4e2fe76820025a75ce6282047e3ae0663ea45ccc Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Tue, 18 Feb 2020 13:51:35 -0600 Subject: [PATCH 5/9] removes duplicate declaration of list_tags_for_resource --- moto/events/models.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/moto/events/models.py b/moto/events/models.py index c400677df..6787f51ab 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -374,14 +374,6 @@ class EventsBackend(BaseBackend): "ResourceNotFoundException", "An entity that you specified does not exist." ) - def list_tags_for_resource(self, arn): - name = arn.split("/")[-1] - if name in self.rules: - return self.tagger.list_tags_for_resource(self.rules[name].arn) - raise JsonRESTError( - "ResourceNotFoundException", "An entity that you specified does not exist." - ) - def tag_resource(self, arn, tags): name = arn.split("/")[-1] if name in self.rules: From 1432e82606946bd9d7b002bbbb5e87423011308f Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Tue, 18 Feb 2020 14:01:15 -0600 Subject: [PATCH 6/9] fixes kms/models create_key parameters --- moto/kms/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/kms/models.py b/moto/kms/models.py index 3d0da036e..89cc5758a 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -111,11 +111,11 @@ class Key(BaseModel): key_usage="ENCRYPT_DECRYPT", customer_master_key_spec="SYMMETRIC_DEFAULT", description=properties["Description"], + tags=properties["Tags"], region=region_name, ) key.key_rotation_status = properties["EnableKeyRotation"] key.enabled = properties["Enabled"] - kms_backend.tag_resource(key.id, properties.get("Tags")) return key From 38413577fc04164886728ac46f1b7563054f56b3 Mon Sep 17 00:00:00 2001 From: Bryan Alexander Date: Wed, 19 Feb 2020 09:18:01 -0600 Subject: [PATCH 7/9] fixes bug in resourcetaggingapi/get_kms_tags --- moto/resourcegroupstaggingapi/models.py | 2 +- tests/test_events/test_events.py | 7 ------ tests/test_kms/test_kms.py | 4 ++-- .../test_resourcegroupstaggingapi.py | 23 +++++++++++++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index 850ab5c04..8c17864f3 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -318,7 +318,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): # KMS def get_kms_tags(kms_key_id): result = [] - for tag in self.kms_backend.list_resource_tags(kms_key_id): + for tag in self.kms_backend.list_resource_tags(kms_key_id).get("Tags",[]): result.append({"Key": tag["TagKey"], "Value": tag["TagValue"]}) return result diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index cf3743d34..80fadb449 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -10,9 +10,6 @@ from moto.core.exceptions import JsonRESTError from nose.tools import assert_raises from moto.core import ACCOUNT_ID -<< << << < HEAD -== == == = ->>>>>> > 100dbd529f174f18d579a1dcc066d55409f2e38f RULES = [ {"Name": "test1", "ScheduleExpression": "rate(5 minutes)"}, @@ -461,10 +458,6 @@ def test_delete_event_bus_errors(): ) -<< << << < HEAD -== == == = - ->>>>>> > 100dbd529f174f18d579a1dcc066d55409f2e38f @mock_events def test_rule_tagging_happy(): client = generate_environment() diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index d2dca6786..d00c885f2 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -682,7 +682,7 @@ def test__assert_default_policy(): ) -@mock_kms +@mock_kms_deprecated def test_key_tagging_happy(): client = boto3.client("kms", region_name="us-east-1") key = client.create_key(Description="test-key-tagging") @@ -702,7 +702,7 @@ def test_key_tagging_happy(): assert expected == actual -@mock_kms +@mock_kms_deprecated def test_key_tagging_sad(): b = KmsBackend() diff --git a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py index 3ee517ce8..dc75bb722 100644 --- a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py +++ b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py @@ -248,3 +248,26 @@ def test_get_many_resources(): ) # TODO test pagenation + + +@mock_kms +def test_get_kms_tags(): + kms = boto3.client("kms", region_name="us-east-1") + key = kms.create_key( + KeyUsage="ENCRYPT_DECRYPT", + Tags=[ + {"TagKey": "key_name", "TagValue": "a_value"}, + {"TagKey": "key_2", "TagValue": "val2"}, + ], + ) + key_id = key["KeyMetadata"]["KeyId"] + + rtapi = boto3.client("resourcegroupstaggingapi", region_name="us-east-1") + resp = rtapi.get_resources( + ResourceTypeFilters=["kms"], + TagFilters=[{"Key": "key_name"}], + ) + resp["ResourceTagMappingList"].should.have.length_of(1) + resp["ResourceTagMappingList"][0]["Tags"].should.contain( + {"Key": "key_name", "Value": "a_value"} + ) From 1221d2653ac3ab5fdeddcffc56ef5d23219cd543 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 19 Feb 2020 09:12:13 -0500 Subject: [PATCH 8/9] fix test cases, bug when no tags are present and conflict --- moto/events/models.py | 2 +- moto/kms/models.py | 17 ++--- moto/resourcegroupstaggingapi/models.py | 2 +- setup.cfg | 2 +- tests/test_kms/test_kms.py | 65 ++++++++++++++----- .../test_resourcegroupstaggingapi.py | 23 ------- 6 files changed, 61 insertions(+), 50 deletions(-) diff --git a/moto/events/models.py b/moto/events/models.py index 6787f51ab..a80b86daa 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -367,7 +367,7 @@ class EventsBackend(BaseBackend): self.event_buses.pop(name, None) def list_tags_for_resource(self, arn): - name = arn.split('/')[-1] + name = arn.split("/")[-1] if name in self.rules: return self.tagger.list_tags_for_resource(self.rules[name].arn) raise JsonRESTError( diff --git a/moto/kms/models.py b/moto/kms/models.py index 89cc5758a..36f72e6de 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -111,7 +111,7 @@ class Key(BaseModel): key_usage="ENCRYPT_DECRYPT", customer_master_key_spec="SYMMETRIC_DEFAULT", description=properties["Description"], - tags=properties["Tags"], + tags=properties.get("Tags", []), region=region_name, ) key.key_rotation_status = properties["EnableKeyRotation"] @@ -131,14 +131,12 @@ class KmsBackend(BaseBackend): def __init__(self): self.keys = {} self.key_to_aliases = defaultdict(set) - self.tagger = TaggingService(keyName='TagKey', valueName='TagValue') + self.tagger = TaggingService(keyName="TagKey", valueName="TagValue") def create_key( self, policy, key_usage, customer_master_key_spec, description, tags, region ): - key = Key( - policy, key_usage, customer_master_key_spec, description, region - ) + key = Key(policy, key_usage, customer_master_key_spec, description, region) self.keys[key.id] = key if tags is not None and len(tags) > 0: self.tag_resource(key.id, tags) @@ -326,7 +324,8 @@ class KmsBackend(BaseBackend): if key_id in self.keys: return self.tagger.list_tags_for_resource(key_id) raise JsonRESTError( - "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + "NotFoundException", + "The request was rejected because the specified entity or resource could not be found.", ) def tag_resource(self, key_id, tags): @@ -334,7 +333,8 @@ class KmsBackend(BaseBackend): self.tagger.tag_resource(key_id, tags) return {} raise JsonRESTError( - "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + "NotFoundException", + "The request was rejected because the specified entity or resource could not be found.", ) def untag_resource(self, key_id, tag_names): @@ -342,7 +342,8 @@ class KmsBackend(BaseBackend): self.tagger.untag_resource_using_names(key_id, tag_names) return {} raise JsonRESTError( - "NotFoundException", "The request was rejected because the specified entity or resource could not be found." + "NotFoundException", + "The request was rejected because the specified entity or resource could not be found.", ) diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index 8c17864f3..d05a53f81 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -318,7 +318,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): # KMS def get_kms_tags(kms_key_id): result = [] - for tag in self.kms_backend.list_resource_tags(kms_key_id).get("Tags",[]): + for tag in self.kms_backend.list_resource_tags(kms_key_id).get("Tags", []): result.append({"Key": tag["TagKey"], "Value": tag["TagValue"]}) return result diff --git a/setup.cfg b/setup.cfg index fb04c16a8..9dbd988db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [nosetests] verbosity=1 detailed-errors=1 -with-coverage=1 +#with-coverage=1 cover-package=moto [bdist_wheel] diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index d00c885f2..3384d940e 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals import base64 import re +from collections import OrderedDict import boto.kms +import boto3 import six import sure # noqa from boto.exception import JSONResponseError @@ -13,7 +15,7 @@ from parameterized import parameterized from moto.core.exceptions import JsonRESTError from moto.kms.models import KmsBackend from moto.kms.exceptions import NotFoundException as MotoNotFoundException -from moto import mock_kms_deprecated +from moto import mock_kms_deprecated, mock_kms PLAINTEXT_VECTORS = ( (b"some encodeable plaintext",), @@ -682,24 +684,55 @@ def test__assert_default_policy(): ) -@mock_kms_deprecated -def test_key_tagging_happy(): - client = boto3.client("kms", region_name="us-east-1") - key = client.create_key(Description="test-key-tagging") - key_id = key["KeyMetadata"]["KeyId"] +if six.PY2: + sort = sorted +else: + sort = lambda l: sorted(l, key=lambda d: d.keys()) - tags = [{"TagKey": "key1", "TagValue": "value1"}, {"TagKey": "key2", "TagValue": "value2"}] - client.tag_resource(KeyId=key_id, Tags=tags) + +@mock_kms +def test_key_tag_on_create_key_happy(): + client = boto3.client("kms", region_name="us-east-1") + + tags = [ + {"TagKey": "key1", "TagValue": "value1"}, + {"TagKey": "key2", "TagValue": "value2"}, + ] + key = client.create_key(Description="test-key-tagging", Tags=tags) + key_id = key["KeyMetadata"]["KeyId"] result = client.list_resource_tags(KeyId=key_id) actual = result.get("Tags", []) - assert tags == actual + assert sort(tags) == sort(actual) client.untag_resource(KeyId=key_id, TagKeys=["key1"]) actual = client.list_resource_tags(KeyId=key_id).get("Tags", []) expected = [{"TagKey": "key2", "TagValue": "value2"}] - assert expected == actual + assert sort(expected) == sort(actual) + + +@mock_kms +def test_key_tag_added_happy(): + client = boto3.client("kms", region_name="us-east-1") + + key = client.create_key(Description="test-key-tagging") + key_id = key["KeyMetadata"]["KeyId"] + tags = [ + {"TagKey": "key1", "TagValue": "value1"}, + {"TagKey": "key2", "TagValue": "value2"}, + ] + client.tag_resource(KeyId=key_id, Tags=tags) + + result = client.list_resource_tags(KeyId=key_id) + actual = result.get("Tags", []) + assert sort(tags) == sort(actual) + + client.untag_resource(KeyId=key_id, TagKeys=["key1"]) + + actual = client.list_resource_tags(KeyId=key_id).get("Tags", []) + expected = [{"TagKey": "key2", "TagValue": "value2"}] + assert sort(expected) == sort(actual) @mock_kms_deprecated @@ -707,19 +740,19 @@ def test_key_tagging_sad(): b = KmsBackend() try: - b.tag_resource('unknown', []) - raise 'tag_resource should fail if KeyId is not known' + b.tag_resource("unknown", []) + raise "tag_resource should fail if KeyId is not known" except JsonRESTError: pass try: - b.untag_resource('unknown', []) - raise 'untag_resource should fail if KeyId is not known' + b.untag_resource("unknown", []) + raise "untag_resource should fail if KeyId is not known" except JsonRESTError: pass try: - b.list_resource_tags('unknown') - raise 'list_resource_tags should fail if KeyId is not known' + b.list_resource_tags("unknown") + raise "list_resource_tags should fail if KeyId is not known" except JsonRESTError: pass diff --git a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py index dc75bb722..3ee517ce8 100644 --- a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py +++ b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py @@ -248,26 +248,3 @@ def test_get_many_resources(): ) # TODO test pagenation - - -@mock_kms -def test_get_kms_tags(): - kms = boto3.client("kms", region_name="us-east-1") - key = kms.create_key( - KeyUsage="ENCRYPT_DECRYPT", - Tags=[ - {"TagKey": "key_name", "TagValue": "a_value"}, - {"TagKey": "key_2", "TagValue": "val2"}, - ], - ) - key_id = key["KeyMetadata"]["KeyId"] - - rtapi = boto3.client("resourcegroupstaggingapi", region_name="us-east-1") - resp = rtapi.get_resources( - ResourceTypeFilters=["kms"], - TagFilters=[{"Key": "key_name"}], - ) - resp["ResourceTagMappingList"].should.have.length_of(1) - resp["ResourceTagMappingList"][0]["Tags"].should.contain( - {"Key": "key_name", "Value": "a_value"} - ) From c162f02091e1c19428b30f6d03de61687b625568 Mon Sep 17 00:00:00 2001 From: Brady Date: Fri, 21 Feb 2020 15:39:23 -0500 Subject: [PATCH 9/9] re-add coverage and remove unused import --- setup.cfg | 2 +- tests/test_kms/test_kms.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 9dbd988db..fb04c16a8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [nosetests] verbosity=1 detailed-errors=1 -#with-coverage=1 +with-coverage=1 cover-package=moto [bdist_wheel] diff --git a/tests/test_kms/test_kms.py b/tests/test_kms/test_kms.py index 3384d940e..a04a24a82 100644 --- a/tests/test_kms/test_kms.py +++ b/tests/test_kms/test_kms.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import base64 import re -from collections import OrderedDict import boto.kms import boto3