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

Skip to content

fix CloudFormation SNS Subscribe with Region parameter #12676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from typing import Optional, TypedDict

import localstack.services.cloudformation.provider_utils as util
from localstack import config
from localstack.aws.connect import ServiceLevelClientFactory
from localstack.services.cloudformation.resource_provider import (
ConvertingInternalClientFactory,
OperationStatus,
ProgressEvent,
ResourceProvider,
Expand Down Expand Up @@ -62,7 +65,7 @@ def create(

"""
model = request.desired_state
sns = request.aws_client_factory.sns
sns = self._get_client(request).sns

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

Expand Down Expand Up @@ -128,7 +131,7 @@ def update(
"""
model = request.desired_state
model["Id"] = request.previous_state["Id"]
sns = request.aws_client_factory.sns
sns = self._get_client(request).sns

attrs = [
"DeliveryPolicy",
Expand All @@ -153,3 +156,23 @@ def update(
@staticmethod
def attr_val(val):
return json.dumps(val) if isinstance(val, dict) else str(val)

@staticmethod
def _get_client(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future we should make this utility available to other resource providers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! We should probably have a simpler way to re-use the client_params, as I now have to access private attributes via request.aws_client_factory._client_creation_params, which isn't great. Maybe we should save them somewhere, I'm not sure 🤔

request: ResourceRequest[SNSSubscriptionProperties],
) -> ServiceLevelClientFactory:
model = request.desired_state
if subscription_region := model.get("Region"):
# FIXME: this is hacky, maybe we should have access to the original parameters for the `aws_client_factory`
# as we now need to manually use them
# Not all internal CloudFormation requests will be directed to the same region and account
# maybe we could need to expose a proper client factory where we can override some parameters like the
# Region
factory = ConvertingInternalClientFactory(use_ssl=config.DISTRIBUTED_MODE)
client_params = dict(request.aws_client_factory._client_creation_params)
client_params["region_name"] = subscription_region
service_factory = factory(**client_params)
else:
service_factory = request.aws_client_factory

return service_factory
41 changes: 41 additions & 0 deletions tests/aws/services/cloudformation/resources/test_sns.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,44 @@ def test_sns_topic_with_attributes(infrastructure_setup, aws_client, snapshot):
TopicArn=outputs["TopicArn"],
)
snapshot.match("topic-archive-policy", response["Attributes"]["ArchivePolicy"])


@markers.aws.validated
def test_sns_subscription_region(
snapshot,
deploy_cfn_template,
aws_client,
sqs_queue,
aws_client_factory,
region_name,
secondary_region_name,
cleanups,
):
snapshot.add_transformer(snapshot.transform.cloudformation_api())
snapshot.add_transformer(snapshot.transform.regex(secondary_region_name, "<region2>"))
topic_name = f"topic-{short_uid()}"
# we create a topic in a secondary region, different from the stack
sns_client = aws_client_factory(region_name=secondary_region_name).sns
topic_arn = sns_client.create_topic(Name=topic_name)["TopicArn"]
cleanups.append(lambda: sns_client.delete_topic(TopicArn=topic_arn))

queue_url = sqs_queue
queue_arn = aws_client.sqs.get_queue_attributes(
QueueUrl=queue_url, AttributeNames=["QueueArn"]
)["Attributes"]["QueueArn"]

# we want to deploy the Stack in a different region than the Topic, to see how CloudFormation properly does the
# `Subscribe` call in the `Region` parameter of the Subscription resource
stack = deploy_cfn_template(
parameters={
"TopicArn": topic_arn,
"QueueArn": queue_arn,
"TopicRegion": secondary_region_name,
},
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/sns_subscription_cross_region.yml"
),
)
sub_arn = stack.outputs["SubscriptionArn"]
subscription = sns_client.get_subscription_attributes(SubscriptionArn=sub_arn)
snapshot.match("subscription-1", subscription)
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,27 @@
"MessageRetentionPeriod": "30"
}
}
},
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": {
"recorded-date": "28-05-2025, 10:47:01",
"recorded-content": {
"subscription-1": {
"Attributes": {
"ConfirmationWasAuthenticated": "true",
"Endpoint": "arn:<partition>:sqs:<region>:111111111111:<resource:1>",
"Owner": "111111111111",
"PendingConfirmation": "false",
"Protocol": "sqs",
"RawMessageDelivery": "true",
"SubscriptionArn": "arn:<partition>:sns:<region2>:111111111111:<resource:4>:<resource:2>",
"SubscriptionPrincipal": "arn:<partition>:iam::111111111111:user/<resource:3>",
"TopicArn": "arn:<partition>:sns:<region2>:111111111111:<resource:4>"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": {
"last_validated_date": "2025-05-28T10:46:56+00:00"
},
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": {
"last_validated_date": "2023-11-27T20:27:29+00:00"
},
Expand Down
24 changes: 24 additions & 0 deletions tests/aws/templates/sns_subscription_cross_region.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Parameters:
TopicArn:
Type: String
Description: The ARN of the SNS topic to subscribe to
QueueArn:
Type: String
Description: The URL of the SQS queue to send messages to
TopicRegion:
Type: String
Description: The region of the SNS Topic
Resources:
SnsSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: sqs
TopicArn: !Ref TopicArn
Endpoint: !Ref QueueArn
RawMessageDelivery: true
Region: !Ref TopicRegion

Outputs:
SubscriptionArn:
Value: !Ref SnsSubscription
Description: The ARN of the SNS subscription
Loading