diff --git a/CHANGELOG.md b/CHANGELOG.md index 457502b9c..1b1ce8603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ Moto Changelog ============== +4.1.11 +------ +Docker Digest for 4.1.11: + + New Methods: + + * AppSync: + * get_introspection_schema() + + * Comprehend: + * detect_key_phrases() + * detect_pii_entities() + * detect_sentiment() + + Miscellaneous: + * EC2: describe_key_pairs() now returns the CreateTime-attribute + * EC2: describe_spot_fleet_requests() now returns the Tags-attribute + * ECR: put_image(): now behaves correctly on duplicate images with duplicate tags + * Organizations: create_policy() now supports the Tags-parameter + * RDS: creation times of all objects are now in UTC + * RDS: creation times of all objects are now in UTC + * S3: Bucket names are now global, meaning they have to be unique across accounts + * S3: select_object_content() now supports None-values + * S3: select_object_content() now supports nested FROM-clauses (from x.y as xy) + * SecretsManager - update_secret() now supports the Description-parameter + * SNS now returns the correct error message for non-existing topics + * SNS: Topics are no longer accessible across regions (only across accounts) + * SNS: delete_topic() is now idempotent and no longer throws an error for non-existent topics + * SQS: Requests and responses in JSON-format are now supported + * SSM: MaintenanceWindows now have tagging support + 4.1.10 ------ Docker Digest for 4.1.10: _sha256:095d1dfadc71b4c68f05240129a32acf6dd7ba722c78afd4f01d8c7c3af0ebb4_ diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a741212a0..693cbd695 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -301,9 +301,11 @@ ## appsync
-29% implemented +25% implemented - [ ] associate_api +- [ ] associate_merged_graphql_api +- [ ] associate_source_graphql_api - [ ] create_api_cache - [X] create_api_key - [ ] create_data_source @@ -321,6 +323,8 @@ - [ ] delete_resolver - [ ] delete_type - [ ] disassociate_api +- [ ] disassociate_merged_graphql_api +- [ ] disassociate_source_graphql_api - [ ] evaluate_code - [ ] evaluate_mapping_template - [ ] flush_api_cache @@ -333,6 +337,7 @@ - [ ] get_introspection_schema - [ ] get_resolver - [X] get_schema_creation_status +- [ ] get_source_api_association - [X] get_type - [X] list_api_keys - [ ] list_data_sources @@ -341,9 +346,12 @@ - [X] list_graphql_apis - [ ] list_resolvers - [ ] list_resolvers_by_function +- [ ] list_source_api_associations - [X] list_tags_for_resource - [ ] list_types +- [ ] list_types_by_association - [X] start_schema_creation +- [ ] start_schema_merge - [X] tag_resource - [X] untag_resource - [ ] update_api_cache @@ -353,6 +361,7 @@ - [ ] update_function - [X] update_graphql_api - [ ] update_resolver +- [ ] update_source_api_association - [ ] update_type
@@ -371,6 +380,7 @@ - [X] create_prepared_statement - [ ] create_presigned_notebook_url - [X] create_work_group +- [ ] delete_capacity_reservation - [ ] delete_data_catalog - [ ] delete_named_query - [ ] delete_notebook @@ -788,7 +798,7 @@ ## cloudtrail
-36% implemented +34% implemented - [X] add_tags - [ ] cancel_query @@ -826,9 +836,11 @@ - [ ] register_organization_delegated_admin - [X] remove_tags - [ ] restore_event_data_store +- [ ] start_event_data_store_ingestion - [ ] start_import - [X] start_logging - [ ] start_query +- [ ] stop_event_data_store_ingestion - [ ] stop_import - [X] stop_logging - [ ] update_channel @@ -5030,7 +5042,7 @@ ## quicksight
-9% implemented +8% implemented - [ ] cancel_ingestion - [ ] create_account_customization @@ -5083,6 +5095,8 @@ - [ ] describe_analysis - [ ] describe_analysis_definition - [ ] describe_analysis_permissions +- [ ] describe_asset_bundle_export_job +- [ ] describe_asset_bundle_import_job - [ ] describe_dashboard - [ ] describe_dashboard_definition - [ ] describe_dashboard_permissions @@ -5119,6 +5133,8 @@ - [ ] get_dashboard_embed_url - [ ] get_session_embed_url - [ ] list_analyses +- [ ] list_asset_bundle_export_jobs +- [ ] list_asset_bundle_import_jobs - [ ] list_dashboard_versions - [ ] list_dashboards - [ ] list_data_sets @@ -5153,6 +5169,8 @@ - [ ] search_data_sources - [ ] search_folders - [ ] search_groups +- [ ] start_asset_bundle_export_job +- [ ] start_asset_bundle_import_job - [ ] tag_resource - [ ] untag_resource - [ ] update_account_customization @@ -6575,11 +6593,11 @@ ## sns
-52% implemented +78% implemented - [X] add_permission -- [ ] check_if_phone_number_is_opted_out -- [ ] confirm_subscription +- [X] check_if_phone_number_is_opted_out +- [X] confirm_subscription - [X] create_platform_application - [X] create_platform_endpoint - [ ] create_sms_sandbox_phone_number @@ -6589,29 +6607,29 @@ - [ ] delete_sms_sandbox_phone_number - [X] delete_topic - [ ] get_data_protection_policy -- [ ] get_endpoint_attributes -- [ ] get_platform_application_attributes -- [ ] get_sms_attributes +- [X] get_endpoint_attributes +- [X] get_platform_application_attributes +- [X] get_sms_attributes - [ ] get_sms_sandbox_account_status - [X] get_subscription_attributes -- [ ] get_topic_attributes +- [X] get_topic_attributes - [X] list_endpoints_by_platform_application - [ ] list_origination_numbers -- [ ] list_phone_numbers_opted_out +- [X] list_phone_numbers_opted_out - [X] list_platform_applications - [ ] list_sms_sandbox_phone_numbers - [X] list_subscriptions -- [ ] list_subscriptions_by_topic +- [X] list_subscriptions_by_topic - [X] list_tags_for_resource - [X] list_topics -- [ ] opt_in_phone_number +- [X] opt_in_phone_number - [X] publish - [X] publish_batch - [ ] put_data_protection_policy - [X] remove_permission - [X] set_endpoint_attributes -- [ ] set_platform_application_attributes -- [ ] set_sms_attributes +- [X] set_platform_application_attributes +- [X] set_sms_attributes - [X] set_subscription_attributes - [ ] set_topic_attributes - [X] subscribe @@ -7036,7 +7054,7 @@ ## wafv2
-23% implemented +22% implemented - [X] associate_web_acl - [ ] check_capacity @@ -7052,6 +7070,8 @@ - [ ] delete_regex_pattern_set - [ ] delete_rule_group - [X] delete_web_acl +- [ ] describe_all_managed_products +- [ ] describe_managed_products_by_vendor - [ ] describe_managed_rule_group - [X] disassociate_web_acl - [ ] generate_mobile_sdk_release_url diff --git a/docs/docs/services/appsync.rst b/docs/docs/services/appsync.rst index a68f03c94..bd2e16dd2 100644 --- a/docs/docs/services/appsync.rst +++ b/docs/docs/services/appsync.rst @@ -28,6 +28,8 @@ appsync |start-h3| Implemented features for this service |end-h3| - [ ] associate_api +- [ ] associate_merged_graphql_api +- [ ] associate_source_graphql_api - [ ] create_api_cache - [X] create_api_key - [ ] create_data_source @@ -45,6 +47,8 @@ appsync - [ ] delete_resolver - [ ] delete_type - [ ] disassociate_api +- [ ] disassociate_merged_graphql_api +- [ ] disassociate_source_graphql_api - [ ] evaluate_code - [ ] evaluate_mapping_template - [ ] flush_api_cache @@ -54,9 +58,10 @@ appsync - [ ] get_domain_name - [ ] get_function - [X] get_graphql_api -- [X] get_introspection_schema +- [ ] get_introspection_schema - [ ] get_resolver - [X] get_schema_creation_status +- [ ] get_source_api_association - [X] get_type - [X] list_api_keys @@ -73,9 +78,12 @@ appsync - [ ] list_resolvers - [ ] list_resolvers_by_function +- [ ] list_source_api_associations - [X] list_tags_for_resource - [ ] list_types +- [ ] list_types_by_association - [X] start_schema_creation +- [ ] start_schema_merge - [X] tag_resource - [X] untag_resource - [ ] update_api_cache @@ -85,5 +93,6 @@ appsync - [ ] update_function - [X] update_graphql_api - [ ] update_resolver +- [ ] update_source_api_association - [ ] update_type diff --git a/docs/docs/services/athena.rst b/docs/docs/services/athena.rst index 20418dde8..88970995b 100644 --- a/docs/docs/services/athena.rst +++ b/docs/docs/services/athena.rst @@ -36,6 +36,7 @@ athena - [X] create_prepared_statement - [ ] create_presigned_notebook_url - [X] create_work_group +- [ ] delete_capacity_reservation - [ ] delete_data_catalog - [ ] delete_named_query - [ ] delete_notebook diff --git a/docs/docs/services/cloudtrail.rst b/docs/docs/services/cloudtrail.rst index 225a57be5..3942a8137 100644 --- a/docs/docs/services/cloudtrail.rst +++ b/docs/docs/services/cloudtrail.rst @@ -67,9 +67,11 @@ cloudtrail - [ ] register_organization_delegated_admin - [X] remove_tags - [ ] restore_event_data_store +- [ ] start_event_data_store_ingestion - [ ] start_import - [X] start_logging - [ ] start_query +- [ ] stop_event_data_store_ingestion - [ ] stop_import - [X] stop_logging - [ ] update_channel diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst index c0e64b57b..1bb26ad7a 100644 --- a/docs/docs/services/quicksight.rst +++ b/docs/docs/services/quicksight.rst @@ -78,6 +78,8 @@ quicksight - [ ] describe_analysis - [ ] describe_analysis_definition - [ ] describe_analysis_permissions +- [ ] describe_asset_bundle_export_job +- [ ] describe_asset_bundle_import_job - [ ] describe_dashboard - [ ] describe_dashboard_definition - [ ] describe_dashboard_permissions @@ -114,6 +116,8 @@ quicksight - [ ] get_dashboard_embed_url - [ ] get_session_embed_url - [ ] list_analyses +- [ ] list_asset_bundle_export_jobs +- [ ] list_asset_bundle_import_jobs - [ ] list_dashboard_versions - [ ] list_dashboards - [ ] list_data_sets @@ -165,6 +169,8 @@ quicksight - [ ] search_data_sources - [ ] search_folders - [ ] search_groups +- [ ] start_asset_bundle_export_job +- [ ] start_asset_bundle_import_job - [ ] tag_resource - [ ] untag_resource - [ ] update_account_customization diff --git a/docs/docs/services/sns.rst b/docs/docs/services/sns.rst index 352176938..a00be10ab 100644 --- a/docs/docs/services/sns.rst +++ b/docs/docs/services/sns.rst @@ -28,8 +28,12 @@ sns |start-h3| Implemented features for this service |end-h3| - [X] add_permission -- [x] check_if_phone_number_is_opted_out -- [x] confirm_subscription +- [X] check_if_phone_number_is_opted_out + + Current implementation returns True for all numbers ending in '99' + + +- [X] confirm_subscription - [X] create_platform_application - [X] create_platform_endpoint - [ ] create_sms_sandbox_phone_number @@ -39,22 +43,22 @@ sns - [ ] delete_sms_sandbox_phone_number - [X] delete_topic - [ ] get_data_protection_policy -- [x] get_endpoint_attributes -- [x] get_platform_application_attributes -- [x] get_sms_attributes +- [X] get_endpoint_attributes +- [X] get_platform_application_attributes +- [X] get_sms_attributes - [ ] get_sms_sandbox_account_status - [X] get_subscription_attributes -- [x] get_topic_attributes +- [X] get_topic_attributes - [X] list_endpoints_by_platform_application - [ ] list_origination_numbers -- [x] list_phone_numbers_opted_out +- [X] list_phone_numbers_opted_out - [X] list_platform_applications - [ ] list_sms_sandbox_phone_numbers - [X] list_subscriptions -- [x] list_subscriptions_by_topic +- [X] list_subscriptions_by_topic - [X] list_tags_for_resource - [X] list_topics -- [x] opt_in_phone_number +- [X] opt_in_phone_number - [X] publish - [X] publish_batch @@ -64,8 +68,8 @@ sns - [ ] put_data_protection_policy - [X] remove_permission - [X] set_endpoint_attributes -- [x] set_platform_application_attributes -- [x] set_sms_attributes +- [X] set_platform_application_attributes +- [X] set_sms_attributes - [X] set_subscription_attributes - [ ] set_topic_attributes - [X] subscribe diff --git a/docs/docs/services/wafv2.rst b/docs/docs/services/wafv2.rst index 3b11eb764..469a83524 100644 --- a/docs/docs/services/wafv2.rst +++ b/docs/docs/services/wafv2.rst @@ -53,6 +53,8 @@ wafv2 The LockToken-parameter is not yet implemented +- [ ] describe_all_managed_products +- [ ] describe_managed_products_by_vendor - [ ] describe_managed_rule_group - [X] disassociate_web_acl - [ ] generate_mobile_sdk_release_url diff --git a/moto/sns/models.py b/moto/sns/models.py index e252af984..840aa6f37 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -5,7 +5,7 @@ import requests import re from collections import OrderedDict -from typing import Any, Dict, List, Iterable, Optional, Tuple +from typing import Any, Dict, List, Iterable, Optional, Tuple, Set from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel from moto.core.utils import ( @@ -76,7 +76,9 @@ class Topic(CloudFormationModel): deduplication_id: Optional[str] = None, ) -> str: message_id = str(mock_random.uuid4()) - subscriptions, _ = self.sns_backend.list_subscriptions(self.arn) + subscriptions, _ = self.sns_backend.list_subscriptions_by_topic( + topic_arn=self.arn + ) for subscription in subscriptions: subscription.publish( message, @@ -418,7 +420,13 @@ class SNSBackend(BaseBackend): service_region, zones, "sns" ) - def update_sms_attributes(self, attrs: Dict[str, str]) -> None: + def get_sms_attributes(self, filter_list: Set[str]) -> Dict[str, str]: + if len(filter_list) > 0: + return {k: v for k, v in self.sms_attributes.items() if k in filter_list} + else: + return self.sms_attributes + + def set_sms_attributes(self, attrs: Dict[str, str]) -> None: self.sms_attributes.update(attrs) def create_topic( @@ -427,7 +435,6 @@ class SNSBackend(BaseBackend): attributes: Optional[Dict[str, str]] = None, tags: Optional[Dict[str, str]] = None, ) -> Topic: - if attributes is None: attributes = {} if attributes.get("FifoTopic") and attributes["FifoTopic"].lower() == "true": @@ -555,16 +562,18 @@ class SNSBackend(BaseBackend): self.subscriptions.pop(subscription_arn, None) def list_subscriptions( - self, topic_arn: Optional[str] = None, next_token: Optional[str] = None + self, next_token: Optional[str] = None ) -> Tuple[List[Subscription], Optional[int]]: - if topic_arn: - topic = self.get_topic(topic_arn) - filtered = OrderedDict( - [(sub.arn, sub) for sub in self._get_topic_subscriptions(topic)] - ) - return self._get_values_nexttoken(filtered, next_token) - else: - return self._get_values_nexttoken(self.subscriptions, next_token) + return self._get_values_nexttoken(self.subscriptions, next_token) + + def list_subscriptions_by_topic( + self, topic_arn: str, next_token: Optional[str] = None + ) -> Tuple[List[Subscription], Optional[int]]: + topic = self.get_topic(topic_arn) + filtered = OrderedDict( + [(sub.arn, sub) for sub in self._get_topic_subscriptions(topic)] + ) + return self._get_values_nexttoken(filtered, next_token) def publish( self, @@ -642,7 +651,7 @@ class SNSBackend(BaseBackend): except KeyError: raise SNSNotFoundError(f"Application with arn {arn} not found") - def set_application_attributes( + def set_platform_application_attributes( self, arn: str, attributes: Dict[str, Any] ) -> PlatformApplication: application = self.get_application(arn) @@ -1048,6 +1057,35 @@ class SNSBackend(BaseBackend): ) return successful, failed + def check_if_phone_number_is_opted_out(self, number: str) -> bool: + """ + Current implementation returns True for all numbers ending in '99' + """ + return number.endswith("99") + + def list_phone_numbers_opted_out(self) -> List[str]: + return self.opt_out_numbers + + def opt_in_phone_number(self, number: str) -> None: + try: + self.opt_out_numbers.remove(number) + except ValueError: + pass + + def confirm_subscription(self) -> None: + pass + + def get_endpoint_attributes(self, arn: str) -> Dict[str, str]: + endpoint = self.get_endpoint(arn) + return endpoint.attributes + + def get_platform_application_attributes(self, arn: str) -> Dict[str, str]: + application = self.get_application(arn) + return application.attributes + + def get_topic_attributes(self) -> None: + pass + sns_backends = BackendDict(SNSBackend, "sns") diff --git a/moto/sns/responses.py b/moto/sns/responses.py index 5bbd84d92..f8afef34e 100644 --- a/moto/sns/responses.py +++ b/moto/sns/responses.py @@ -307,7 +307,7 @@ class SNSResponse(BaseResponse): def list_subscriptions_by_topic(self) -> str: topic_arn = self._get_param("TopicArn") next_token = self._get_param("NextToken") - subscriptions, next_token = self.backend.list_subscriptions( + subscriptions, next_token = self.backend.list_subscriptions_by_topic( topic_arn, next_token=next_token ) @@ -443,14 +443,14 @@ class SNSResponse(BaseResponse): def get_platform_application_attributes(self) -> str: arn = self._get_param("PlatformApplicationArn") - application = self.backend.get_application(arn) + attributes = self.backend.get_platform_application_attributes(arn) if self.request_json: return json.dumps( { "GetPlatformApplicationAttributesResponse": { "GetPlatformApplicationAttributesResult": { - "Attributes": application.attributes + "Attributes": attributes }, "ResponseMetadata": { "RequestId": "384ac68d-3775-11df-8963-01868b7c937f" @@ -460,13 +460,13 @@ class SNSResponse(BaseResponse): ) template = self.response_template(GET_PLATFORM_APPLICATION_ATTRIBUTES_TEMPLATE) - return template.render(application=application) + return template.render(attributes=attributes) def set_platform_application_attributes(self) -> str: arn = self._get_param("PlatformApplicationArn") attributes = self._get_attributes() - self.backend.set_application_attributes(arn, attributes) + self.backend.set_platform_application_attributes(arn, attributes) if self.request_json: return json.dumps( @@ -589,15 +589,13 @@ class SNSResponse(BaseResponse): def get_endpoint_attributes(self) -> Union[str, Tuple[str, Dict[str, int]]]: arn = self._get_param("EndpointArn") try: - endpoint = self.backend.get_endpoint(arn) + attributes = self.backend.get_endpoint_attributes(arn) if self.request_json: return json.dumps( { "GetEndpointAttributesResponse": { - "GetEndpointAttributesResult": { - "Attributes": endpoint.attributes - }, + "GetEndpointAttributesResult": {"Attributes": attributes}, "ResponseMetadata": { "RequestId": "384ac68d-3775-11df-8963-01868b7c937f" }, @@ -606,7 +604,7 @@ class SNSResponse(BaseResponse): ) template = self.response_template(GET_ENDPOINT_ATTRIBUTES_TEMPLATE) - return template.render(endpoint=endpoint) + return template.render(attributes=attributes) except SNSNotFoundError: error_response = self._error("NotFound", "Endpoint does not exist") return error_response, dict(status=404) @@ -683,7 +681,7 @@ class SNSResponse(BaseResponse): if "key" in item and "value" in item: result[item["key"]] = item["value"] - self.backend.update_sms_attributes(result) + self.backend.set_sms_attributes(result) template = self.response_template(SET_SMS_ATTRIBUTES_TEMPLATE) return template.render() @@ -694,12 +692,7 @@ class SNSResponse(BaseResponse): if key.startswith("attributes.member.1"): filter_list.add(value[0]) - if len(filter_list) > 0: - result = { - k: v for k, v in self.backend.sms_attributes.items() if k in filter_list - } - else: - result = self.backend.sms_attributes + result = self.backend.get_sms_attributes(filter_list) template = self.response_template(GET_SMS_ATTRIBUTES_TEMPLATE) return template.render(attributes=result) @@ -715,21 +708,19 @@ class SNSResponse(BaseResponse): ) return error_response, dict(status=400) - # There should be a nicer way to set if a nubmer has opted out + x = self.backend.check_if_phone_number_is_opted_out(number) template = self.response_template(CHECK_IF_OPTED_OUT_TEMPLATE) - return template.render(opt_out=str(number.endswith("99")).lower()) + return template.render(opt_out=str(x).lower()) def list_phone_numbers_opted_out(self) -> str: + numbers = self.backend.list_phone_numbers_opted_out() template = self.response_template(LIST_OPTOUT_TEMPLATE) - return template.render(opt_outs=self.backend.opt_out_numbers) + return template.render(opt_outs=numbers) def opt_in_phone_number(self) -> str: number = self._get_param("phoneNumber") - try: - self.backend.opt_out_numbers.remove(number) - except ValueError: - pass + self.backend.opt_in_phone_number(number) template = self.response_template(OPT_IN_NUMBER_TEMPLATE) return template.render() @@ -955,10 +946,10 @@ DELETE_PLATFORM_APPLICATION_TEMPLATE = """ - {% for attribute in endpoint.attributes %} + {% for attribute in attributes %} {{ attribute }} - {{ endpoint.attributes[attribute] }} + {{ attributes[attribute] }} {% endfor %} @@ -994,10 +985,10 @@ LIST_ENDPOINTS_BY_PLATFORM_APPLICATION_TEMPLATE = """ - {% for attribute in application.attributes %} + {% for attribute in attributes %} {{ attribute }} - {{ application.attributes[attribute] }} + {{ attributes[attribute] }} {% endfor %}