Repository: getmoto/moto
Pull request #7058 (2023-11-23 00:32:26)

Enhancements to CloudWatch Export Tasks Testing

This revision brings improvements to the `moto` library, with a focus on enhancing logging capabilities and export task features. A breakdown of the updates is as follows:

#### 1. Upgraded Logging Model:
   - The `logs/models.py` file has been revised to include new imports and updates to the `LogsBackend` class.
   - Introduction of a new class, `ExportTask`, designed to manage export tasks encompassing aspects like task initiation, status monitoring, and JSON format representation.
   - The `LogsBackend` class has been modified to facilitate the initiation and management of export tasks through methods like `create_export_task` and `describe_export_tasks`, which handle tasks from start to finish and provide detailed descriptions of export tasks.

#### 2. Refined Log Management Responses:
   - Changes in `logs/responses.py` include an updated `create_export_task` and the new method `describe_export_tasks`. These modifications are in line with the latest backend enhancements, ensuring a smooth integration with the AWS CloudWatch Logs service.

#### 3. Extensive Testing Suite:
   - New test scenarios in `tests/test_logs/test_logs.py` cover a wide range of functionalities, validating the implementation of export task creation, execution, and description.
   - The tests ensure robustness by covering successful task creation, status verification, and the handling of edge cases like resource not found exceptions.



**Files Changed:**
- `moto/logs/models.py`: Modifications and additions to the logging model.
- `moto/logs/responses.py`: Updated response handling for log-related requests.
- `tests/test_logs/test_logs.py`: Added comprehensive tests for new functionalities.



Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2e0096eb1..03f196446 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -78,6 +78,10 @@ jobs:
     needs: lint
     uses: ./.github/workflows/tests_sdk_dotnet.yml
 
+  rubytest:
+    needs: lint
+    uses: ./.github/workflows/tests_sdk_ruby.yml
+
   test:
     needs: [lint]
     if: "!contains(github.event.pull_request.labels.*.name, 'java')"
@@ -98,6 +102,7 @@ jobs:
     runs-on: ubuntu-latest
     permissions:
       id-token: write
+      packages: write
     needs: [dotnettest, javatest, test, testserver ]
     if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'getmoto/moto' }}
     steps:
diff --git a/.gitignore b/.gitignore
index b7cc0fbe3..690cc0cff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,4 @@ moto/moto_proxy/certs/*.conf
 other_langs/tests_java/target
 other_langs/tests_dotnet/ExampleTestProject/bin
 other_langs/tests_dotnet/ExampleTestProject/obj
+other_langs/tests_ruby/Gemfile.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be0712d27..f390d1794 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,24 @@
 Moto Changelog
 ==============
 
+4.2.10
+-----
+Docker Digest for 4.2.10: _sha256:f72acd62b994654d01bdec6f5cc779f4ab30083b441e2fb7eff0c13e0bbfdca7_
+
+    New Methods:
+        * CognitoIdentity: list_identity_pools()
+
+    Miscellaneous:
+        * Autoscaling: describe_tags() now supports the key/value filters
+        * CloudFormation: AWS::Logs::LogGroup now supports tags
+        * CloudWatch: get_metric_data() no longer throws an error when supplying >10 queries
+        * CognitoIdentity: get_credentials_for_identity() now returns Expiration as a number, fixing compatibility with the GoLang SDK
+        * EFS: describe_access_points() now supports the FileSystemId-parameter
+        * LakeFormation: list_permissions() now supports the DataLocation-parameter
+        * LakeFormation: register_resource() now throws an exception when registering an existing resource
+        * SQS: Ensure all responses are in JSON-format when required, fixing compatibility with the Ruby SDK
+
+
 4.2.9
 -----
 Docker Digest for 4.2.9: _sha256:4e9d89322b5ca9196fa7efda78b1269580be7aa6879894950e2728edc946573f_
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 5d8ba44d1..7a75fb3ab 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -1137,7 +1137,7 @@
 
 ## cognito-identity
 <details>
-<summary>34% implemented</summary>
+<summary>39% implemented</summary>
 
 - [X] create_identity_pool
 - [ ] delete_identities
@@ -1151,7 +1151,7 @@
 - [X] get_open_id_token_for_developer_identity
 - [ ] get_principal_tag_attribute_map
 - [X] list_identities
-- [ ] list_identity_pools
+- [X] list_identity_pools
 - [ ] list_tags_for_resource
 - [ ] lookup_developer_identity
 - [ ] merge_developer_identities
@@ -1921,6 +1921,7 @@
 - [ ] associate_enclave_certificate_iam_role
 - [X] associate_iam_instance_profile
 - [ ] associate_instance_event_window
+- [ ] associate_ipam_byoasn
 - [ ] associate_ipam_resource_discovery
 - [ ] associate_nat_gateway_address
 - [X] associate_route_table
@@ -2111,6 +2112,7 @@
 - [ ] delete_vpn_connection_route
 - [X] delete_vpn_gateway
 - [ ] deprovision_byoip_cidr
+- [ ] deprovision_ipam_byoasn
 - [ ] deprovision_ipam_pool_cidr
 - [ ] deprovision_public_ipv4_pool_cidr
 - [X] deregister_image
@@ -2173,6 +2175,7 @@
 - [X] describe_instance_types
 - [X] describe_instances
 - [X] describe_internet_gateways
+- [ ] describe_ipam_byoasn
 - [ ] describe_ipam_pools
 - [ ] describe_ipam_resource_discoveries
 - [ ] describe_ipam_resource_discovery_associations
@@ -2291,6 +2294,7 @@
 - [ ] disassociate_enclave_certificate_iam_role
 - [X] disassociate_iam_instance_profile
 - [ ] disassociate_instance_event_window
+- [ ] disassociate_ipam_byoasn
 - [ ] disassociate_ipam_resource_discovery
 - [ ] disassociate_nat_gateway_address
 - [X] disassociate_route_table
@@ -2339,6 +2343,7 @@
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
 - [ ] get_ipam_discovered_accounts
+- [ ] get_ipam_discovered_public_addresses
 - [ ] get_ipam_discovered_resource_cidrs
 - [ ] get_ipam_pool_allocations
 - [ ] get_ipam_pool_cidrs
@@ -2444,6 +2449,7 @@
 - [ ] move_address_to_vpc
 - [ ] move_byoip_cidr_to_ipam
 - [ ] provision_byoip_cidr
+- [ ] provision_ipam_byoasn
 - [ ] provision_ipam_pool_cidr
 - [ ] provision_public_ipv4_pool_cidr
 - [ ] purchase_capacity_block
@@ -2521,7 +2527,7 @@
 
 ## ecr
 <details>
-<summary>68% implemented</summary>
+<summary>65% implemented</summary>
 
 - [ ] batch_check_layer_availability
 - [X] batch_delete_image
@@ -2563,7 +2569,9 @@
 - [ ] start_lifecycle_policy_preview
 - [X] tag_resource
 - [X] untag_resource
+- [ ] update_pull_through_cache_rule
 - [ ] upload_layer_part
+- [ ] validate_pull_through_cache_rule
 </details>
 
 ## ecs
@@ -3286,7 +3294,7 @@
 
 ## glue
 <details>
-<summary>32% implemented</summary>
+<summary>31% implemented</summary>
 
 - [X] batch_create_partition
 - [ ] batch_delete_connection
@@ -3366,6 +3374,8 @@
 - [ ] get_classifiers
 - [ ] get_column_statistics_for_partition
 - [ ] get_column_statistics_for_table
+- [ ] get_column_statistics_task_run
+- [ ] get_column_statistics_task_runs
 - [ ] get_connection
 - [ ] get_connections
 - [X] get_crawler
@@ -3426,6 +3436,7 @@
 - [ ] get_workflow_runs
 - [ ] import_catalog_to_glue
 - [ ] list_blueprints
+- [ ] list_column_statistics_task_runs
 - [X] list_crawlers
 - [ ] list_crawls
 - [ ] list_custom_entity_types
@@ -3456,6 +3467,7 @@
 - [ ] run_statement
 - [ ] search_tables
 - [ ] start_blueprint_run
+- [ ] start_column_statistics_task_run
 - [X] start_crawler
 - [ ] start_crawler_schedule
 - [ ] start_data_quality_rule_recommendation_run
@@ -3467,6 +3479,7 @@
 - [ ] start_ml_labeling_set_generation_task_run
 - [X] start_trigger
 - [ ] start_workflow_run
+- [ ] stop_column_statistics_task_run
 - [X] stop_crawler
 - [ ] stop_crawler_schedule
 - [X] stop_session
@@ -5331,6 +5344,7 @@
 - [X] create_ingestion
 - [ ] create_namespace
 - [ ] create_refresh_schedule
+- [ ] create_role_membership
 - [ ] create_template
 - [ ] create_template_alias
 - [ ] create_theme
@@ -5352,6 +5366,8 @@
 - [ ] delete_iam_policy_assignment
 - [ ] delete_namespace
 - [ ] delete_refresh_schedule
+- [ ] delete_role_custom_permission
+- [ ] delete_role_membership
 - [ ] delete_template
 - [ ] delete_template_alias
 - [ ] delete_theme
@@ -5389,6 +5405,7 @@
 - [ ] describe_ip_restriction
 - [ ] describe_namespace
 - [ ] describe_refresh_schedule
+- [ ] describe_role_custom_permission
 - [ ] describe_template
 - [ ] describe_template_alias
 - [ ] describe_template_definition
@@ -5422,6 +5439,7 @@
 - [ ] list_ingestions
 - [ ] list_namespaces
 - [ ] list_refresh_schedules
+- [ ] list_role_memberships
 - [ ] list_tags_for_resource
 - [ ] list_template_aliases
 - [ ] list_template_versions
@@ -5466,6 +5484,7 @@
 - [ ] update_ip_restriction
 - [ ] update_public_sharing_settings
 - [ ] update_refresh_schedule
+- [ ] update_role_custom_permission
 - [ ] update_template
 - [ ] update_template_alias
 - [ ] update_template_permissions
@@ -5691,7 +5710,7 @@
 
 ## redshift
 <details>
-<summary>23% implemented</summary>
+<summary>22% implemented</summary>
 
 - [ ] accept_reserved_node_exchange
 - [ ] add_partner
@@ -5715,6 +5734,7 @@
 - [ ] create_event_subscription
 - [ ] create_hsm_client_certificate
 - [ ] create_hsm_configuration
+- [ ] create_redshift_idc_application
 - [ ] create_scheduled_action
 - [X] create_snapshot_copy_grant
 - [ ] create_snapshot_schedule
@@ -5733,6 +5753,7 @@
 - [ ] delete_hsm_client_certificate
 - [ ] delete_hsm_configuration
 - [ ] delete_partner
+- [ ] delete_redshift_idc_application
 - [ ] delete_resource_policy
 - [ ] delete_scheduled_action
 - [X] delete_snapshot_copy_grant
@@ -5767,6 +5788,7 @@
 - [ ] describe_node_configuration_options
 - [ ] describe_orderable_cluster_options
 - [ ] describe_partners
+- [ ] describe_redshift_idc_applications
 - [ ] describe_reserved_node_exchange_status
 - [ ] describe_reserved_node_offerings
 - [ ] describe_reserved_nodes
@@ -5802,6 +5824,7 @@
 - [ ] modify_custom_domain_association
 - [ ] modify_endpoint_access
 - [ ] modify_event_subscription
+- [ ] modify_redshift_idc_application
 - [ ] modify_scheduled_action
 - [X] modify_snapshot_copy_retention_period
 - [ ] modify_snapshot_schedule
@@ -7215,31 +7238,59 @@
 
 ## sso-admin
 <details>
-<summary>21% implemented</summary>
+<summary>10% implemented</summary>
 
 - [ ] attach_customer_managed_policy_reference_to_permission_set
 - [ ] attach_managed_policy_to_permission_set
 - [X] create_account_assignment
+- [ ] create_application
+- [ ] create_application_assignment
+- [ ] create_instance
 - [ ] create_instance_access_control_attribute_configuration
 - [X] create_permission_set
+- [ ] create_trusted_token_issuer
 - [X] delete_account_assignment
+- [ ] delete_application
+- [ ] delete_application_access_scope
+- [ ] delete_application_assignment
+- [ ] delete_application_authentication_method
+- [ ] delete_application_grant
 - [ ] delete_inline_policy_from_permission_set
+- [ ] delete_instance
 - [ ] delete_instance_access_control_attribute_configuration
 - [X] delete_permission_set
 - [ ] delete_permissions_boundary_from_permission_set
+- [ ] delete_trusted_token_issuer
 - [ ] describe_account_assignment_creation_status
 - [ ] describe_account_assignment_deletion_status
+- [ ] describe_application
+- [ ] describe_application_assignment
+- [ ] describe_application_provider
+- [ ] describe_instance
 - [ ] describe_instance_access_control_attribute_configuration
 - [X] describe_permission_set
 - [ ] describe_permission_set_provisioning_status
+- [ ] describe_trusted_token_issuer
 - [ ] detach_customer_managed_policy_reference_from_permission_set
 - [ ] detach_managed_policy_from_permission_set
+- [ ] get_application_access_scope
+- [ ] get_application_assignment_configuration
+- [ ] get_application_authentication_method
+- [ ] get_application_grant
 - [ ] get_inline_policy_for_permission_set
 - [ ] get_permissions_boundary_for_permission_set
 - [ ] list_account_assignment_creation_status
 - [ ] list_account_assignment_deletion_status
 - [X] list_account_assignments
+- [ ] list_account_assignments_for_principal
 - [ ] list_accounts_for_provisioned_permission_set
+- [ ] list_application_access_scopes
+- [ ] list_application_assignments
+- [ ] list_application_assignments_for_principal
+- [ ] list_application_authentication_methods
+- [ ] list_application_grants
+- [ ] list_application_providers
+- [ ] list_applications
 - [ ] list_customer_managed_policy_references_in_permission_set
 - [ ] list_instances
 - [ ] list_managed_policies_in_permission_set
@@ -7247,13 +7298,21 @@
 - [X] list_permission_sets
 - [ ] list_permission_sets_provisioned_to_account
 - [ ] list_tags_for_resource
+- [ ] list_trusted_token_issuers
 - [ ] provision_permission_set
+- [ ] put_application_access_scope
+- [ ] put_application_assignment_configuration
+- [ ] put_application_authentication_method
+- [ ] put_application_grant
 - [ ] put_inline_policy_to_permission_set
 - [ ] put_permissions_boundary_to_permission_set
 - [ ] tag_resource
 - [ ] untag_resource
+- [ ] update_application
+- [ ] update_instance
 - [ ] update_instance_access_control_attribute_configuration
 - [X] update_permission_set
+- [ ] update_trusted_token_issuer
 </details>
 
 ## stepfunctions
@@ -7675,7 +7734,6 @@
 - lookoutvision
 - m2
 - machinelearning
-- macie
 - macie2
 - managedblockchain-query
 - marketplace-catalog
@@ -7763,6 +7821,7 @@
 - tnb
 - transfer
 - translate
+- trustedadvisor
 - verifiedpermissions
 - voice-id
 - vpc-lattice
diff --git a/MANIFEST.in b/MANIFEST.in
index eeed788f7..d35a9d24a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,7 +1,7 @@
 include README.md LICENSE AUTHORS.md
 include requirements.txt requirements-dev.txt
+include moto/py.typed
 include moto/config/resources/aws_managed_rules.json
-include moto/ec2/_models/*.py
 include moto/ec2/resources/instance_types.json
 include moto/ec2/resources/instance_type_offerings/*/*.json
 include moto/ec2/resources/latest_amis/*.json
@@ -14,7 +14,6 @@ include moto/moto_proxy/*
 include moto/moto_proxy/certs/__init__.py
 include moto/moto_proxy/certs/req.conf.tmpl
 include moto/rds/resources/cluster_options/*.json
-include moto/servicequotas/resources/*/*.json
 include moto/ssm/resources/*.json
 include moto/ssm/resources/ami-amazon-linux-latest/*.json
 include moto/ssm/resources/ecs/optimized_amis/*.json
diff --git a/docs/docs/faq.rst b/docs/docs/faq.rst
index 96955863d..5380569e3 100644
--- a/docs/docs/faq.rst
+++ b/docs/docs/faq.rst
@@ -8,6 +8,12 @@
 FAQ
 ======
 
+Why is my test data disappearing?
+###################################
+To prevent state from leaking across different tests, Moto automatically deletes any created data after a Moto-decorator ends.
+
+So make sure that your decorator is active for the entire duration of the test-method.
+
 Is Moto concurrency safe?
 ############################
 
diff --git a/docs/docs/getting_started.rst b/docs/docs/getting_started.rst
index df50a696d..a184aef0e 100644
--- a/docs/docs/getting_started.rst
+++ b/docs/docs/getting_started.rst
@@ -254,23 +254,33 @@ Here is an example:
         with mock_s3():
             yield boto3.client("s3", region_name="us-east-1")
 
+    @pytest.fixture
+    def create_bucket1(s3):
+        boto3.client("s3").create_bucket(Bucket="b1")
 
-In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`,
-which sets the proper fake environment variables. The fake environment variables are used so that `botocore` doesn't try to locate real
-credentials on your system.
-
-Next, once you need to do anything with the mocked AWS environment, do something like:
+    @pytest.fixture
+    def create_bucket2(s3):
+        boto3.client("s3").create_bucket(Bucket="b2")
 
-.. sourcecode:: python
-
-    def test_create_bucket(s3):
-        # s3 is a fixture defined above that yields a boto3 s3 client.
-        # Feel free to instantiate another boto3 S3 client -- Keep note of the region though.
+    def test_s3_directly(s3):
         s3.create_bucket(Bucket="somebucket")
 
         result = s3.list_buckets()
         assert len(result["Buckets"]) == 1
-        assert result["Buckets"][0]["Name"] == "somebucket"
+
+    def test_bucket_creation(create_bucket1, create_bucket2):
+        buckets = boto3.client("s3").list_buckets()["Buckets"]
+        assert len(result["Buckets"]) == 2
+
+
+In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`,
+which sets the proper fake environment variables. The fake environment variables are used so that `botocore` doesn't try to locate real
+credentials on your system.
+
+With Moto activated within the fixture, we can pass it to a test-method to ensure that any other AWS-calls are also mocked inside that test method.
+We can also combine multiple fixtures that use the same Moto-fixture.
+
+Moto will delete any data after the mock ends, so the state is not shared across methods.
 
 
 .. _pesky_imports_section:
diff --git a/docs/docs/services/autoscaling.rst b/docs/docs/services/autoscaling.rst
index b6e4fb294..35fd64370 100644
--- a/docs/docs/services/autoscaling.rst
+++ b/docs/docs/services/autoscaling.rst
@@ -64,7 +64,6 @@ autoscaling
 - [X] describe_tags
   
         Pagination is not yet implemented.
-        Only the `auto-scaling-group` and `propagate-at-launch` filters are implemented.
         
 
 - [ ] describe_termination_policy_types
diff --git a/docs/docs/services/cognito-identity.rst b/docs/docs/services/cognito-identity.rst
index af66b4204..f8b596a79 100644
--- a/docs/docs/services/cognito-identity.rst
+++ b/docs/docs/services/cognito-identity.rst
@@ -41,7 +41,11 @@ cognito-identity
         The MaxResults-parameter has not yet been implemented
         
 
-- [ ] list_identity_pools
+- [X] list_identity_pools
+  
+        The MaxResults-parameter has not yet been implemented
+        
+
 - [ ] list_tags_for_resource
 - [ ] lookup_developer_identity
 - [ ] merge_developer_identities
diff --git a/docs/docs/services/ec2.rst b/docs/docs/services/ec2.rst
index 72e1ffbf2..95c9d6a8b 100644
--- a/docs/docs/services/ec2.rst
+++ b/docs/docs/services/ec2.rst
@@ -48,6 +48,7 @@ ec2
 - [ ] associate_enclave_certificate_iam_role
 - [X] associate_iam_instance_profile
 - [ ] associate_instance_event_window
+- [ ] associate_ipam_byoasn
 - [ ] associate_ipam_resource_discovery
 - [ ] associate_nat_gateway_address
 - [X] associate_route_table
@@ -242,6 +243,7 @@ ec2
 - [ ] delete_vpn_connection_route
 - [X] delete_vpn_gateway
 - [ ] deprovision_byoip_cidr
+- [ ] deprovision_ipam_byoasn
 - [ ] deprovision_ipam_pool_cidr
 - [ ] deprovision_public_ipv4_pool_cidr
 - [X] deregister_image
@@ -312,6 +314,7 @@ ec2
 - [X] describe_instance_types
 - [X] describe_instances
 - [X] describe_internet_gateways
+- [ ] describe_ipam_byoasn
 - [ ] describe_ipam_pools
 - [ ] describe_ipam_resource_discoveries
 - [ ] describe_ipam_resource_discovery_associations
@@ -449,6 +452,7 @@ ec2
 - [ ] disassociate_enclave_certificate_iam_role
 - [X] disassociate_iam_instance_profile
 - [ ] disassociate_instance_event_window
+- [ ] disassociate_ipam_byoasn
 - [ ] disassociate_ipam_resource_discovery
 - [ ] disassociate_nat_gateway_address
 - [X] disassociate_route_table
@@ -497,6 +501,7 @@ ec2
 - [ ] get_instance_uefi_data
 - [ ] get_ipam_address_history
 - [ ] get_ipam_discovered_accounts
+- [ ] get_ipam_discovered_public_addresses
 - [ ] get_ipam_discovered_resource_cidrs
 - [ ] get_ipam_pool_allocations
 - [ ] get_ipam_pool_cidrs
@@ -606,6 +611,7 @@ ec2
 - [ ] move_address_to_vpc
 - [ ] move_byoip_cidr_to_ipam
 - [ ] provision_byoip_cidr
+- [ ] provision_ipam_byoasn
 - [ ] provision_ipam_pool_cidr
 - [ ] provision_public_ipv4_pool_cidr
 - [ ] purchase_capacity_block
diff --git a/docs/docs/services/ecr.rst b/docs/docs/services/ecr.rst
index 64cab0146..857f737af 100644
--- a/docs/docs/services/ecr.rst
+++ b/docs/docs/services/ecr.rst
@@ -77,5 +77,7 @@ ecr
 - [ ] start_lifecycle_policy_preview
 - [X] tag_resource
 - [X] untag_resource
+- [ ] update_pull_through_cache_rule
 - [ ] upload_layer_part
+- [ ] validate_pull_through_cache_rule
 
diff --git a/docs/docs/services/glue.rst b/docs/docs/services/glue.rst
index 3ec1b98fc..58fd07358 100644
--- a/docs/docs/services/glue.rst
+++ b/docs/docs/services/glue.rst
@@ -107,6 +107,8 @@ glue
 - [ ] get_classifiers
 - [ ] get_column_statistics_for_partition
 - [ ] get_column_statistics_for_table
+- [ ] get_column_statistics_task_run
+- [ ] get_column_statistics_task_runs
 - [ ] get_connection
 - [ ] get_connections
 - [X] get_crawler
@@ -179,6 +181,7 @@ glue
 - [ ] get_workflow_runs
 - [ ] import_catalog_to_glue
 - [ ] list_blueprints
+- [ ] list_column_statistics_task_runs
 - [X] list_crawlers
 - [ ] list_crawls
 - [ ] list_custom_entity_types
@@ -209,6 +212,7 @@ glue
 - [ ] run_statement
 - [ ] search_tables
 - [ ] start_blueprint_run
+- [ ] start_column_statistics_task_run
 - [X] start_crawler
 - [ ] start_crawler_schedule
 - [ ] start_data_quality_rule_recommendation_run
@@ -220,6 +224,7 @@ glue
 - [ ] start_ml_labeling_set_generation_task_run
 - [X] start_trigger
 - [ ] start_workflow_run
+- [ ] stop_column_statistics_task_run
 - [X] stop_crawler
 - [ ] stop_crawler_schedule
 - [X] stop_session
diff --git a/docs/docs/services/lakeformation.rst b/docs/docs/services/lakeformation.rst
index e328c98c2..fb95571c2 100644
--- a/docs/docs/services/lakeformation.rst
+++ b/docs/docs/services/lakeformation.rst
@@ -64,7 +64,7 @@ lakeformation
 - [X] list_lf_tags
 - [X] list_permissions
   
-        No parameters have been implemented yet
+        No pagination has been implemented yet.
         
 
 - [X] list_resources
diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst
index 1cdc89deb..51e65ce0c 100644
--- a/docs/docs/services/quicksight.rst
+++ b/docs/docs/services/quicksight.rst
@@ -42,6 +42,7 @@ quicksight
 - [X] create_ingestion
 - [ ] create_namespace
 - [ ] create_refresh_schedule
+- [ ] create_role_membership
 - [ ] create_template
 - [ ] create_template_alias
 - [ ] create_theme
@@ -63,6 +64,8 @@ quicksight
 - [ ] delete_iam_policy_assignment
 - [ ] delete_namespace
 - [ ] delete_refresh_schedule
+- [ ] delete_role_custom_permission
+- [ ] delete_role_membership
 - [ ] delete_template
 - [ ] delete_template_alias
 - [ ] delete_theme
@@ -100,6 +103,7 @@ quicksight
 - [ ] describe_ip_restriction
 - [ ] describe_namespace
 - [ ] describe_refresh_schedule
+- [ ] describe_role_custom_permission
 - [ ] describe_template
 - [ ] describe_template_alias
 - [ ] describe_template_definition
@@ -141,6 +145,7 @@ quicksight
 - [ ] list_ingestions
 - [ ] list_namespaces
 - [ ] list_refresh_schedules
+- [ ] list_role_memberships
 - [ ] list_tags_for_resource
 - [ ] list_template_aliases
 - [ ] list_template_versions
@@ -194,6 +199,7 @@ quicksight
 - [ ] update_ip_restriction
 - [ ] update_public_sharing_settings
 - [ ] update_refresh_schedule
+- [ ] update_role_custom_permission
 - [ ] update_template
 - [ ] update_template_alias
 - [ ] update_template_permissions
diff --git a/docs/docs/services/redshift.rst b/docs/docs/services/redshift.rst
index 31bbeeaea..6abb8ead6 100644
--- a/docs/docs/services/redshift.rst
+++ b/docs/docs/services/redshift.rst
@@ -47,6 +47,7 @@ redshift
 - [ ] create_event_subscription
 - [ ] create_hsm_client_certificate
 - [ ] create_hsm_configuration
+- [ ] create_redshift_idc_application
 - [ ] create_scheduled_action
 - [X] create_snapshot_copy_grant
 - [ ] create_snapshot_schedule
@@ -65,6 +66,7 @@ redshift
 - [ ] delete_hsm_client_certificate
 - [ ] delete_hsm_configuration
 - [ ] delete_partner
+- [ ] delete_redshift_idc_application
 - [ ] delete_resource_policy
 - [ ] delete_scheduled_action
 - [X] delete_snapshot_copy_grant
@@ -99,6 +101,7 @@ redshift
 - [ ] describe_node_configuration_options
 - [ ] describe_orderable_cluster_options
 - [ ] describe_partners
+- [ ] describe_redshift_idc_applications
 - [ ] describe_reserved_node_exchange_status
 - [ ] describe_reserved_node_offerings
 - [ ] describe_reserved_nodes
@@ -134,6 +137,7 @@ redshift
 - [ ] modify_custom_domain_association
 - [ ] modify_endpoint_access
 - [ ] modify_event_subscription
+- [ ] modify_redshift_idc_application
 - [ ] modify_scheduled_action
 - [X] modify_snapshot_copy_retention_period
 - [ ] modify_snapshot_schedule
diff --git a/docs/docs/services/sso-admin.rst b/docs/docs/services/sso-admin.rst
index 07eb000e2..6e6003a4c 100644
--- a/docs/docs/services/sso-admin.rst
+++ b/docs/docs/services/sso-admin.rst
@@ -30,20 +30,40 @@ sso-admin
 - [ ] attach_customer_managed_policy_reference_to_permission_set
 - [ ] attach_managed_policy_to_permission_set
 - [X] create_account_assignment
+- [ ] create_application
+- [ ] create_application_assignment
+- [ ] create_instance
 - [ ] create_instance_access_control_attribute_configuration
 - [X] create_permission_set
+- [ ] create_trusted_token_issuer
 - [X] delete_account_assignment
+- [ ] delete_application
+- [ ] delete_application_access_scope
+- [ ] delete_application_assignment
+- [ ] delete_application_authentication_method
+- [ ] delete_application_grant
 - [ ] delete_inline_policy_from_permission_set
+- [ ] delete_instance
 - [ ] delete_instance_access_control_attribute_configuration
 - [X] delete_permission_set
 - [ ] delete_permissions_boundary_from_permission_set
+- [ ] delete_trusted_token_issuer
 - [ ] describe_account_assignment_creation_status
 - [ ] describe_account_assignment_deletion_status
+- [ ] describe_application
+- [ ] describe_application_assignment
+- [ ] describe_application_provider
+- [ ] describe_instance
 - [ ] describe_instance_access_control_attribute_configuration
 - [X] describe_permission_set
 - [ ] describe_permission_set_provisioning_status
+- [ ] describe_trusted_token_issuer
 - [ ] detach_customer_managed_policy_reference_from_permission_set
 - [ ] detach_managed_policy_from_permission_set
+- [ ] get_application_access_scope
+- [ ] get_application_assignment_configuration
+- [ ] get_application_authentication_method
+- [ ] get_application_grant
 - [ ] get_inline_policy_for_permission_set
 - [ ] get_permissions_boundary_for_permission_set
 - [ ] list_account_assignment_creation_status
@@ -53,7 +73,15 @@ sso-admin
         Pagination has not yet been implemented
         
 
+- [ ] list_account_assignments_for_principal
 - [ ] list_accounts_for_provisioned_permission_set
+- [ ] list_application_access_scopes
+- [ ] list_application_assignments
+- [ ] list_application_assignments_for_principal
+- [ ] list_application_authentication_methods
+- [ ] list_application_grants
+- [ ] list_application_providers
+- [ ] list_applications
 - [ ] list_customer_managed_policy_references_in_permission_set
 - [ ] list_instances
 - [ ] list_managed_policies_in_permission_set
@@ -61,11 +89,19 @@ sso-admin
 - [X] list_permission_sets
 - [ ] list_permission_sets_provisioned_to_account
 - [ ] list_tags_for_resource
+- [ ] list_trusted_token_issuers
 - [ ] provision_permission_set
+- [ ] put_application_access_scope
+- [ ] put_application_assignment_configuration
+- [ ] put_application_authentication_method
+- [ ] put_application_grant
 - [ ] put_inline_policy_to_permission_set
 - [ ] put_permissions_boundary_to_permission_set
 - [ ] tag_resource
 - [ ] untag_resource
+- [ ] update_application
+- [ ] update_instance
 - [ ] update_instance_access_control_attribute_configuration
 - [X] update_permission_set
+- [ ] update_trusted_token_issuer
 
diff --git a/moto/__init__.py b/moto/__init__.py
index 32b721550..a234c1448 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -249,7 +249,7 @@ mock_all = MockAll
 # logging.getLogger('boto').setLevel(logging.CRITICAL)
 
 __title__ = "moto"
-__version__ = "4.2.10.dev"
+__version__ = "4.2.11.dev"
 
 
 try:
diff --git a/moto/autoscaling/models.py b/moto/autoscaling/models.py
index 19b0739e0..c57989431 100644
--- a/moto/autoscaling/models.py
+++ b/moto/autoscaling/models.py
@@ -472,10 +472,10 @@ class FakeAutoScalingGroup(CloudFormationModel):
     @tags.setter
     def tags(self, tags: List[Dict[str, str]]) -> None:
         for tag in tags:
-            if "resource_id" not in tag or not tag["resource_id"]:
-                tag["resource_id"] = self.name
-            if "resource_type" not in tag or not tag["resource_type"]:
-                tag["resource_type"] = "auto-scaling-group"
+            if "ResourceId" not in tag or not tag["ResourceId"]:
+                tag["ResourceId"] = self.name
+            if "ResourceType" not in tag or not tag["ResourceType"]:
+                tag["ResourceType"] = "auto-scaling-group"
         self._tags = tags
 
     @property
@@ -1376,31 +1376,31 @@ class AutoScalingBackend(BaseBackend):
 
     def create_or_update_tags(self, tags: List[Dict[str, str]]) -> None:
         for tag in tags:
-            group_name = tag["resource_id"]
+            group_name = tag["ResourceId"]
             group = self.autoscaling_groups[group_name]
             old_tags = group.tags
 
             new_tags = []
             # if key was in old_tags, update old tag
             for old_tag in old_tags:
-                if old_tag["key"] == tag["key"]:
+                if old_tag["Key"] == tag["Key"]:
                     new_tags.append(tag)
                 else:
                     new_tags.append(old_tag)
 
             # if key was never in old_tag's add it (create tag)
-            if not any(new_tag["key"] == tag["key"] for new_tag in new_tags):
+            if not any(new_tag["Key"] == tag["Key"] for new_tag in new_tags):
                 new_tags.append(tag)
 
             group.tags = new_tags
 
     def delete_tags(self, tags: List[Dict[str, str]]) -> None:
         for tag_to_delete in tags:
-            group_name = tag_to_delete["resource_id"]
-            key_to_delete = tag_to_delete["key"]
+            group_name = tag_to_delete["ResourceId"]
+            key_to_delete = tag_to_delete["Key"]
             group = self.autoscaling_groups[group_name]
             old_tags = group.tags
-            group.tags = [x for x in old_tags if x["key"] != key_to_delete]
+            group.tags = [x for x in old_tags if x["Key"] != key_to_delete]
 
     def attach_load_balancers(
         self, group_name: str, load_balancer_names: List[str]
@@ -1558,20 +1558,21 @@ class AutoScalingBackend(BaseBackend):
     def describe_tags(self, filters: List[Dict[str, str]]) -> List[Dict[str, str]]:
         """
         Pagination is not yet implemented.
-        Only the `auto-scaling-group` and `propagate-at-launch` filters are implemented.
         """
         resources = self.autoscaling_groups.values()
         tags = list(itertools.chain(*[r.tags for r in resources]))
         for f in filters:
             if f["Name"] == "auto-scaling-group":
-                tags = [t for t in tags if t["resource_id"] in f["Values"]]
+                tags = [t for t in tags if t["ResourceId"] in f["Values"]]
             if f["Name"] == "propagate-at-launch":
                 values = [v.lower() for v in f["Values"]]
                 tags = [
-                    t
-                    for t in tags
-                    if t.get("propagate_at_launch", "").lower() in values
+                    t for t in tags if t.get("PropagateAtLaunch", "").lower() in values
                 ]
+            if f["Name"] == "key":
+                tags = [t for t in tags if t["Key"] in f["Values"]]
+            if f["Name"] == "value":
+                tags = [t for t in tags if t["Value"] in f["Values"]]
         return tags
 
     def enable_metrics_collection(self, group_name: str, metrics: List[str]) -> None:
diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py
index 563bfe578..3240349f1 100644
--- a/moto/autoscaling/responses.py
+++ b/moto/autoscaling/responses.py
@@ -93,7 +93,7 @@ class AutoScalingResponse(BaseResponse):
             target_group_arns=self._get_multi_param("TargetGroupARNs.member"),
             placement_group=self._get_param("PlacementGroup"),
             termination_policies=self._get_multi_param("TerminationPolicies.member"),
-            tags=self._get_list_prefix("Tags.member"),
+            tags=params.get("Tags", []),
             capacity_rebalance=self._get_bool_param("CapacityRebalance", False),
             new_instances_protected_from_scale_in=self._get_bool_param(
                 "NewInstancesProtectedFromScaleIn", False
@@ -260,16 +260,14 @@ class AutoScalingResponse(BaseResponse):
         return template.render()
 
     def create_or_update_tags(self) -> str:
-        tags = self._get_list_prefix("Tags.member")
-
-        self.autoscaling_backend.create_or_update_tags(tags)
+        self.autoscaling_backend.create_or_update_tags(
+            self._get_params().get("Tags", [])
+        )
         template = self.response_template(UPDATE_AUTOSCALING_GROUP_TEMPLATE)
         return template.render()
 
     def delete_tags(self) -> str:
-        tags = self._get_list_prefix("Tags.member")
-
-        self.autoscaling_backend.delete_tags(tags)
+        self.autoscaling_backend.delete_tags(self._get_params().get("Tags", []))
         template = self.response_template(UPDATE_AUTOSCALING_GROUP_TEMPLATE)
         return template.render()
 
@@ -1427,11 +1425,11 @@ DESCRIBE_TAGS_TEMPLATE = """<DescribeTagsResponse xmlns="http://autoscaling.amaz
     <Tags>
 {% for tag in tags %}
       <member>
-        <ResourceId>{{ tag.resource_id }}</ResourceId>
-        <ResourceType>{{ tag.resource_type }}</ResourceType>
-        <Key>{{ tag.key }}</Key>
-        <Value>{{ tag.value }}</Value>
-        <PropagateAtLaunch>{{ tag.propagate_at_launch }}</PropagateAtLaunch>
+        <ResourceId>{{ tag.resource_id or tag.ResourceId }}</ResourceId>
+        <ResourceType>{{ tag.resource_type or tag.ResourceType }}</ResourceType>
+        <Key>{{ tag.key or tag.Key }}</Key>
+        <Value>{{ tag.value or tag.Value }}</Value>
+        <PropagateAtLaunch>{{ tag.propagate_at_launch or tag.PropagateAtLaunch }}</PropagateAtLaunch>
       </member>
 {% endfor %}
     </Tags>
diff --git a/moto/cognitoidentity/models.py b/moto/cognitoidentity/models.py
index d326b7a4a..2be58aa7f 100644
--- a/moto/cognitoidentity/models.py
+++ b/moto/cognitoidentity/models.py
@@ -5,7 +5,7 @@ import re
 from collections import OrderedDict
 from typing import Any, Dict, List, Optional
 from moto.core import BaseBackend, BackendDict, BaseModel
-from moto.core.utils import iso_8601_datetime_with_milliseconds, utcnow
+from moto.core.utils import utcnow
 from .exceptions import InvalidNameException, ResourceNotFoundError
 from .utils import get_random_identity_id
 
@@ -139,12 +139,11 @@ class CognitoIdentityBackend(BaseBackend):
         duration = 90
         now = utcnow()
         expiration = now + datetime.timedelta(seconds=duration)
-        expiration_str = str(iso_8601_datetime_with_milliseconds(expiration))
         return json.dumps(
             {
                 "Credentials": {
                     "AccessKeyId": "TESTACCESSKEY12345",
-                    "Expiration": expiration_str,
+                    "Expiration": expiration.timestamp(),
                     "SecretKey": "ABCSECRETKEY",
                     "SessionToken": "ABC12345",
                 },
@@ -174,5 +173,17 @@ class CognitoIdentityBackend(BaseBackend):
         """
         return json.dumps(self.pools_identities[identity_pool_id])
 
+    def list_identity_pools(self) -> str:
+        """
+        The MaxResults-parameter has not yet been implemented
+        """
+        return json.dumps(
+            {
+                "IdentityPools": [
+                    json.loads(pool.to_json()) for pool in self.identity_pools.values()
+                ]
+            }
+        )
+
 
 cognitoidentity_backends = BackendDict(CognitoIdentityBackend, "cognito-identity")
diff --git a/moto/cognitoidentity/responses.py b/moto/cognitoidentity/responses.py
index 34499ed02..ba1d157bc 100644
--- a/moto/cognitoidentity/responses.py
+++ b/moto/cognitoidentity/responses.py
@@ -80,3 +80,6 @@ class CognitoIdentityResponse(BaseResponse):
         return self.backend.list_identities(
             self._get_param("IdentityPoolId") or get_random_identity_id(self.region)
         )
+
+    def list_identity_pools(self) -> str:
+        return self.backend.list_identity_pools()
diff --git a/moto/dynamodb/exceptions.py b/moto/dynamodb/exceptions.py
index 57823cfee..029e8eb32 100644
--- a/moto/dynamodb/exceptions.py
+++ b/moto/dynamodb/exceptions.py
@@ -19,6 +19,13 @@ class MockValidationException(DynamodbException):
         self.exception_msg = message
 
 
+class KeyIsEmptyStringException(MockValidationException):
+    def __init__(self, empty_key: str):
+        super().__init__(
+            message=f"One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: {empty_key}"
+        )
+
+
 class InvalidIndexNameError(MockValidationException):
     pass
 
diff --git a/moto/dynamodb/parsing/key_condition_expression.py b/moto/dynamodb/parsing/key_condition_expression.py
index 7638f0e24..2885f7652 100644
--- a/moto/dynamodb/parsing/key_condition_expression.py
+++ b/moto/dynamodb/parsing/key_condition_expression.py
@@ -1,6 +1,6 @@
 from enum import Enum
 from typing import Any, List, Dict, Tuple, Optional, Union
-from moto.dynamodb.exceptions import MockValidationException
+from moto.dynamodb.exceptions import MockValidationException, KeyIsEmptyStringException
 from moto.utilities.tokenizer import GenericTokenizer
 
 
@@ -209,6 +209,8 @@ def validate_schema(
         )
     if comparison != "=":
         raise MockValidationException("Query key condition not supported")
+    if "S" in hash_value and hash_value["S"] == "":
+        raise KeyIsEmptyStringException(index_hash_key)  # type: ignore[arg-type]
 
     index_range_key = get_key(schema, "RANGE")
     range_key, range_comparison, range_values = next(
@@ -219,9 +221,12 @@ def validate_schema(
         ),
         (None, None, []),
     )
-    if index_range_key and len(results) > 1 and range_key != index_range_key:
-        raise MockValidationException(
-            f"Query condition missed key schema element: {index_range_key}"
-        )
+    if index_range_key:
+        if len(results) > 1 and range_key != index_range_key:
+            raise MockValidationException(
+                f"Query condition missed key schema element: {index_range_key}"
+            )
+        if {"S": ""} in range_values:
+            raise KeyIsEmptyStringException(index_range_key)
 
     return hash_value, range_comparison, range_values  # type: ignore[return-value]
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index a751aad8c..1cf70b350 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -14,6 +14,7 @@ from .exceptions import (
     MockValidationException,
     ResourceNotFoundException,
     UnknownKeyType,
+    KeyIsEmptyStringException,
 )
 from moto.dynamodb.models import dynamodb_backends, Table, DynamoDBBackend
 from moto.dynamodb.models.utilities import dynamo_json_dump
@@ -554,10 +555,7 @@ class DynamoHandler(BaseResponse):
         key = self.body["Key"]
         empty_keys = [k for k, v in key.items() if not next(iter(v.values()))]
         if empty_keys:
-            raise MockValidationException(
-                "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an "
-                f"empty string value. Key: {empty_keys[0]}"
-            )
+            raise KeyIsEmptyStringException(empty_keys[0])
 
         projection_expression = self._get_projection_expression()
         attributes_to_get = self.body.get("AttributesToGet")
diff --git a/moto/logs/models.py b/moto/logs/models.py
index 878040185..b2c0bef93 100644
--- a/moto/logs/models.py
+++ b/moto/logs/models.py
@@ -1,4 +1,5 @@
-from datetime import timedelta
+import json
+from datetime import timedelta, datetime
 from typing import Any, Dict, Iterable, List, Tuple, Optional
 from moto.core import BaseBackend, BackendDict, BaseModel
 from moto.core import CloudFormationModel
@@ -719,6 +720,39 @@ class LogResourcePolicy(CloudFormationModel):
         logs_backends[account_id][region_name].delete_resource_policy(resource_name)
 
 
+class ExportTask(BaseModel):
+    def __init__(
+        self,
+        task_id: str,
+        task_name: str,
+        log_group_name: str,
+        destination: str,
+        destination_prefix: str,
+        from_time: int,
+        to: int,
+    ):
+        self.task_id = task_id
+        self.task_name = task_name
+        self.log_group_name = log_group_name
+        self.destination = destination
+        self.destination_prefix = destination_prefix
+        self.from_time = from_time
+        self.to = to
+        self.status = {"code": "active", "message": "Task is active"}
+
+    def to_json(self) -> Dict[str, Any]:
+        return {
+            "taskId": self.task_id,
+            "taskName": self.task_name,
+            "logGroupName": self.log_group_name,
+            "destination": self.destination,
+            "destinationPrefix": self.destination_prefix,
+            "from": self.from_time,
+            "to": self.to,
+            "status": self.status,
+        }
+
+
 class LogsBackend(BaseBackend):
     def __init__(self, region_name: str, account_id: str):
         super().__init__(region_name, account_id)
@@ -728,6 +762,7 @@ class LogsBackend(BaseBackend):
         self.resource_policies: Dict[str, LogResourcePolicy] = dict()
         self.destinations: Dict[str, Destination] = dict()
         self.tagger = TaggingService()
+        self.export_tasks: Dict[str, ExportTask] = dict()
 
     @staticmethod
     def default_vpc_endpoint_service(
@@ -1204,12 +1239,45 @@ class LogsBackend(BaseBackend):
         return self.queries[query_id]
 
     def create_export_task(
-        self, log_group_name: str, destination: Dict[str, Any]
+        self,
+        taskName: str,
+        logGroupName: str,
+        destination: str,
+        destinationPrefix: str,
+        fromTime: int,
+        to: int,
     ) -> str:
         s3_backends[self.account_id]["global"].get_bucket(destination)
-        if log_group_name not in self.groups:
+        if logGroupName not in self.groups:
             raise ResourceNotFoundException()
-        return str(mock_random.uuid4())
+        task_id = str(mock_random.uuid4())
+        self.export_tasks[task_id] = ExportTask(
+            task_id,
+            taskName,
+            logGroupName,
+            destination,
+            destinationPrefix,
+            fromTime,
+            to,
+        )
+        if fromTime <= datetime.now().timestamp() <= to:
+            logs = self.filter_log_events(
+                logGroupName, [], fromTime, to, None, None, "", False
+            )
+            s3_backends[self.account_id]["global"].put_object(
+                destination, destinationPrefix, json.dumps(logs)
+            )
+            self.export_tasks[task_id].status["code"] = "completed"
+            self.export_tasks[task_id].status["message"] = "Task is completed"
+        return task_id
+
+    def describe_export_tasks(self, taskId: str = "") -> Tuple[List[ExportTask], str]:
+        if taskId:
+            if taskId not in self.export_tasks:
+                raise ResourceNotFoundException()
+            return [self.export_tasks[taskId]], ""
+        else:
+            return list(self.export_tasks.values()), ""
 
     def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
         return self.tagger.get_tag_dict_for_resource(resource_arn)
diff --git a/moto/logs/responses.py b/moto/logs/responses.py
index ae7d6d434..3c2f7094b 100644
--- a/moto/logs/responses.py
+++ b/moto/logs/responses.py
@@ -433,13 +433,24 @@ class LogsResponse(BaseResponse):
         return json.dumps(query.to_result_json())
 
     def create_export_task(self) -> str:
-        log_group_name = self._get_param("logGroupName")
-        destination = self._get_param("destination")
         task_id = self.logs_backend.create_export_task(
-            log_group_name=log_group_name, destination=destination
+            logGroupName=self._get_param("logGroupName"),
+            fromTime=self._get_int_param("from"),
+            to=self._get_int_param("to"),
+            destination=self._get_param("destination"),
+            destinationPrefix=self._get_param("destinationPrefix"),
+            taskName=self._get_param("taskName"),
         )
         return json.dumps(dict(taskId=str(task_id)))
 
+    def describe_export_tasks(self) -> str:
+        task_id = self._get_param("taskId")
+
+        tasks, next_token = self.logs_backend.describe_export_tasks(taskId=task_id)
+        return json.dumps(
+            {"exportTasks": [task.to_json() for task in tasks], "nextToken": next_token}
+        )
+
     def list_tags_for_resource(self) -> str:
         resource_arn = self._get_param("resourceArn")
         tags = self.logs_backend.list_tags_for_resource(resource_arn)
diff --git a/moto/py.typed b/moto/py.typed
new file mode 100644
index 000000000..e69de29bb
diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py
index 9c5207a12..2dc827f3a 100644
--- a/moto/sqs/responses.py
+++ b/moto/sqs/responses.py
@@ -289,7 +289,13 @@ class SQSResponse(BaseResponse):
         queue_name = self._get_queue_name()
         self.sqs_backend.set_queue_attributes(queue_name, attribute)
 
-        return SET_QUEUE_ATTRIBUTE_RESPONSE
+        return self._empty_response(SET_QUEUE_ATTRIBUTE_RESPONSE)
+
+    def _empty_response(self, xml_response: str) -> str:
+        # 'Empty' XML response always needs some fields
+        if self.is_json():
+            return "{}"
+        return xml_response
 
     @jsonify_error
     def delete_queue(self) -> str:
@@ -298,8 +304,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.delete_queue(queue_name)
 
-        template = self.response_template(DELETE_QUEUE_RESPONSE)
-        return template.render()
+        return self._empty_response(DELETE_QUEUE_RESPONSE)
 
     @jsonify_error
     def send_message(self) -> Union[str, TYPE_RESPONSE]:
@@ -506,8 +511,7 @@ class SQSResponse(BaseResponse):
     def purge_queue(self) -> str:
         queue_name = self._get_queue_name()
         self.sqs_backend.purge_queue(queue_name)
-        template = self.response_template(PURGE_QUEUE_RESPONSE)
-        return template.render()
+        return self._empty_response(PURGE_QUEUE_RESPONSE)
 
     @jsonify_error
     def receive_message(self) -> Union[str, TYPE_RESPONSE]:
@@ -684,8 +688,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.add_permission(queue_name, actions, account_ids, label)
 
-        template = self.response_template(ADD_PERMISSION_RESPONSE)
-        return template.render()
+        return self._empty_response(ADD_PERMISSION_RESPONSE)
 
     @jsonify_error
     def remove_permission(self) -> str:
@@ -694,8 +697,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.remove_permission(queue_name, label)
 
-        template = self.response_template(REMOVE_PERMISSION_RESPONSE)
-        return template.render()
+        return self._empty_response(REMOVE_PERMISSION_RESPONSE)
 
     @jsonify_error
     def tag_queue(self) -> str:
diff --git a/setup.cfg b/setup.cfg
index 7d837a4aa..ec63fa0d6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -41,6 +41,9 @@ package_dir =
     moto = moto
 include_package_data = True
 
+[options.package_data]
+moto = py.typed
+
 [options.extras_require]
 all =
     python-jose[cryptography]>=3.1.0,<4.0.0


Test patch:
diff --git a/.github/workflows/tests_sdk_dotnet.yml b/.github/workflows/tests_sdk_dotnet.yml
index 4bf92e76f..cf84b9f7e 100644
--- a/.github/workflows/tests_sdk_dotnet.yml
+++ b/.github/workflows/tests_sdk_dotnet.yml
@@ -17,7 +17,7 @@ jobs:
       run: |
         pip install build
         python -m build
-        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh &
+        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
         python scripts/ci_wait_for_server.py
     - uses: actions/setup-dotnet@v3
     - uses: actions/cache@v3
diff --git a/.github/workflows/tests_sdk_java.yml b/.github/workflows/tests_sdk_java.yml
index 298c40fd6..6e8199648 100644
--- a/.github/workflows/tests_sdk_java.yml
+++ b/.github/workflows/tests_sdk_java.yml
@@ -17,7 +17,7 @@ jobs:
       run: |
         pip install build
         python -m build
-        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh &
+        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
         python scripts/ci_wait_for_server.py
     - uses: actions/setup-java@v3
       with:
diff --git a/.github/workflows/tests_sdk_ruby.yml b/.github/workflows/tests_sdk_ruby.yml
new file mode 100644
index 000000000..14e4cbbca
--- /dev/null
+++ b/.github/workflows/tests_sdk_ruby.yml
@@ -0,0 +1,36 @@
+name: Ruby SDK test
+on: [workflow_call]
+
+jobs:
+  test:
+
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        ruby-version: ['3.2']
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Ruby ${{ matrix.ruby-version }}
+        uses: ruby/setup-ruby@ec02537da5712d66d4d50a0f33b7eb52773b5ed1
+        with:
+          ruby-version: ${{ matrix.ruby-version }}
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.8"
+      - name: Start MotoServer
+        run: |
+          pip install build
+          python -m build
+          docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
+          python scripts/ci_wait_for_server.py
+      - name: Install dependencies
+        run: cd other_langs/tests_ruby && bundle install
+      - name: Run tests
+        env:
+          AWS_ENDPOINT_URL: "http://localhost:5000"
+        run: |
+          mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
+          cd other_langs/tests_ruby && ruby test/*
diff --git a/other_langs/tests_ruby/Gemfile b/other_langs/tests_ruby/Gemfile
new file mode 100644
index 000000000..e26f68af1
--- /dev/null
+++ b/other_langs/tests_ruby/Gemfile
@@ -0,0 +1,4 @@
+source "https://rubygems.org"
+
+gem 'aws-sdk-sqs', '1.65.0'
+gem "minitest"
\ No newline at end of file
diff --git a/other_langs/tests_ruby/test/aws_sqs_test.rb b/other_langs/tests_ruby/test/aws_sqs_test.rb
new file mode 100644
index 000000000..7bae8b26f
--- /dev/null
+++ b/other_langs/tests_ruby/test/aws_sqs_test.rb
@@ -0,0 +1,40 @@
+require 'aws-sdk-sqs'
+require 'minitest/autorun'
+
+class AwsSqsTest < Minitest::Test
+  def test_all_sqs_actions
+    region = 'us-east-1'
+
+    sqs_client = Aws::SQS::Client.new(region: region)
+
+    sqs_client.list_queues
+
+    sqs_client.create_queue(queue_name: "q1")
+
+    sqs_client.list_queues
+
+    queue_url = sqs_client.get_queue_url(queue_name: "q1").queue_url
+
+    sqs_client.add_permission({queue_url: queue_url, label: "String", aws_account_ids: ["String"], actions: ["GetQueueUrl"]})
+
+    sqs_client.send_message(
+      queue_url: queue_url,
+      message_body: "message_body"
+    )
+
+    sqs_client.remove_permission({queue_url: queue_url, label: "String"})
+
+    sqs_client.set_queue_attributes({queue_url: queue_url, attributes: {"All" => "DelaySeconds"}})
+
+    sqs_client.receive_message(
+      queue_url: queue_url,
+      max_number_of_messages: 10,
+      attribute_names: ['All'],
+      message_attribute_names: ['All']
+    ).messages
+
+    sqs_client.purge_queue({queue_url: queue_url})
+
+    sqs_client.delete_queue(queue_url: queue_url)
+  end
+end
\ No newline at end of file
diff --git a/tests/test_autoscaling/test_autoscaling_cloudformation.py b/tests/test_autoscaling/test_autoscaling_cloudformation.py
index 5eb71bdfe..feb39af9e 100644
--- a/tests/test_autoscaling/test_autoscaling_cloudformation.py
+++ b/tests/test_autoscaling/test_autoscaling_cloudformation.py
@@ -355,8 +355,29 @@ def test_autoscaling_group_with_elb():
     cf.create_stack(StackName="web_stack", TemplateBody=web_setup_template_json)
 
     autoscale_group = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
+    asg_name = autoscale_group["AutoScalingGroupName"]
     assert "my-launch-config" in autoscale_group["LaunchConfigurationName"]
     assert autoscale_group["LoadBalancerNames"] == ["my-elb"]
+    assert len(autoscale_group["Tags"]) == 2
+    assert {
+        "ResourceId": asg_name,
+        "ResourceType": "auto-scaling-group",
+        "Key": "propagated-test-tag",
+        "Value": "propagated-test-tag-value",
+        "PropagateAtLaunch": True,
+    } in autoscale_group["Tags"]
+    assert {
+        "ResourceId": asg_name,
+        "ResourceType": "auto-scaling-group",
+        "Key": "not-propagated-test-tag",
+        "Value": "not-propagated-test-tag-value",
+        "PropagateAtLaunch": False,
+    } in autoscale_group["Tags"]
+
+    tags = client.describe_tags(
+        Filters=[{"Name": "auto-scaling-group", "Values": [asg_name]}]
+    )["Tags"]
+    assert tags == autoscale_group["Tags"]
 
     # Confirm the Launch config was actually created
     assert len(client.describe_launch_configurations()["LaunchConfigurations"]) == 1
diff --git a/tests/test_autoscaling/test_autoscaling_tags.py b/tests/test_autoscaling/test_autoscaling_tags.py
index 13e82dc89..cce02bccc 100644
--- a/tests/test_autoscaling/test_autoscaling_tags.py
+++ b/tests/test_autoscaling/test_autoscaling_tags.py
@@ -233,6 +233,39 @@ def test_describe_tags_filter_by_propgateatlaunch():
     ]
 
 
+@mock_autoscaling
+def test_describe_tags_filter_by_key_or_value():
+    subnet = setup_networking()["subnet1"]
+    client = boto3.client("autoscaling", region_name="us-east-1")
+    create_asgs(client, subnet)
+
+    tags = client.describe_tags(Filters=[{"Name": "key", "Values": ["test_key"]}])[
+        "Tags"
+    ]
+    assert tags == [
+        {
+            "ResourceId": "test_asg",
+            "ResourceType": "auto-scaling-group",
+            "Key": "test_key",
+            "Value": "updated_test_value",
+            "PropagateAtLaunch": True,
+        }
+    ]
+
+    tags = client.describe_tags(Filters=[{"Name": "value", "Values": ["test_value2"]}])[
+        "Tags"
+    ]
+    assert tags == [
+        {
+            "ResourceId": "test_asg",
+            "ResourceType": "auto-scaling-group",
+            "Key": "test_key2",
+            "Value": "test_value2",
+            "PropagateAtLaunch": False,
+        }
+    ]
+
+
 @mock_autoscaling
 def test_create_20_tags_auto_scaling_group():
     """test to verify that the tag-members are sorted correctly, and there is no regression for
diff --git a/tests/test_cognitoidentity/test_cognitoidentity.py b/tests/test_cognitoidentity/test_cognitoidentity.py
index 197cc4647..742c27a81 100644
--- a/tests/test_cognitoidentity/test_cognitoidentity.py
+++ b/tests/test_cognitoidentity/test_cognitoidentity.py
@@ -240,3 +240,15 @@ def test_list_identities():
     identities = conn.list_identities(IdentityPoolId=identity_pool_id, MaxResults=123)
     assert "IdentityPoolId" in identities and "Identities" in identities
     assert identity_id in [x["IdentityId"] for x in identities["Identities"]]
+
+
+@mock_cognitoidentity
+def test_list_identity_pools():
+    conn = boto3.client("cognito-identity", "us-west-2")
+    identity_pool_data = conn.create_identity_pool(
+        IdentityPoolName="test_identity_pool", AllowUnauthenticatedIdentities=True
+    )
+    identity_pool_id = identity_pool_data["IdentityPoolId"]
+    identity_data = conn.list_identity_pools(MaxResults=10)
+    assert identity_pool_id == identity_data["IdentityPools"][0]["IdentityPoolId"]
+    assert "test_identity_pool" == identity_data["IdentityPools"][0]["IdentityPoolName"]
diff --git a/tests/test_dynamodb/exceptions/test_key_length_exceptions.py b/tests/test_dynamodb/exceptions/test_key_length_exceptions.py
index f3749f846..90c200855 100644
--- a/tests/test_dynamodb/exceptions/test_key_length_exceptions.py
+++ b/tests/test_dynamodb/exceptions/test_key_length_exceptions.py
@@ -3,6 +3,7 @@ import pytest
 
 from moto import mock_dynamodb
 from botocore.exceptions import ClientError
+from boto3.dynamodb.conditions import Key
 from moto.dynamodb.limits import HASH_KEY_MAX_LENGTH, RANGE_KEY_MAX_LENGTH
 
 
@@ -323,3 +324,40 @@ def test_item_add_empty_key_exception():
         ex.value.response["Error"]["Message"]
         == "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: forum_name"
     )
+
+
+@mock_dynamodb
+def test_query_empty_key_exception():
+    name = "TestTable"
+    conn = boto3.client("dynamodb", region_name="us-west-2")
+    conn.create_table(
+        TableName=name,
+        KeySchema=[
+            {"AttributeName": "hk", "KeyType": "HASH"},
+            {"AttributeName": "rk", "KeyType": "RANGE"},
+        ],
+        AttributeDefinitions=[
+            {"AttributeName": "hk", "AttributeType": "S"},
+            {"AttributeName": "rk", "AttributeType": "S"},
+        ],
+        ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
+    )
+    table = boto3.resource("dynamodb", "us-west-2").Table(name)
+
+    with pytest.raises(ClientError) as ex:
+        table.query(KeyConditionExpression=Key("hk").eq(""))
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+    assert (
+        ex.value.response["Error"]["Message"]
+        == "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: hk"
+    )
+
+    with pytest.raises(ClientError) as ex:
+        table.query(KeyConditionExpression=Key("hk").eq("sth") & Key("rk").eq(""))
+    assert ex.value.response["Error"]["Code"] == "ValidationException"
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
+    assert (
+        ex.value.response["Error"]["Message"]
+        == "One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: rk"
+    )
diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py
index b89d5f051..fedc8bbfe 100644
--- a/tests/test_logs/test_logs.py
+++ b/tests/test_logs/test_logs.py
@@ -1,5 +1,5 @@
 import json
-from datetime import timedelta
+from datetime import timedelta, datetime
 from uuid import UUID
 
 import boto3
@@ -1384,7 +1384,10 @@ def test_create_export_task_happy_path():
     logs.create_log_group(logGroupName=log_group_name)
     s3.create_bucket(Bucket=destination)
     resp = logs.create_export_task(
-        logGroupName=log_group_name, fromTime=fromTime, to=to, destination=destination
+        logGroupName=log_group_name,
+        fromTime=fromTime,
+        to=to,
+        destination=destination,
     )
     # taskId resembles a valid UUID (i.e. a string of 32 hexadecimal digits)
     assert UUID(resp["taskId"])
@@ -1425,3 +1428,94 @@ def test_create_export_raises_ResourceNotFoundException_log_group_not_found():
             to=to,
             destination=destination,
         )
+
+
+@mock_s3
+@mock_logs
+def test_create_export_executes_export_task():
+    log_group_name = "/aws/codebuild/blah1"
+    destination = "mybucket"
+    fromTime = int(datetime.now().replace(hour=0, minute=0, second=0).timestamp())
+    to = int(datetime.now().replace(hour=23, minute=59, second=59).timestamp())
+    logs = boto3.client("logs", region_name="ap-southeast-1")
+    s3 = boto3.client("s3", "us-east-1")
+    logs.create_log_group(logGroupName=log_group_name)
+    s3.create_bucket(Bucket=destination)
+    resp = logs.create_export_task(
+        logGroupName=log_group_name,
+        fromTime=fromTime,
+        to=to,
+        destination=destination,
+    )
+    taskId = resp["taskId"]
+    resp = logs.describe_export_tasks(taskId=taskId)
+    assert len(resp["exportTasks"]) == 1
+    assert resp["exportTasks"][0]["logGroupName"] == log_group_name
+    assert resp["exportTasks"][0]["destination"] == destination
+    assert resp["exportTasks"][0]["from"] == fromTime
+    assert resp["exportTasks"][0]["to"] == to
+    assert resp["exportTasks"][0]["status"]["code"] == "completed"
+    assert resp["exportTasks"][0]["status"]["message"] == "Task is completed"
+
+
+@mock_s3
+@mock_logs
+def test_describe_export_tasks_happy_path():
+    log_group_name = "/aws/codebuild/blah1"
+    destination = "mybucket"
+    fromTime = 1611316574
+    to = 1642852574
+    logs = boto3.client("logs", region_name="ap-southeast-1")
+    s3 = boto3.client("s3", "us-east-1")
+    logs.create_log_group(logGroupName=log_group_name)
+    s3.create_bucket(Bucket=destination)
+    logs.create_export_task(
+        logGroupName=log_group_name,
+        fromTime=fromTime,
+        to=to,
+        destination=destination,
+    )
+    resp = logs.describe_export_tasks()
+    assert len(resp["exportTasks"]) == 1
+    assert resp["exportTasks"][0]["logGroupName"] == log_group_name
+    assert resp["exportTasks"][0]["destination"] == destination
+    assert resp["exportTasks"][0]["from"] == fromTime
+    assert resp["exportTasks"][0]["to"] == to
+    assert resp["exportTasks"][0]["status"]["code"] == "active"
+    assert resp["exportTasks"][0]["status"]["message"] == "Task is active"
+
+
+@mock_s3
+@mock_logs
+def test_describe_export_tasks_task_id():
+    log_group_name = "/aws/codebuild/blah1"
+    destination = "mybucket"
+    fromTime = 1611316574
+    to = 1642852574
+    logs = boto3.client("logs", region_name="ap-southeast-1")
+    s3 = boto3.client("s3", "us-east-1")
+    logs.create_log_group(logGroupName=log_group_name)
+    s3.create_bucket(Bucket=destination)
+    resp = logs.create_export_task(
+        logGroupName=log_group_name,
+        fromTime=fromTime,
+        to=to,
+        destination=destination,
+    )
+    taskId = resp["taskId"]
+    resp = logs.describe_export_tasks(taskId=taskId)
+    assert len(resp["exportTasks"]) == 1
+    assert resp["exportTasks"][0]["logGroupName"] == log_group_name
+    assert resp["exportTasks"][0]["destination"] == destination
+    assert resp["exportTasks"][0]["from"] == fromTime
+    assert resp["exportTasks"][0]["to"] == to
+    assert resp["exportTasks"][0]["status"]["code"] == "active"
+    assert resp["exportTasks"][0]["status"]["message"] == "Task is active"
+
+
+@mock_s3
+@mock_logs
+def test_describe_export_tasks_raises_ResourceNotFoundException_task_id_not_found():
+    logs = boto3.client("logs", region_name="ap-southeast-1")
+    with pytest.raises(logs.exceptions.ResourceNotFoundException):
+        logs.describe_export_tasks(taskId="368a7022dea3dd621")