Publish messages to SNS when CloudFormation NotifcationARNs is set (#4295)
Co-authored-by: Paul Roberts <paroberts@guidewire.com>
This commit is contained in:
parent
5081e96ebd
commit
45b2684eb6
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user