Publish messages to SNS when CloudFormation NotifcationARNs is set (#4295)

Co-authored-by: Paul Roberts <paroberts@guidewire.com>
This commit is contained in:
Paul Roberts 2021-09-16 02:26:50 -07:00 committed by GitHub
parent 5081e96ebd
commit 45b2684eb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 25 deletions

View File

@ -8,7 +8,12 @@ from boto3 import Session
from collections import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_without_milliseconds
from moto.core.models import ACCOUNT_ID
from moto.core.utils import (
iso_8601_datetime_with_milliseconds,
iso_8601_datetime_without_milliseconds,
)
from moto.sns.models import sns_backends
from .parsing import ResourceMap, OutputMap
from .utils import (
@ -261,19 +266,21 @@ class FakeStack(BaseModel):
def _add_stack_event(
self, resource_status, resource_status_reason=None, resource_properties=None
):
self.events.append(
FakeEvent(
stack_id=self.stack_id,
stack_name=self.name,
logical_resource_id=self.name,
physical_resource_id=self.stack_id,
resource_type="AWS::CloudFormation::Stack",
resource_status=resource_status,
resource_status_reason=resource_status_reason,
resource_properties=resource_properties,
)
event = FakeEvent(
stack_id=self.stack_id,
stack_name=self.name,
logical_resource_id=self.name,
physical_resource_id=self.stack_id,
resource_type="AWS::CloudFormation::Stack",
resource_status=resource_status,
resource_status_reason=resource_status_reason,
resource_properties=resource_properties,
)
event.sendToSns(self.region_name, self.notification_arns)
self.events.append(event)
def _add_resource_event(
self,
logical_resource_id,
@ -431,6 +438,7 @@ class FakeEvent(BaseModel):
resource_status,
resource_status_reason=None,
resource_properties=None,
client_request_token=None,
):
self.stack_id = stack_id
self.stack_name = stack_name
@ -442,6 +450,35 @@ class FakeEvent(BaseModel):
self.resource_properties = resource_properties
self.timestamp = datetime.utcnow()
self.event_id = uuid.uuid4()
self.client_request_token = client_request_token
def sendToSns(self, region, sns_topic_arns):
message = """StackId='{stack_id}'
Timestamp='{timestamp}'
EventId='{event_id}'
LogicalResourceId='{logical_resource_id}'
Namespace='{account_id}'
ResourceProperties='{resource_properties}'
ResourceStatus='{resource_status}'
ResourceStatusReason='{resource_status_reason}'
ResourceType='{resource_type}'
StackName='{stack_name}'
ClientRequestToken='{client_request_token}'""".format(
stack_id=self.stack_id,
timestamp=iso_8601_datetime_with_milliseconds(self.timestamp),
event_id=self.event_id,
logical_resource_id=self.logical_resource_id,
account_id=ACCOUNT_ID,
resource_properties=self.resource_properties,
resource_status=self.resource_status,
resource_status_reason=self.resource_status_reason,
resource_type=self.resource_type,
stack_name=self.stack_name,
client_request_token=self.client_request_token,
)
for sns_topic_arn in sns_topic_arns:
sns_backends[region].publish(message, arn=sns_topic_arn)
def filter_stacks(all_stacks, status_filter):

View File

@ -11,6 +11,7 @@ import boto.s3
import boto.s3.key
import boto.cloudformation
from boto.exception import BotoServerError
from freezegun import freeze_time
import sure # noqa
import pytest
@ -19,6 +20,8 @@ from moto.core import ACCOUNT_ID
from moto import (
mock_cloudformation_deprecated,
mock_s3_deprecated,
mock_sns_deprecated,
mock_sqs_deprecated,
mock_route53_deprecated,
mock_iam_deprecated,
mock_dynamodb2_deprecated,
@ -151,18 +154,69 @@ def test_creating_stacks_across_regions():
@mock_cloudformation_deprecated
@mock_sns_deprecated
@mock_sqs_deprecated
def test_create_stack_with_notification_arn():
sqs_conn = boto.connect_sqs()
queue = sqs_conn.create_queue("fake-queue", visibility_timeout=3)
queue_arn = queue.get_attributes()["QueueArn"]
sns_conn = boto.connect_sns()
topic = sns_conn.create_topic("fake-topic")
topic_arn = topic["CreateTopicResponse"]["CreateTopicResult"]["TopicArn"]
sns_conn.subscribe(topic_arn, "sqs", queue_arn)
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack_with_notifications",
template_body=dummy_template_json,
notification_arns="arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID),
)
with freeze_time("2015-01-01 12:00:00"):
conn.create_stack(
"test_stack_with_notifications",
template_body=dummy_template_json,
notification_arns=topic_arn,
)
stack = conn.describe_stacks()[0]
[n.value for n in stack.notification_arns].should.contain(
"arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)
)
[n.value for n in stack.notification_arns].should.contain(topic_arn)
with freeze_time("2015-01-01 12:00:01"):
message = queue.read(1)
msg = json.loads(message.get_body())
msg["Message"].should.contain("StackId='{}'\n".format(stack.stack_id))
msg["Message"].should.contain("Timestamp='2015-01-01T12:00:00.000Z'\n")
msg["Message"].should.contain("LogicalResourceId='test_stack_with_notifications'\n")
msg["Message"].should.contain("ResourceStatus='CREATE_IN_PROGRESS'\n")
msg["Message"].should.contain("ResourceStatusReason='User Initiated'\n")
msg["Message"].should.contain("ResourceType='AWS::CloudFormation::Stack'\n")
msg["Message"].should.contain("StackName='test_stack_with_notifications'\n")
msg.should.have.key("MessageId")
msg.should.have.key("Signature")
msg.should.have.key("SignatureVersion")
msg.should.have.key("Subject")
msg["Timestamp"].should.equal("2015-01-01T12:00:00.000Z")
msg["TopicArn"].should.equal(topic_arn)
msg.should.have.key("Type")
msg.should.have.key("UnsubscribeURL")
with freeze_time("2015-01-01 12:00:02"):
message = queue.read(1)
msg = json.loads(message.get_body())
msg["Message"].should.contain("StackId='{}'\n".format(stack.stack_id))
msg["Message"].should.contain("Timestamp='2015-01-01T12:00:00.000Z'\n")
msg["Message"].should.contain("LogicalResourceId='test_stack_with_notifications'\n")
msg["Message"].should.contain("ResourceStatus='CREATE_COMPLETE'\n")
msg["Message"].should.contain("ResourceStatusReason='None'\n")
msg["Message"].should.contain("ResourceType='AWS::CloudFormation::Stack'\n")
msg["Message"].should.contain("StackName='test_stack_with_notifications'\n")
msg.should.have.key("MessageId")
msg.should.have.key("Signature")
msg.should.have.key("SignatureVersion")
msg.should.have.key("Subject")
msg["Timestamp"].should.equal("2015-01-01T12:00:00.000Z")
msg["TopicArn"].should.equal(topic_arn)
msg.should.have.key("Type")
msg.should.have.key("UnsubscribeURL")
@mock_cloudformation_deprecated

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
import json
from collections import OrderedDict
from datetime import datetime, timedelta
from freezegun import freeze_time
import pytz
import boto3
@ -11,7 +12,14 @@ import sure # noqa
import pytest
from moto import mock_cloudformation, mock_dynamodb2, mock_s3, mock_sqs, mock_ec2
from moto import (
mock_cloudformation,
mock_dynamodb2,
mock_s3,
mock_sns,
mock_sqs,
mock_ec2,
)
from moto.core import ACCOUNT_ID
from .test_cloudformation_stack_crud import dummy_template_json2, dummy_template_json4
from tests import EXAMPLE_AMI_ID
@ -901,18 +909,64 @@ def test_creating_stacks_across_regions():
@mock_cloudformation
@mock_sns
@mock_sqs
def test_create_stack_with_notification_arn():
sqs = boto3.resource("sqs", region_name="us-east-1")
queue = sqs.create_queue(QueueName="fake-queue")
queue_arn = queue.attributes["QueueArn"]
sns = boto3.client("sns", region_name="us-east-1")
topic = sns.create_topic(Name="fake-topic")
topic_arn = topic["TopicArn"]
sns.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint=queue_arn)
cf = boto3.resource("cloudformation", region_name="us-east-1")
cf.create_stack(
StackName="test_stack_with_notifications",
TemplateBody=dummy_template_json,
NotificationARNs=["arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)],
NotificationARNs=[topic_arn],
)
stack = list(cf.stacks.all())[0]
stack.notification_arns.should.contain(
"arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)
)
stack.notification_arns.should.contain(topic_arn)
messages = queue.receive_messages()
messages.should.have.length_of(1)
msg = json.loads(messages[0].body)
msg["Message"].should.contain("StackId='{}'\n".format(stack.stack_id))
msg["Message"].should.contain("LogicalResourceId='test_stack_with_notifications'\n")
msg["Message"].should.contain("ResourceStatus='CREATE_IN_PROGRESS'\n")
msg["Message"].should.contain("ResourceStatusReason='User Initiated'\n")
msg["Message"].should.contain("ResourceType='AWS::CloudFormation::Stack'\n")
msg["Message"].should.contain("StackName='test_stack_with_notifications'\n")
msg.should.have.key("MessageId")
msg.should.have.key("Signature")
msg.should.have.key("SignatureVersion")
msg.should.have.key("Subject")
msg.should.have.key("Timestamp")
msg["TopicArn"].should.equal(topic_arn)
msg.should.have.key("Type")
msg.should.have.key("UnsubscribeURL")
messages = queue.receive_messages()
messages.should.have.length_of(1)
msg = json.loads(messages[0].body)
msg["Message"].should.contain("StackId='{}'\n".format(stack.stack_id))
msg["Message"].should.contain("LogicalResourceId='test_stack_with_notifications'\n")
msg["Message"].should.contain("ResourceStatus='CREATE_COMPLETE'\n")
msg["Message"].should.contain("ResourceStatusReason='None'\n")
msg["Message"].should.contain("ResourceType='AWS::CloudFormation::Stack'\n")
msg["Message"].should.contain("StackName='test_stack_with_notifications'\n")
msg.should.have.key("MessageId")
msg.should.have.key("Signature")
msg.should.have.key("SignatureVersion")
msg.should.have.key("Subject")
msg.should.have.key("Timestamp")
msg["TopicArn"].should.equal(topic_arn)
msg.should.have.key("Type")
msg.should.have.key("UnsubscribeURL")
@mock_cloudformation