Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b90f172

Browse files
authored
fix CloudFormation SNS Subscribe with Region parameter (#12676)
1 parent a298730 commit b90f172

File tree

5 files changed

+115
-2
lines changed

5 files changed

+115
-2
lines changed

‎localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
from typing import Optional, TypedDict
77

88
import localstack.services.cloudformation.provider_utils as util
9+
from localstack import config
10+
from localstack.aws.connect import ServiceLevelClientFactory
911
from localstack.services.cloudformation.resource_provider import (
12+
ConvertingInternalClientFactory,
1013
OperationStatus,
1114
ProgressEvent,
1215
ResourceProvider,
@@ -62,7 +65,7 @@ def create(
6265
6366
"""
6467
model = request.desired_state
65-
sns = request.aws_client_factory.sns
68+
sns = self._get_client(request).sns
6669

6770
params = util.select_attributes(model=model, params=["TopicArn", "Protocol", "Endpoint"])
6871

@@ -128,7 +131,7 @@ def update(
128131
"""
129132
model = request.desired_state
130133
model["Id"] = request.previous_state["Id"]
131-
sns = request.aws_client_factory.sns
134+
sns = self._get_client(request).sns
132135

133136
attrs = [
134137
"DeliveryPolicy",
@@ -153,3 +156,23 @@ def update(
153156
@staticmethod
154157
def attr_val(val):
155158
return json.dumps(val) if isinstance(val, dict) else str(val)
159+
160+
@staticmethod
161+
def _get_client(
162+
request: ResourceRequest[SNSSubscriptionProperties],
163+
) -> ServiceLevelClientFactory:
164+
model = request.desired_state
165+
if subscription_region := model.get("Region"):
166+
# FIXME: this is hacky, maybe we should have access to the original parameters for the `aws_client_factory`
167+
# as we now need to manually use them
168+
# Not all internal CloudFormation requests will be directed to the same region and account
169+
# maybe we could need to expose a proper client factory where we can override some parameters like the
170+
# Region
171+
factory = ConvertingInternalClientFactory(use_ssl=config.DISTRIBUTED_MODE)
172+
client_params = dict(request.aws_client_factory._client_creation_params)
173+
client_params["region_name"] = subscription_region
174+
service_factory = factory(**client_params)
175+
else:
176+
service_factory = request.aws_client_factory
177+
178+
return service_factory

‎tests/aws/services/cloudformation/resources/test_sns.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,44 @@ def test_sns_topic_with_attributes(infrastructure_setup, aws_client, snapshot):
150150
TopicArn=outputs["TopicArn"],
151151
)
152152
snapshot.match("topic-archive-policy", response["Attributes"]["ArchivePolicy"])
153+
154+
155+
@markers.aws.validated
156+
def test_sns_subscription_region(
157+
snapshot,
158+
deploy_cfn_template,
159+
aws_client,
160+
sqs_queue,
161+
aws_client_factory,
162+
region_name,
163+
secondary_region_name,
164+
cleanups,
165+
):
166+
snapshot.add_transformer(snapshot.transform.cloudformation_api())
167+
snapshot.add_transformer(snapshot.transform.regex(secondary_region_name, "<region2>"))
168+
topic_name = f"topic-{short_uid()}"
169+
# we create a topic in a secondary region, different from the stack
170+
sns_client = aws_client_factory(region_name=secondary_region_name).sns
171+
topic_arn = sns_client.create_topic(Name=topic_name)["TopicArn"]
172+
cleanups.append(lambda: sns_client.delete_topic(TopicArn=topic_arn))
173+
174+
queue_url = sqs_queue
175+
queue_arn = aws_client.sqs.get_queue_attributes(
176+
QueueUrl=queue_url, AttributeNames=["QueueArn"]
177+
)["Attributes"]["QueueArn"]
178+
179+
# we want to deploy the Stack in a different region than the Topic, to see how CloudFormation properly does the
180+
# `Subscribe` call in the `Region` parameter of the Subscription resource
181+
stack = deploy_cfn_template(
182+
parameters={
183+
"TopicArn": topic_arn,
184+
"QueueArn": queue_arn,
185+
"TopicRegion": secondary_region_name,
186+
},
187+
template_path=os.path.join(
188+
os.path.dirname(__file__), "../../../templates/sns_subscription_cross_region.yml"
189+
),
190+
)
191+
sub_arn = stack.outputs["SubscriptionArn"]
192+
subscription = sns_client.get_subscription_attributes(SubscriptionArn=sub_arn)
193+
snapshot.match("subscription-1", subscription)

‎tests/aws/services/cloudformation/resources/test_sns.snapshot.json‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,27 @@
112112
"MessageRetentionPeriod": "30"
113113
}
114114
}
115+
},
116+
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": {
117+
"recorded-date": "28-05-2025, 10:47:01",
118+
"recorded-content": {
119+
"subscription-1": {
120+
"Attributes": {
121+
"ConfirmationWasAuthenticated": "true",
122+
"Endpoint": "arn:<partition>:sqs:<region>:111111111111:<resource:1>",
123+
"Owner": "111111111111",
124+
"PendingConfirmation": "false",
125+
"Protocol": "sqs",
126+
"RawMessageDelivery": "true",
127+
"SubscriptionArn": "arn:<partition>:sns:<region2>:111111111111:<resource:4>:<resource:2>",
128+
"SubscriptionPrincipal": "arn:<partition>:iam::111111111111:user/<resource:3>",
129+
"TopicArn": "arn:<partition>:sns:<region2>:111111111111:<resource:4>"
130+
},
131+
"ResponseMetadata": {
132+
"HTTPHeaders": {},
133+
"HTTPStatusCode": 200
134+
}
135+
}
136+
}
115137
}
116138
}

‎tests/aws/services/cloudformation/resources/test_sns.validation.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": {
3+
"last_validated_date": "2025-05-28T10:46:56+00:00"
4+
},
25
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": {
36
"last_validated_date": "2023-11-27T20:27:29+00:00"
47
},
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Parameters:
2+
TopicArn:
3+
Type: String
4+
Description: The ARN of the SNS topic to subscribe to
5+
QueueArn:
6+
Type: String
7+
Description: The URL of the SQS queue to send messages to
8+
TopicRegion:
9+
Type: String
10+
Description: The region of the SNS Topic
11+
Resources:
12+
SnsSubscription:
13+
Type: AWS::SNS::Subscription
14+
Properties:
15+
Protocol: sqs
16+
TopicArn: !Ref TopicArn
17+
Endpoint: !Ref QueueArn
18+
RawMessageDelivery: true
19+
Region: !Ref TopicRegion
20+
21+
Outputs:
22+
SubscriptionArn:
23+
Value: !Ref SnsSubscription
24+
Description: The ARN of the SNS subscription

0 commit comments

Comments
 (0)