Skip to content

Commit

Permalink
add update operations for AWS::SNS::Subscription and AWS::SQS::QueueP…
Browse files Browse the repository at this point in the history
…olicy. (#10545)
  • Loading branch information
pinzon committed Mar 29, 2024
1 parent a5c6433 commit 05c52b2
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 9 deletions.
34 changes: 28 additions & 6 deletions localstack/services/sns/resource_providers/aws_sns_subscription.py
Expand Up @@ -66,17 +66,14 @@ def create(

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

def attr_val(val):
return json.dumps(val) if isinstance(val, (dict, list)) else str(val)

attrs = [
"DeliveryPolicy",
"FilterPolicy",
"FilterPolicyScope",
"RawMessageDelivery",
"RedrivePolicy",
]
attributes = {a: attr_val(model[a]) for a in attrs if a in model}
attributes = {a: self.attr_val(model[a]) for a in attrs if a in model}

if attributes:
params["Attributes"] = attributes
Expand Down Expand Up @@ -128,6 +125,31 @@ def update(
"""
Update a resource
"""
raise NotImplementedError
model = request.desired_state
model["Id"] = request.previous_state["Id"]
sns = request.aws_client_factory.sns

attrs = [
"DeliveryPolicy",
"FilterPolicy",
"FilterPolicyScope",
"RawMessageDelivery",
"RedrivePolicy",
]
for a in attrs:
if a in model:
sns.set_subscription_attributes(
SubscriptionArn=model["Id"],
AttributeName=a,
AttributeValue=self.attr_val(model[a]),
)
return ProgressEvent(
status=OperationStatus.SUCCESS,
resource_model=model,
custom_context=request.custom_context,
)

@staticmethod
def attr_val(val):
return json.dumps(val) if isinstance(val, dict) else str(val)
Expand Up @@ -98,4 +98,13 @@ def update(
"""
Update a resource
"""
raise NotImplementedError
model = request.desired_state
sqs = request.aws_client_factory.sqs
for queue in model.get("Queues", []):
policy = json.dumps(model["PolicyDocument"])
sqs.set_queue_attributes(QueueUrl=queue, Attributes={"Policy": policy})

return ProgressEvent(
status=OperationStatus.SUCCESS,
resource_model=request.desired_state,
)
31 changes: 31 additions & 0 deletions tests/aws/services/cloudformation/resources/test_sns.py
Expand Up @@ -100,3 +100,34 @@ def test_deploy_stack_with_sns_topic(deploy_cfn_template, aws_client):
rs = aws_client.sns.list_topics()
topics = [tp for tp in rs["Topics"] if tp["TopicArn"] == topic_arn]
assert not topics


@markers.aws.validated
def test_update_subscription(snapshot, deploy_cfn_template, aws_client, sqs_queue, sns_topic):
topic_arn = sns_topic["Attributes"]["TopicArn"]
queue_url = sqs_queue
queue_arn = aws_client.sqs.get_queue_attributes(
QueueUrl=queue_url, AttributeNames=["QueueArn"]
)["Attributes"]["QueueArn"]

stack = deploy_cfn_template(
parameters={"TopicArn": topic_arn, "QueueArn": queue_arn},
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/sns_subscription.yml"
),
)
sub_arn = stack.outputs["SubscriptionArn"]
subscription = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn)
snapshot.match("subscription-1", subscription)

deploy_cfn_template(
parameters={"TopicArn": topic_arn, "QueueArn": queue_arn},
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/sns_subscription_update.yml"
),
stack_name=stack.stack_name,
is_update=True,
)
subscription_updated = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn)
snapshot.match("subscription-2", subscription_updated)
snapshot.add_transformer(snapshot.transform.cloudformation_api())
39 changes: 39 additions & 0 deletions tests/aws/services/cloudformation/resources/test_sns.snapshot.json
Expand Up @@ -65,5 +65,44 @@
}
}
}
},
"tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": {
"recorded-date": "29-03-2024, 21:16:26",
"recorded-content": {
"subscription-1": {
"Attributes": {
"ConfirmationWasAuthenticated": "true",
"Endpoint": "arn:aws:sqs:<region>:111111111111:<resource:1>",
"Owner": "111111111111",
"PendingConfirmation": "false",
"Protocol": "sqs",
"RawMessageDelivery": "true",
"SubscriptionArn": "arn:aws:sns:<region>:111111111111:<resource:4>:<resource:2>",
"SubscriptionPrincipal": "arn:aws:iam::111111111111:user/<resource:3>",
"TopicArn": "arn:aws:sns:<region>:111111111111:<resource:4>"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"subscription-2": {
"Attributes": {
"ConfirmationWasAuthenticated": "true",
"Endpoint": "arn:aws:sqs:<region>:111111111111:<resource:1>",
"Owner": "111111111111",
"PendingConfirmation": "false",
"Protocol": "sqs",
"RawMessageDelivery": "false",
"SubscriptionArn": "arn:aws:sns:<region>:111111111111:<resource:4>:<resource:2>",
"SubscriptionPrincipal": "arn:aws:iam::111111111111:user/<resource:3>",
"TopicArn": "arn:aws:sns:<region>:111111111111:<resource:4>"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
}
}
@@ -1,5 +1,8 @@
{
"tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": {
"last_validated_date": "2023-11-27T20:27:29+00:00"
},
"tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": {
"last_validated_date": "2024-03-29T21:16:21+00:00"
}
}
39 changes: 39 additions & 0 deletions tests/aws/services/cloudformation/resources/test_sqs.py
Expand Up @@ -5,6 +5,7 @@

from localstack.testing.pytest import markers
from localstack.utils.strings import short_uid
from localstack.utils.sync import wait_until


@markers.aws.unknown
Expand Down Expand Up @@ -120,3 +121,41 @@ def test_update_queue_no_change(deploy_cfn_template, aws_client, snapshot):
},
)
snapshot.match("outputs-2", updated_stack.outputs)


@markers.aws.validated
def test_update_sqs_queuepolicy(deploy_cfn_template, aws_client, snapshot):
stack = deploy_cfn_template(
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/sqs_with_queuepolicy.yaml"
)
)

policy = aws_client.sqs.get_queue_attributes(
QueueUrl=stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"]
)
snapshot.match("policy1", policy["Attributes"]["Policy"])

updated_stack = deploy_cfn_template(
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/sqs_with_queuepolicy_updated.yaml"
),
is_update=True,
stack_name=stack.stack_name,
)

def check_policy_updated():
policy_updated = aws_client.sqs.get_queue_attributes(
QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"]
)
assert policy_updated["Attributes"]["Policy"] != policy["Attributes"]["Policy"]
return policy_updated

wait_until(check_policy_updated)

policy = aws_client.sqs.get_queue_attributes(
QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"]
)

snapshot.match("policy2", policy["Attributes"]["Policy"])
snapshot.add_transformer(snapshot.transform.cloudformation_api())
35 changes: 35 additions & 0 deletions tests/aws/services/cloudformation/resources/test_sqs.snapshot.json
Expand Up @@ -11,5 +11,40 @@
"QueueUrl": "<queue-url>"
}
}
},
"tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": {
"recorded-date": "27-03-2024, 20:30:24",
"recorded-content": {
"policy1": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"sqs:SendMessage",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
"Resource": "arn:aws:sqs:<region>:111111111111:<resource:1>"
}
]
},
"policy2": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": [
"sqs:SendMessage",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
"Resource": "arn:aws:sqs:<region>:111111111111:<resource:1>"
}
]
}
}
}
}
@@ -1,5 +1,8 @@
{
"tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": {
"last_validated_date": "2023-12-08T20:11:26+00:00"
},
"tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": {
"last_validated_date": "2024-03-27T20:30:23+00:00"
}
}
20 changes: 20 additions & 0 deletions tests/aws/templates/sns_subscription.yml
@@ -0,0 +1,20 @@
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
Resources:
SnsSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: sqs
TopicArn: !Ref TopicArn
Endpoint: !Ref QueueArn
RawMessageDelivery: true

Outputs:
SubscriptionArn:
Value: !Ref SnsSubscription
Description: The ARN of the SNS subscription
20 changes: 20 additions & 0 deletions tests/aws/templates/sns_subscription_update.yml
@@ -0,0 +1,20 @@
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
Resources:
SnsSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: sqs
TopicArn: !Ref TopicArn
Endpoint: !Ref QueueArn
RawMessageDelivery: false

Outputs:
SubscriptionArn:
Value: !Ref SnsSubscription
Description: The ARN of the SNS subscription
2 changes: 0 additions & 2 deletions tests/aws/templates/sqs_with_queuepolicy.yaml
@@ -1,8 +1,6 @@
Resources:
Queue4A7E3555:
Type: AWS::SQS::Queue
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
QueuePolicy25439813:
Type: AWS::SQS::QueuePolicy
Properties:
Expand Down
25 changes: 25 additions & 0 deletions tests/aws/templates/sqs_with_queuepolicy_updated.yaml
@@ -0,0 +1,25 @@
Resources:
Queue4A7E3555:
Type: AWS::SQS::Queue
QueuePolicy25439813:
Type: AWS::SQS::QueuePolicy
Properties:
PolicyDocument:
Statement:
- Action:
- sqs:SendMessage
- sqs:GetQueueAttributes
- sqs:GetQueueUrl
Effect: Deny
Principal: "*"
Resource:
Fn::GetAtt:
- Queue4A7E3555
- Arn
Version: "2012-10-17"
Queues:
- Ref: Queue4A7E3555
Outputs:
QueueUrlOutput:
Value:
Ref: Queue4A7E3555

0 comments on commit 05c52b2

Please sign in to comment.