Skip to content

Commit e0c26d1

Browse files
committed
fix CloudFormation SNS Subscribe with Region parameter
1 parent a298730 commit e0c26d1

File tree

5 files changed

+104
-1
lines changed

5 files changed

+104
-1
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from typing import Optional, TypedDict
77

88
import localstack.services.cloudformation.provider_utils as util
9+
from localstack import config
910
from localstack.services.cloudformation.resource_provider import (
11+
ConvertingInternalClientFactory,
1012
OperationStatus,
1113
ProgressEvent,
1214
ResourceProvider,
@@ -62,7 +64,18 @@ def create(
6264
6365
"""
6466
model = request.desired_state
65-
sns = request.aws_client_factory.sns
67+
if subscription_region := model.get("Region"):
68+
# FIXME: this is hacky, maybe we should have access to the original parameters for the `aws_client_factory`
69+
# as we now need to manually use them
70+
# Not all internal CloudFormation requests will be directed to the same region and account
71+
# maybe we could need to expose a proper client factory where we can override some parameters like the
72+
# Region
73+
factory = ConvertingInternalClientFactory(use_ssl=config.DISTRIBUTED_MODE)
74+
client_params = dict(request.aws_client_factory._client_creation_params)
75+
client_params["region_name"] = subscription_region
76+
sns = factory(**client_params).sns
77+
else:
78+
sns = request.aws_client_factory.sns
6679

6780
params = util.select_attributes(model=model, params=["TopicArn", "Protocol", "Endpoint"])
6881

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)