localstack-core 4.11.2.dev14__py3-none-any.whl → 4.12.1.dev18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. localstack/aws/api/ec2/__init__.py +13 -0
  2. localstack/aws/api/iam/__init__.py +1 -0
  3. localstack/aws/api/lambda_/__init__.py +616 -0
  4. localstack/aws/api/logs/__init__.py +188 -0
  5. localstack/aws/api/opensearch/__init__.py +11 -0
  6. localstack/aws/api/route53/__init__.py +3 -0
  7. localstack/aws/api/s3/__init__.py +2 -0
  8. localstack/aws/api/s3control/__init__.py +19 -0
  9. localstack/aws/api/secretsmanager/__init__.py +9 -0
  10. localstack/aws/connect.py +35 -15
  11. localstack/config.py +8 -0
  12. localstack/constants.py +3 -0
  13. localstack/dev/kubernetes/__main__.py +39 -14
  14. localstack/runtime/analytics.py +11 -0
  15. localstack/services/acm/provider.py +13 -1
  16. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  17. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -1
  18. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  19. localstack/services/cloudformation/provider.py +26 -1
  20. localstack/services/cloudformation/provider_utils.py +20 -0
  21. localstack/services/cloudformation/resource_provider.py +5 -4
  22. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  23. localstack/services/cloudformation/v2/provider.py +41 -0
  24. localstack/services/kinesis/packages.py +1 -1
  25. localstack/services/kms/models.py +6 -2
  26. localstack/services/lambda_/analytics.py +11 -2
  27. localstack/services/lambda_/invocation/event_manager.py +15 -11
  28. localstack/services/lambda_/invocation/lambda_models.py +4 -0
  29. localstack/services/lambda_/invocation/lambda_service.py +11 -0
  30. localstack/services/lambda_/provider.py +70 -13
  31. localstack/services/opensearch/packages.py +34 -20
  32. localstack/services/route53/provider.py +7 -0
  33. localstack/services/route53resolver/provider.py +5 -0
  34. localstack/services/s3/constants.py +5 -0
  35. localstack/services/s3/exceptions.py +9 -0
  36. localstack/services/s3/models.py +9 -1
  37. localstack/services/s3/provider.py +25 -30
  38. localstack/services/s3/utils.py +46 -1
  39. localstack/services/s3control/provider.py +6 -0
  40. localstack/services/scheduler/provider.py +4 -2
  41. localstack/services/secretsmanager/provider.py +4 -0
  42. localstack/services/ses/provider.py +4 -0
  43. localstack/services/sns/constants.py +13 -0
  44. localstack/services/sns/provider.py +5 -0
  45. localstack/services/sns/v2/models.py +3 -0
  46. localstack/services/sns/v2/provider.py +100 -0
  47. localstack/services/sqs/constants.py +6 -0
  48. localstack/services/sqs/provider.py +9 -1
  49. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  50. localstack/services/ssm/provider.py +6 -0
  51. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +193 -107
  52. localstack/services/stepfunctions/backend/execution.py +4 -5
  53. localstack/services/stepfunctions/provider.py +21 -14
  54. localstack/services/sts/provider.py +7 -0
  55. localstack/services/support/provider.py +5 -1
  56. localstack/services/swf/provider.py +5 -1
  57. localstack/services/transcribe/provider.py +7 -0
  58. localstack/testing/aws/lambda_utils.py +1 -1
  59. localstack/testing/aws/util.py +2 -1
  60. localstack/testing/config.py +1 -0
  61. localstack/utils/aws/client_types.py +2 -4
  62. localstack/utils/bootstrap.py +2 -2
  63. localstack/utils/catalog/catalog.py +3 -2
  64. localstack/utils/container_utils/container_client.py +22 -13
  65. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  66. localstack/version.py +2 -2
  67. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +6 -6
  68. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +76 -75
  69. localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
  70. localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
  71. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
  72. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
  73. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
  74. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
  75. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
  76. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
  77. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
@@ -33,6 +33,7 @@ from localstack.aws.api.s3 import (
33
33
  BucketAlreadyOwnedByYou,
34
34
  BucketCannedACL,
35
35
  BucketLifecycleConfiguration,
36
+ BucketLocationConstraint,
36
37
  BucketLoggingStatus,
37
38
  BucketName,
38
39
  BucketNotEmpty,
@@ -117,7 +118,6 @@ from localstack.aws.api.s3 import (
117
118
  InvalidArgument,
118
119
  InvalidBucketName,
119
120
  InvalidDigest,
120
- InvalidLocationConstraint,
121
121
  InvalidObjectState,
122
122
  InvalidPartNumber,
123
123
  InvalidPartOrder,
@@ -229,7 +229,7 @@ from localstack.aws.handlers import (
229
229
  preprocess_request,
230
230
  serve_custom_service_request_handlers,
231
231
  )
232
- from localstack.constants import AWS_REGION_US_EAST_1
232
+ from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1
233
233
  from localstack.services.edge import ROUTER
234
234
  from localstack.services.plugins import ServiceLifecycleHook
235
235
  from localstack.services.s3.codec import AwsChunkedDecoder
@@ -278,6 +278,7 @@ from localstack.services.s3.utils import (
278
278
  etag_to_base_64_content_md5,
279
279
  extract_bucket_key_version_id_from_copy_source,
280
280
  generate_safe_version_id,
281
+ get_bucket_location_xml,
281
282
  get_canned_acl,
282
283
  get_class_attrs_from_spec_class,
283
284
  get_failed_precondition_copy_source,
@@ -304,6 +305,7 @@ from localstack.services.s3.utils import (
304
305
  validate_dict_fields,
305
306
  validate_failed_precondition,
306
307
  validate_kms_key_id,
308
+ validate_location_constraint,
307
309
  validate_tag_set,
308
310
  )
309
311
  from localstack.services.s3.validation import (
@@ -493,29 +495,19 @@ class S3Provider(S3Api, ServiceLifecycleHook):
493
495
  raise InvalidBucketName("The specified bucket is not valid.", BucketName=bucket_name)
494
496
 
495
497
  create_bucket_configuration = request.get("CreateBucketConfiguration") or {}
496
- bucket_region = create_bucket_configuration.get("LocationConstraint")
498
+
497
499
  bucket_tags = create_bucket_configuration.get("Tags")
498
500
  if bucket_tags:
499
501
  validate_tag_set(bucket_tags, type_set="create-bucket")
500
- if bucket_region:
501
- if context.region == AWS_REGION_US_EAST_1:
502
- if bucket_region in ("us-east-1", "aws-global"):
503
- raise InvalidLocationConstraint(
504
- "The specified location-constraint is not valid",
505
- LocationConstraint=bucket_region,
506
- )
507
- elif context.region != bucket_region:
508
- raise CommonServiceException(
509
- code="IllegalLocationConstraintException",
510
- message=f"The {bucket_region} location constraint is incompatible for the region specific endpoint this request was sent to.",
511
- )
512
- else:
502
+
503
+ location_constraint = create_bucket_configuration.get("LocationConstraint", "")
504
+ validate_location_constraint(context.region, location_constraint)
505
+
506
+ bucket_region = location_constraint
507
+ if not location_constraint:
513
508
  bucket_region = AWS_REGION_US_EAST_1
514
- if context.region != bucket_region:
515
- raise CommonServiceException(
516
- code="IllegalLocationConstraintException",
517
- message="The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.",
518
- )
509
+ if location_constraint == BucketLocationConstraint.EU:
510
+ bucket_region = AWS_REGION_EU_WEST_1
519
511
 
520
512
  store = self.get_store(context.account_id, bucket_region)
521
513
 
@@ -554,6 +546,7 @@ class S3Provider(S3Api, ServiceLifecycleHook):
554
546
  acl=acl,
555
547
  object_ownership=request.get("ObjectOwnership"),
556
548
  object_lock_enabled_for_bucket=request.get("ObjectLockEnabledForBucket"),
549
+ location_constraint=location_constraint,
557
550
  )
558
551
 
559
552
  store.buckets[bucket_name] = s3_bucket
@@ -709,16 +702,18 @@ class S3Provider(S3Api, ServiceLifecycleHook):
709
702
  """
710
703
  store, s3_bucket = self._get_cross_account_bucket(context, bucket)
711
704
 
712
- location_constraint = (
713
- '<?xml version="1.0" encoding="UTF-8"?>\n'
714
- '<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">{{location}}</LocationConstraint>'
715
- )
716
-
717
- location = s3_bucket.bucket_region if s3_bucket.bucket_region != "us-east-1" else ""
718
- location_constraint = location_constraint.replace("{{location}}", location)
705
+ # TODO: Remove usage of getattr once persistence mechanism is updated.
706
+ # If the stored constraint is None the bucket was made before the storage of location_constraint.
707
+ # The EU location constraint wasn't supported before this point so we can safely default to the region.
708
+ location_constraint = getattr(s3_bucket, "location_constraint", None)
709
+ if location_constraint is None:
710
+ location_constraint = (
711
+ s3_bucket.bucket_region if s3_bucket.bucket_region != "us-east-1" else ""
712
+ )
719
713
 
720
- response = GetBucketLocationOutput(LocationConstraint=location_constraint)
721
- return response
714
+ return GetBucketLocationOutput(
715
+ LocationConstraint=get_bucket_location_xml(location_constraint)
716
+ )
722
717
 
723
718
  @handler("PutObject", expand=False)
724
719
  def put_object(
@@ -34,6 +34,7 @@ from localstack.aws.api.s3 import (
34
34
  Grantee,
35
35
  HeadObjectRequest,
36
36
  InvalidArgument,
37
+ InvalidLocationConstraint,
37
38
  InvalidRange,
38
39
  InvalidTag,
39
40
  LifecycleExpiration,
@@ -57,18 +58,25 @@ from localstack.aws.api.s3 import (
57
58
  from localstack.aws.api.s3 import Type as GranteeType
58
59
  from localstack.aws.chain import HandlerChain
59
60
  from localstack.aws.connect import connect_to
61
+ from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1
60
62
  from localstack.http import Response
61
63
  from localstack.services.s3 import checksums
62
64
  from localstack.services.s3.constants import (
63
65
  ALL_USERS_ACL_GRANTEE,
64
66
  AUTHENTICATED_USERS_ACL_GRANTEE,
67
+ BUCKET_LOCATION_CONSTRAINTS,
65
68
  CHECKSUM_ALGORITHMS,
69
+ EU_WEST_1_LOCATION_CONSTRAINTS,
66
70
  LOG_DELIVERY_ACL_GRANTEE,
67
71
  SIGNATURE_V2_PARAMS,
68
72
  SIGNATURE_V4_PARAMS,
69
73
  SYSTEM_METADATA_SETTABLE_HEADERS,
70
74
  )
71
- from localstack.services.s3.exceptions import InvalidRequest, MalformedXML
75
+ from localstack.services.s3.exceptions import (
76
+ IllegalLocationConstraintException,
77
+ InvalidRequest,
78
+ MalformedXML,
79
+ )
72
80
  from localstack.utils.aws import arns
73
81
  from localstack.utils.aws.arns import parse_arn
74
82
  from localstack.utils.objects import singleton_factory
@@ -888,6 +896,27 @@ def validate_tag_set(
888
896
  keys.add(key)
889
897
 
890
898
 
899
+ def validate_location_constraint(context_region: str, location_constraint: str) -> None:
900
+ if location_constraint:
901
+ if context_region == AWS_REGION_US_EAST_1:
902
+ if (
903
+ not config.ALLOW_NONSTANDARD_REGIONS
904
+ and location_constraint not in BUCKET_LOCATION_CONSTRAINTS
905
+ ):
906
+ raise InvalidLocationConstraint(
907
+ "The specified location-constraint is not valid",
908
+ LocationConstraint=location_constraint,
909
+ )
910
+ elif context_region == AWS_REGION_EU_WEST_1:
911
+ if location_constraint not in EU_WEST_1_LOCATION_CONSTRAINTS:
912
+ raise IllegalLocationConstraintException(location_constraint)
913
+ elif context_region != location_constraint:
914
+ raise IllegalLocationConstraintException(location_constraint)
915
+ else:
916
+ if context_region != AWS_REGION_US_EAST_1:
917
+ raise IllegalLocationConstraintException("unspecified")
918
+
919
+
891
920
  def get_unique_key_id(
892
921
  bucket: BucketName, object_key: ObjectKey, version_id: ObjectVersionId
893
922
  ) -> str:
@@ -1105,3 +1134,19 @@ def is_version_older_than_other(version_id: str, other: str):
1105
1134
  See `generate_safe_version_id`
1106
1135
  """
1107
1136
  return base64.b64decode(version_id, altchars=b"._") < base64.b64decode(other, altchars=b"._")
1137
+
1138
+
1139
+ def get_bucket_location_xml(location_constraint: str) -> str:
1140
+ """
1141
+ Returns the formatted XML for the GetBucketLocation operation.
1142
+
1143
+ :param location_constraint: The location constraint to return in the XML. It can be an empty string when
1144
+ it's not specified in the bucket configuration.
1145
+ :return: The XML response.
1146
+ """
1147
+
1148
+ return (
1149
+ '<?xml version="1.0" encoding="UTF-8"?>\n'
1150
+ '<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"'
1151
+ + ("/>" if not location_constraint else f">{location_constraint}</LocationConstraint>")
1152
+ )
@@ -12,6 +12,7 @@ from localstack.aws.api.s3control import (
12
12
  from localstack.aws.forwarder import NotImplementedAvoidFallbackError
13
13
  from localstack.services.s3.models import s3_stores
14
14
  from localstack.services.s3control.validation import validate_tags
15
+ from localstack.state import StateVisitor
15
16
  from localstack.utils.tagging import TaggingService
16
17
 
17
18
 
@@ -21,6 +22,11 @@ class NoSuchResource(CommonServiceException):
21
22
 
22
23
 
23
24
  class S3ControlProvider(S3ControlApi):
25
+ def accept_state_visitor(self, visitor: StateVisitor):
26
+ from moto.s3control.models import s3control_backends
27
+
28
+ visitor.visit(s3control_backends)
29
+
24
30
  """
25
31
  S3Control is a management interface for S3, and can access some of its internals with no public API
26
32
  This requires us to access the s3 stores directly
@@ -1,11 +1,12 @@
1
1
  import logging
2
2
  import re
3
3
 
4
- from moto.scheduler.models import EventBridgeSchedulerBackend
4
+ from moto.scheduler.models import EventBridgeSchedulerBackend, scheduler_backends
5
5
 
6
6
  from localstack.aws.api.scheduler import SchedulerApi, ValidationException
7
7
  from localstack.services.events.rule import RULE_SCHEDULE_CRON_REGEX, RULE_SCHEDULE_RATE_REGEX
8
8
  from localstack.services.plugins import ServiceLifecycleHook
9
+ from localstack.state import StateVisitor
9
10
  from localstack.utils.patch import patch
10
11
 
11
12
  LOG = logging.getLogger(__name__)
@@ -17,7 +18,8 @@ RULE_SCHEDULE_AT_REGEX = re.compile(AT_REGEX)
17
18
 
18
19
 
19
20
  class SchedulerProvider(SchedulerApi, ServiceLifecycleHook):
20
- pass
21
+ def accept_state_visitor(self, visitor: StateVisitor):
22
+ visitor.visit(scheduler_backends)
21
23
 
22
24
 
23
25
  def _validate_schedule_expression(schedule_expression: str) -> None:
@@ -65,6 +65,7 @@ from localstack.aws.api.secretsmanager import (
65
65
  )
66
66
  from localstack.aws.connect import connect_to
67
67
  from localstack.services.moto import call_moto
68
+ from localstack.state import StateVisitor
68
69
  from localstack.utils.aws import arns
69
70
  from localstack.utils.patch import patch
70
71
  from localstack.utils.time import today_no_time
@@ -105,6 +106,9 @@ class SecretsmanagerProvider(SecretsmanagerApi):
105
106
  super().__init__()
106
107
  apply_patches()
107
108
 
109
+ def accept_state_visitor(self, visitor: StateVisitor):
110
+ visitor.visit(secretsmanager_backends)
111
+
108
112
  @staticmethod
109
113
  def get_moto_backend_for_resource(
110
114
  name_or_arn: str, context: RequestContext
@@ -62,6 +62,7 @@ from localstack.http import Resource, Response
62
62
  from localstack.services.moto import call_moto
63
63
  from localstack.services.plugins import ServiceLifecycleHook
64
64
  from localstack.services.ses.models import EmailType, SentEmail, SentEmailBody
65
+ from localstack.state import StateVisitor
65
66
  from localstack.utils.aws import arns
66
67
  from localstack.utils.files import mkdir
67
68
  from localstack.utils.strings import long_uid, to_str
@@ -177,6 +178,9 @@ def register_ses_api_resource():
177
178
 
178
179
 
179
180
  class SesProvider(SesApi, ServiceLifecycleHook):
181
+ def accept_state_visitor(self, visitor: StateVisitor):
182
+ visitor.visit(ses_backends)
183
+
180
184
  #
181
185
  # Lifecycle Hooks
182
186
  #
@@ -25,6 +25,19 @@ VALID_SUBSCRIPTION_ATTR_NAME: list[str] = [
25
25
  "SubscriptionRoleArn",
26
26
  ]
27
27
 
28
+
29
+ VALID_POLICY_ACTIONS = [
30
+ "GetTopicAttributes",
31
+ "SetTopicAttributes",
32
+ "AddPermission",
33
+ "RemovePermission",
34
+ "DeleteTopic",
35
+ "Subscribe",
36
+ "ListSubscriptionsByTopic",
37
+ "Publish",
38
+ "Receive",
39
+ ]
40
+
28
41
  MSG_ATTR_NAME_REGEX = re.compile(r"^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9_\-.]+$")
29
42
  ATTR_TYPE_REGEX = re.compile(r"^(String|Number|Binary)\..+$")
30
43
  VALID_MSG_ATTR_NAME_CHARS = set(ascii_letters + digits + "." + "-" + "_")
@@ -86,6 +86,7 @@ from localstack.utils.aws.arns import (
86
86
  from localstack.utils.collections import PaginatedList, select_from_typed_dict
87
87
  from localstack.utils.strings import short_uid, to_bytes, to_str
88
88
 
89
+ from ...state import StateVisitor
89
90
  from .analytics import internal_api_calls
90
91
 
91
92
  # set up logger
@@ -118,6 +119,10 @@ class SnsProvider(SnsApi, ServiceLifecycleHook):
118
119
  self._publisher = PublishDispatcher()
119
120
  self._signature_cert_pem: str = SNS_SERVER_CERT
120
121
 
122
+ def accept_state_visitor(self, visitor: StateVisitor):
123
+ visitor.visit(sns_backends)
124
+ visitor.visit(sns_stores)
125
+
121
126
  def on_before_stop(self):
122
127
  self._publisher.shutdown()
123
128
 
@@ -7,6 +7,7 @@ from typing import Literal, TypedDict
7
7
  from localstack.aws.api.sns import (
8
8
  Endpoint,
9
9
  MessageAttributeMap,
10
+ PhoneNumber,
10
11
  PlatformApplication,
11
12
  PublishBatchRequestEntry,
12
13
  TopicAttributesMap,
@@ -192,5 +193,7 @@ class SnsStore(BaseStore):
192
193
 
193
194
  TAGS: TaggingService = CrossRegionAttribute(default=TaggingService)
194
195
 
196
+ PHONE_NUMBERS_OPTED_OUT: list[PhoneNumber] = CrossRegionAttribute(default=list)
197
+
195
198
 
196
199
  sns_stores = AccountRegionBundle("sns", SnsStore)
@@ -10,12 +10,15 @@ from rolo import Request, Router, route
10
10
 
11
11
  from localstack.aws.api import CommonServiceException, RequestContext
12
12
  from localstack.aws.api.sns import (
13
+ ActionsList,
13
14
  AmazonResourceName,
14
15
  BatchEntryIdsNotDistinctException,
16
+ CheckIfPhoneNumberIsOptedOutResponse,
15
17
  ConfirmSubscriptionResponse,
16
18
  CreateEndpointResponse,
17
19
  CreatePlatformApplicationResponse,
18
20
  CreateTopicResponse,
21
+ DelegatesList,
19
22
  Endpoint,
20
23
  EndpointDisabledException,
21
24
  GetEndpointAttributesResponse,
@@ -26,6 +29,7 @@ from localstack.aws.api.sns import (
26
29
  InvalidParameterException,
27
30
  InvalidParameterValueException,
28
31
  ListEndpointsByPlatformApplicationResponse,
32
+ ListPhoneNumbersOptedOutResponse,
29
33
  ListPlatformApplicationsResponse,
30
34
  ListString,
31
35
  ListSubscriptionsByTopicResponse,
@@ -35,6 +39,7 @@ from localstack.aws.api.sns import (
35
39
  MapStringToString,
36
40
  MessageAttributeMap,
37
41
  NotFoundException,
42
+ OptInPhoneNumberResponse,
38
43
  PhoneNumber,
39
44
  PlatformApplication,
40
45
  PublishBatchRequestEntryList,
@@ -57,10 +62,12 @@ from localstack.aws.api.sns import (
57
62
  attributeValue,
58
63
  authenticateOnUnsubscribe,
59
64
  endpoint,
65
+ label,
60
66
  message,
61
67
  messageStructure,
62
68
  nextToken,
63
69
  protocol,
70
+ string,
64
71
  subject,
65
72
  subscriptionARN,
66
73
  topicARN,
@@ -84,6 +91,7 @@ from localstack.services.sns.constants import (
84
91
  SUBSCRIPTION_TOKENS_ENDPOINT,
85
92
  VALID_APPLICATION_PLATFORMS,
86
93
  VALID_MSG_ATTR_NAME_CHARS,
94
+ VALID_POLICY_ACTIONS,
87
95
  VALID_SUBSCRIPTION_ATTR_NAME,
88
96
  )
89
97
  from localstack.services.sns.filter import FilterPolicyValidator
@@ -118,6 +126,7 @@ from localstack.services.sns.v2.utils import (
118
126
  parse_and_validate_topic_arn,
119
127
  validate_subscription_attribute,
120
128
  )
129
+ from localstack.state import StateVisitor
121
130
  from localstack.utils.aws.arns import (
122
131
  extract_account_id_from_arn,
123
132
  extract_region_from_arn,
@@ -142,6 +151,9 @@ class SnsProvider(SnsApi, ServiceLifecycleHook):
142
151
  self._publisher = PublishDispatcher()
143
152
  self._signature_cert_pem: str = SNS_SERVER_CERT
144
153
 
154
+ def accept_state_visitor(self, visitor: StateVisitor):
155
+ visitor.visit(sns_stores)
156
+
145
157
  def on_before_stop(self):
146
158
  self._publisher.shutdown()
147
159
 
@@ -1073,6 +1085,94 @@ class SnsProvider(SnsApi, ServiceLifecycleHook):
1073
1085
 
1074
1086
  return GetSMSAttributesResponse(attributes=return_attributes)
1075
1087
 
1088
+ #
1089
+ # Phone number operations
1090
+ #
1091
+
1092
+ def check_if_phone_number_is_opted_out(
1093
+ self, context: RequestContext, phone_number: PhoneNumber, **kwargs
1094
+ ) -> CheckIfPhoneNumberIsOptedOutResponse:
1095
+ store = sns_stores[context.account_id][context.region]
1096
+ return CheckIfPhoneNumberIsOptedOutResponse(
1097
+ isOptedOut=phone_number in store.PHONE_NUMBERS_OPTED_OUT
1098
+ )
1099
+
1100
+ def list_phone_numbers_opted_out(
1101
+ self, context: RequestContext, next_token: string | None = None, **kwargs
1102
+ ) -> ListPhoneNumbersOptedOutResponse:
1103
+ store = self.get_store(context.account_id, context.region)
1104
+ numbers_opted_out = PaginatedList(store.PHONE_NUMBERS_OPTED_OUT)
1105
+ page, nxt = numbers_opted_out.get_page(
1106
+ token_generator=lambda x: x,
1107
+ next_token=next_token,
1108
+ page_size=100,
1109
+ )
1110
+ phone_numbers = {"phoneNumbers": page, "nextToken": nxt}
1111
+ return ListPhoneNumbersOptedOutResponse(**phone_numbers)
1112
+
1113
+ def opt_in_phone_number(
1114
+ self, context: RequestContext, phone_number: PhoneNumber, **kwargs
1115
+ ) -> OptInPhoneNumberResponse:
1116
+ store = self.get_store(context.account_id, context.region)
1117
+ if phone_number in store.PHONE_NUMBERS_OPTED_OUT:
1118
+ store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number)
1119
+ return OptInPhoneNumberResponse()
1120
+
1121
+ #
1122
+ # Permission operations
1123
+ #
1124
+
1125
+ def add_permission(
1126
+ self,
1127
+ context: RequestContext,
1128
+ topic_arn: topicARN,
1129
+ label: label,
1130
+ aws_account_id: DelegatesList,
1131
+ action_name: ActionsList,
1132
+ **kwargs,
1133
+ ) -> None:
1134
+ topic: Topic = self._get_topic(topic_arn, context)
1135
+ policy = json.loads(topic["attributes"]["Policy"])
1136
+ statement = next(
1137
+ (statement for statement in policy["Statement"] if statement["Sid"] == label),
1138
+ None,
1139
+ )
1140
+
1141
+ if statement:
1142
+ raise InvalidParameterException("Invalid parameter: Statement already exists")
1143
+
1144
+ if any(action not in VALID_POLICY_ACTIONS for action in action_name):
1145
+ raise InvalidParameterException(
1146
+ "Invalid parameter: Policy statement action out of service scope!"
1147
+ )
1148
+
1149
+ principals = [
1150
+ f"arn:{get_partition(context.region)}:iam::{account_id}:root"
1151
+ for account_id in aws_account_id
1152
+ ]
1153
+ actions = [f"SNS:{action}" for action in action_name]
1154
+
1155
+ statement = {
1156
+ "Sid": label,
1157
+ "Effect": "Allow",
1158
+ "Principal": {"AWS": principals[0] if len(principals) == 1 else principals},
1159
+ "Action": actions[0] if len(actions) == 1 else actions,
1160
+ "Resource": topic_arn,
1161
+ }
1162
+
1163
+ policy["Statement"].append(statement)
1164
+ topic["attributes"]["Policy"] = json.dumps(policy)
1165
+
1166
+ def remove_permission(
1167
+ self, context: RequestContext, topic_arn: topicARN, label: label, **kwargs
1168
+ ) -> None:
1169
+ topic = self._get_topic(topic_arn, context)
1170
+ policy = json.loads(topic["attributes"]["Policy"])
1171
+ statements = policy["Statement"]
1172
+ statements = [statement for statement in statements if statement["Sid"] != label]
1173
+ policy["Statement"] = statements
1174
+ topic["attributes"]["Policy"] = json.dumps(policy)
1175
+
1076
1176
  def list_tags_for_resource(
1077
1177
  self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs
1078
1178
  ) -> ListTagsForResourceResponse:
@@ -31,6 +31,12 @@ INTERNAL_QUEUE_ATTRIBUTES = [
31
31
  QueueAttributeName.QueueArn,
32
32
  ]
33
33
 
34
+ #
35
+ # If these attributes are set to their default values, they are effectively
36
+ # deleted from the queue attributes and not returned in future calls to get_queue_attributes()
37
+ #
38
+ DELETE_IF_DEFAULT = {"KmsMasterKeyId": "", "KmsDataKeyReusePeriodSeconds": "300"}
39
+
34
40
  INVALID_STANDARD_QUEUE_ATTRIBUTES = [
35
41
  QueueAttributeName.FifoQueue,
36
42
  QueueAttributeName.ContentBasedDeduplication,
@@ -101,6 +101,7 @@ from localstack.services.sqs.utils import (
101
101
  parse_queue_url,
102
102
  )
103
103
  from localstack.services.stores import AccountRegionBundle
104
+ from localstack.state import StateVisitor
104
105
  from localstack.utils.aws.arns import parse_arn
105
106
  from localstack.utils.bootstrap import is_api_enabled
106
107
  from localstack.utils.cloudwatch.cloudwatch_util import (
@@ -659,6 +660,9 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
659
660
  self._router_rules = []
660
661
  self._init_cloudwatch_metrics_reporting()
661
662
 
663
+ def accept_state_visitor(self, visitor: StateVisitor):
664
+ visitor.visit(sqs_stores)
665
+
662
666
  @staticmethod
663
667
  def get_store(account_id: str, region: str) -> SqsStore:
664
668
  return sqs_stores[account_id][region]
@@ -1269,7 +1273,11 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
1269
1273
  for k, v in attributes.items():
1270
1274
  if k in sqs_constants.INTERNAL_QUEUE_ATTRIBUTES:
1271
1275
  raise InvalidAttributeName(f"Unknown Attribute {k}.")
1272
- queue.attributes[k] = v
1276
+ if k in sqs_constants.DELETE_IF_DEFAULT and v == sqs_constants.DELETE_IF_DEFAULT[k]:
1277
+ if k in queue.attributes:
1278
+ del queue.attributes[k]
1279
+ else:
1280
+ queue.attributes[k] = v
1273
1281
 
1274
1282
  # Special cases
1275
1283
  if queue.attributes.get(QueueAttributeName.Policy) == "":