cfn-bridge 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +36 -0
  6. data/Rakefile +2 -0
  7. data/bin/cfn-bridge +5 -0
  8. data/cfn-bridge.gemspec +33 -0
  9. data/lib/cfn-bridge.rb +1 -0
  10. data/lib/cloud_formation/bridge/cli.rb +16 -0
  11. data/lib/cloud_formation/bridge/errors.rb +5 -0
  12. data/lib/cloud_formation/bridge/exception_notifier.rb +59 -0
  13. data/lib/cloud_formation/bridge/executor.rb +50 -0
  14. data/lib/cloud_formation/bridge/http_bridge.rb +30 -0
  15. data/lib/cloud_formation/bridge/names.rb +34 -0
  16. data/lib/cloud_formation/bridge/poller.rb +67 -0
  17. data/lib/cloud_formation/bridge/request.rb +94 -0
  18. data/lib/cloud_formation/bridge/resources/base.rb +31 -0
  19. data/lib/cloud_formation/bridge/resources/cloud_formation_outputs.rb +44 -0
  20. data/lib/cloud_formation/bridge/resources/subscribe_queue_to_topic.rb +66 -0
  21. data/lib/cloud_formation/bridge/version.rb +5 -0
  22. data/spec/files/outputs-formation.json +48 -0
  23. data/spec/files/sample-create-message.json +12 -0
  24. data/spec/files/sample-delete-message.json +12 -0
  25. data/spec/files/subscribe-to-sns-formation.json +79 -0
  26. data/spec/files/test-formation.json +75 -0
  27. data/spec/lib/cloud_formation/bridge/executor_spec.rb +66 -0
  28. data/spec/lib/cloud_formation/bridge/poller_spec.rb +88 -0
  29. data/spec/lib/cloud_formation/bridge/request_spec.rb +130 -0
  30. data/spec/lib/cloud_formation/bridge/resources/cloud_formation_outputs_spec.rb +36 -0
  31. data/spec/lib/cloud_formation/bridge/resources/subscribe_queue_to_topic_spec.rb +95 -0
  32. data/spec/spec_helper.rb +92 -0
  33. data/spec/support/cloud_formation_creator.rb +104 -0
  34. data/spec/support/file_support.rb +7 -0
  35. metadata +245 -0
@@ -0,0 +1,48 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+
4
+ "Description": "Tests the CFN outputs resource",
5
+
6
+ "Parameters": {
7
+ "EntryTopic": {
8
+ "Description": "SNS topic that will be used",
9
+ "Type": "String"
10
+ },
11
+ "EntryQueue": {
12
+ "Description": "The queue to be polled",
13
+ "Type": "String"
14
+ },
15
+ "Name": {
16
+ "Description": "The CFN name to be loaded",
17
+ "Type": "String"
18
+ }
19
+ },
20
+
21
+ "Outputs": {
22
+ "Queue": {
23
+ "Description": "The queue to be polled",
24
+ "Value": {
25
+ "Fn::GetAtt": ["OutputsResource", "Queue"]
26
+ }
27
+ },
28
+ "Topic": {
29
+ "Description": "The topic to be polled",
30
+ "Value": {
31
+ "Fn::GetAtt": ["OutputsResource", "Topic"]
32
+ }
33
+ }
34
+ },
35
+
36
+ "Resources": {
37
+ "OutputsResource": {
38
+ "Type": "Custom::CloudFormationOutputs",
39
+ "Properties": {
40
+ "ServiceToken" : {"Ref" : "EntryTopic"},
41
+ "Name": {
42
+ "Ref": "Name"
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Type" : "Notification",
3
+ "MessageId" : "2c954555-19cd-58b9-9461-0909bad60c89",
4
+ "TopicArn" : "arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z",
5
+ "Subject" : "AWS CloudFormation custom resource request",
6
+ "Message" : "{\"RequestType\":\"Create\",\"TopicArn\":\"arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z\",\"ResponseURL\":\"https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A047170177871%3Astack/test-custom-11fa3098-9e58-4fb4-8af3-63e29a8fa4aa/4d120bd0-1db2-11e4-a357-500162a66ca8%7COutputsResource%7C51e2334b-ace8-4b52-bf7d-3c9f88610c20?AWSAccessKeyId=AKIAJNXHFR7P7YGKLDPQ&Expires=1407368443&Signature=LW%2BCCq8ypCC4qWVy2wdOFx6CTyA%3D\",\"StackId\":\"arn:aws:cloudformation:us-east-1:047170177871:stack/test-custom-11fa3098-9e58-4fb4-8af3-63e29a8fa4aa/4d120bd0-1db2-11e4-a357-500162a66ca8\",\"RequestId\":\"51e2334b-ace8-4b52-bf7d-3c9f88610c20\",\"LogicalResourceId\":\"OutputsResource\",\"ResourceType\":\"Custom::CloudFormationOutputs\",\"ResourceProperties\":{\"Name\":\"test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11\",\"ServiceToken\":\"arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z\"}}",
7
+ "Timestamp" : "2014-08-06T21:40:43.535Z",
8
+ "SignatureVersion" : "1",
9
+ "Signature" : "KSCvlwOejMvy61BVOfmbHqF3LCab0wpnuwlWLXUPXNl8tBjd8j1lHaFdtLzIB7B0jj+t6uvDHA/AM03TmNYuM9wXUO2A71oqls2PWIdchjoW/dCrjvdE0ztqmMLaMG87VqvrZVnIDYKQpZugl5a4AshKEPx2WcEUiLxMi9gjE5L0GpmEG7qE9xyJH7KMQemCAUcNOWAK1Y6LWRGybVICslEACn8NYJc92akDN6im7b9w+i71DJC7dFDEgC/3y7YeGCFsq9I/MQXv71JrWMaJxHfp+LOXP6ucISKBMfJYpBpB4GsVuB0jW/cELkPqFyb2WVvHxWiWm1wvh2K/Vuo74Q==",
10
+ "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-e372f8ca30337fdb084e8ac449342c77.pem",
11
+ "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z:1d19910d-8377-4c9d-a380-46102f10e36f"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Type" : "Notification",
3
+ "MessageId" : "f13ac688-5253-5d90-b825-8ad4f1631b9f",
4
+ "TopicArn" : "arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z",
5
+ "Subject" : "AWS CloudFormation custom resource request",
6
+ "Message" : "{\"RequestType\":\"Delete\",\"TopicArn\":\"arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z\",\"ResponseURL\":\"https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A047170177871%3Astack/test-custom-11fa3098-9e58-4fb4-8af3-63e29a8fa4aa/4d120bd0-1db2-11e4-a357-500162a66ca8%7COutputsResource%7Cf3686111-7338-46be-b312-dd1baaea84b9?AWSAccessKeyId=AKIAJNXHFR7P7YGKLDPQ&Expires=1407368464&Signature=YXOhftA3sOP4YR6NV2c78wHkhP4%3D\",\"StackId\":\"arn:aws:cloudformation:us-east-1:047170177871:stack/test-custom-11fa3098-9e58-4fb4-8af3-63e29a8fa4aa/4d120bd0-1db2-11e4-a357-500162a66ca8\",\"RequestId\":\"f3686111-7338-46be-b312-dd1baaea84b9\",\"LogicalResourceId\":\"OutputsResource\",\"PhysicalResourceId\":\"arn:aws:cloudformation:us-east-1:047170177871:stack/test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11/39d01580-1db2-11e4-9b37-50fa5262a838\",\"ResourceType\":\"Custom::CloudFormationOutputs\",\"ResourceProperties\":{\"Name\":\"test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11\",\"ServiceToken\":\"arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z\"}}",
7
+ "Timestamp" : "2014-08-06T21:41:04.888Z",
8
+ "SignatureVersion" : "1",
9
+ "Signature" : "AkfEKcPydGagg5VJoSjWv1MkgwlCAaKuxHZ8NR6SOZePdcT/6H8OiE7epEjt9C17wtIx/WnqOHS7Nmm5PufogNYsxb2fCcZ9D6Av02vd1M5KwmpsRvWWHR2qLvOQk0lRjkkrD0Ol2Yt9TBwxsmYX77b6Nzg7Fn0qGoPNNwlCyUU0qfIQgXfDZR8SO9AZIS7GJ2MUTzXVJCNc5VoNcEDl+0y0yOrz97TetS4qBXhUaNhgfag/Zt/hMNHR+DKlirTSYWx7HhiSDt2jrlhsLMZy0wzMzKwV0Gp40xVZGc33kk38tt1ChrkKUzpEca3u8pRZ8f2jU0EI0Xh48Sn2tAGrLw==",
10
+ "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-e372f8ca30337fdb084e8ac449342c77.pem",
11
+ "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:047170177871:test-custom-b2bea71b-e1b8-4b8f-9855-1c92e1f0cd11-CustomResourcesTopic-1E5ZVSSFPY65Z:1d19910d-8377-4c9d-a380-46102f10e36f"
12
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+
4
+ "Description": "Tests the subscribe sns to sqs resource",
5
+
6
+ "Parameters": {
7
+ "EntryTopic": {
8
+ "Description": "SNS topic that will be used",
9
+ "Type": "String"
10
+ }
11
+ },
12
+
13
+ "Outputs": {
14
+ "FirstQueue": {
15
+ "Description": "The first queue",
16
+ "Value": {
17
+ "Fn::GetAtt": ["FirstQueue", "QueueName"]
18
+ }
19
+ },
20
+ "FirstQueueArn": {
21
+ "Description": "The first queue",
22
+ "Value": {
23
+ "Fn::GetAtt": ["FirstQueue", "Arn"]
24
+ }
25
+ },
26
+ "FirstTopic": {
27
+ "Description": "The first topic",
28
+ "Value": {
29
+ "Ref": "FirstTopic"
30
+ }
31
+ },
32
+ "SubscriptionArn": {
33
+ "Description": "The arn for the subscription created",
34
+ "Value": {
35
+ "Fn::GetAtt": ["SubscribeResource", "Arn"]
36
+ }
37
+ },
38
+ "SubscriptionEndpoint": {
39
+ "Description": "The endpoint for the subscription created",
40
+ "Value": {
41
+ "Fn::GetAtt": ["SubscribeResource", "Endpoint"]
42
+ }
43
+ },
44
+ "SubscriptionProtocol": {
45
+ "Description": "The protocol for the subscription created",
46
+ "Value": {
47
+ "Fn::GetAtt": ["SubscribeResource", "Protocol"]
48
+ }
49
+ }
50
+ },
51
+
52
+ "Resources": {
53
+ "FirstQueue": {
54
+ "Type": "AWS::SQS::Queue",
55
+ "Properties": {
56
+ "ReceiveMessageWaitTimeSeconds": 20,
57
+ "VisibilityTimeout": 60
58
+ }
59
+ },
60
+ "FirstTopic": {
61
+ "Type": "AWS::SNS::Topic"
62
+ },
63
+ "SubscribeResource": {
64
+ "Type": "Custom::SubscribeSQSQueueToSNSTopic",
65
+ "Properties": {
66
+ "ServiceToken": {
67
+ "Ref": "EntryTopic"
68
+ },
69
+ "TopicArn": {
70
+ "Ref": "FirstTopic"
71
+ },
72
+ "QueueName": {
73
+ "Fn::GetAtt": ["FirstQueue", "QueueName"]
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+
4
+ "Description": "Creates the SNS topic, SQS queue and instance that will service the custom resources queue",
5
+
6
+ "Outputs": {
7
+ "Queue": {
8
+ "Description": "The queue to where we will poll messages",
9
+ "Value": {
10
+ "Fn::GetAtt": ["CustomResourcesQueue", "QueueName"]
11
+ }
12
+ },
13
+ "Topic" : {
14
+ "Description": "The topic that will be taking messages",
15
+ "Value": {
16
+ "Ref": "CustomResourcesTopic"
17
+ }
18
+ }
19
+ },
20
+
21
+ "Resources": {
22
+ "CustomResourcesQueue": {
23
+ "Type": "AWS::SQS::Queue",
24
+ "Properties": {
25
+ "ReceiveMessageWaitTimeSeconds": 20,
26
+ "VisibilityTimeout": 60
27
+ }
28
+ },
29
+ "CustomResourcesTopic": {
30
+ "Type": "AWS::SNS::Topic",
31
+ "Properties": {
32
+ "Subscription": [
33
+ {
34
+ "Endpoint": {
35
+ "Fn::GetAtt": ["CustomResourcesQueue", "Arn"]
36
+ },
37
+ "Protocol": "sqs"
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ "SNSToSQSPolicy": {
43
+ "Type": "AWS::SQS::QueuePolicy",
44
+ "Properties": {
45
+ "PolicyDocument": {
46
+ "Id": "PushMessageToSQSPolicy",
47
+ "Version": "2012-10-17",
48
+ "Statement": [
49
+ {
50
+ "Sid": "allow-sns-to-send-message-to-sqs",
51
+ "Effect": "Allow",
52
+ "Action": ["sqs:SendMessage"],
53
+ "Principal": {
54
+ "AWS": "*"
55
+ },
56
+ "Resource": "*",
57
+ "Condition": {
58
+ "ArnEquals": {
59
+ "aws:SourceArn": {
60
+ "Ref": "CustomResourcesTopic"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ },
67
+ "Queues": [
68
+ {
69
+ "Ref": "CustomResourcesQueue"
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,66 @@
1
+ require 'cloud_formation/bridge/executor'
2
+ require 'cloud_formation/bridge/request'
3
+ require 'cloud_formation/bridge/names'
4
+
5
+ describe CloudFormation::Bridge::Executor do
6
+
7
+ FIELDS = CloudFormation::Bridge::Names::FIELDS
8
+ TYPES = CloudFormation::Bridge::Names::TYPES
9
+
10
+ let(:custom_resource) { double("custom-resource") }
11
+ let(:url) { "http://example.com/request-id" }
12
+ let(:custom_resource_name) { "sample-custom-resource" }
13
+ let(:registry) { { custom_resource_name => custom_resource } }
14
+ let(:response) { { FIELDS::LOGICAL_RESOURCE_ID => "sample-resource" } }
15
+
16
+ context 'when processing resources that are available' do
17
+
18
+ TYPES::ALL.each do |type|
19
+ it "should #{type.downcase} the resource if it is a #{type} request" do
20
+ request = CloudFormation::Bridge::Request.new(
21
+ FIELDS::REQUEST_TYPE => type,
22
+ FIELDS::RESOURCE_TYPE => custom_resource_name,
23
+ )
24
+
25
+ expect(custom_resource).to receive(type.downcase).with(request).and_return(response)
26
+
27
+ expect(request).to receive(:succeed!).with(response)
28
+
29
+ executor = CloudFormation::Bridge::Executor.new(registry)
30
+ executor.execute(request)
31
+ end
32
+ end
33
+
34
+ it 'should fail the request if the resource type is unknown' do
35
+ request = CloudFormation::Bridge::Request.new(
36
+ FIELDS::REQUEST_TYPE => TYPES::CREATE,
37
+ FIELDS::RESOURCE_TYPE => custom_resource_name,
38
+ )
39
+
40
+ expect(request).to receive(:fail!) do |message|
41
+ expect(message).to match(/#{custom_resource_name}/)
42
+ end
43
+
44
+ executor = CloudFormation::Bridge::Executor.new
45
+ executor.execute(request)
46
+ end
47
+
48
+ it 'should fail the request if the resource raised an exception' do
49
+ request = CloudFormation::Bridge::Request.new(
50
+ FIELDS::REQUEST_TYPE => TYPES::CREATE,
51
+ FIELDS::RESOURCE_TYPE => custom_resource_name,
52
+ )
53
+
54
+ message = "This should not have been called"
55
+ expect(custom_resource).to receive(:create).and_raise(ArgumentError.new(message))
56
+
57
+ expect(request).to receive(:fail!).with(message)
58
+
59
+ executor = CloudFormation::Bridge::Executor.new(registry)
60
+ executor.execute(request)
61
+ end
62
+
63
+ end
64
+
65
+
66
+ end
@@ -0,0 +1,88 @@
1
+ require 'cloud_formation/bridge/poller'
2
+ require 'cloud_formation/bridge/executor'
3
+
4
+ describe CloudFormation::Bridge::Poller do
5
+
6
+ include CloudFormationCreator
7
+ include FileSupport
8
+
9
+ shared_context 'pulls messages from queue' do
10
+
11
+ it "should correctly poll the queue and execute the message" do
12
+ message = read_file("sample-create-message.json")
13
+ queue.send_message(message)
14
+
15
+ expect(executor).to receive(:execute) do |request|
16
+ expect(request).to be_create
17
+ expect(request.logical_resource_id).to eq('OutputsResource')
18
+ expect(request.resource_type).to eq('Custom::CloudFormationOutputs')
19
+ end
20
+
21
+ expect(poller.visible_messages).to eq(1)
22
+
23
+ thread_poller = poller
24
+
25
+ thread = Thread.new do
26
+ thread_poller.start
27
+ end
28
+
29
+ while poller.visible_messages != 0
30
+ sleep(1)
31
+ end
32
+
33
+ poller.stop
34
+
35
+ thread.join
36
+ end
37
+
38
+ end
39
+
40
+ context 'in integration', integration: true do
41
+
42
+ let(:queue_name) { "test-cfn-queue-#{SecureRandom.uuid}" }
43
+ let(:queue) { queues.create(queue_name) }
44
+ let(:executor) { instance_double(CloudFormation::Bridge::Executor) }
45
+ let(:poller) { CloudFormation::Bridge::Poller.new(queue_name, executor) }
46
+
47
+ after do
48
+ queue.delete
49
+ end
50
+
51
+ include_context 'pulls messages from queue'
52
+
53
+ end
54
+
55
+ context 'in isolation' do
56
+
57
+ let(:queue_name) { "test-cfn-queue-#{SecureRandom.uuid}" }
58
+ let(:executor) { instance_double(CloudFormation::Bridge::Executor) }
59
+ let(:queue_items) { [] }
60
+ let(:queue) { instance_double(AWS::SQS::Queue) }
61
+ let(:poller) { CloudFormation::Bridge::Poller.new(queue_name, executor) }
62
+
63
+ before do
64
+ allow(queue).to receive(:send_message) do |message|
65
+ queue_items << message
66
+ nil
67
+ end
68
+ allow(queue).to receive(:visible_messages) { queue_items.size }
69
+ allow(queue).to receive(:receive_message) do
70
+ if queue_items.empty?
71
+ sleep(20)
72
+ nil
73
+ else
74
+ message = queue_items.shift
75
+ message_double = instance_double(AWS::SQS::ReceivedMessage, id: "some-id", body: message, handle: 'some-handle')
76
+ expect(message_double).to receive(:delete)
77
+ message_double
78
+ end
79
+ end
80
+
81
+ allow(poller).to receive(:queue).and_return(queue)
82
+ end
83
+
84
+ include_context 'pulls messages from queue'
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,130 @@
1
+ require 'cloud_formation/bridge/names'
2
+ require 'cloud_formation/bridge/request'
3
+ require 'cloud_formation/bridge/http_bridge'
4
+
5
+ describe CloudFormation::Bridge::Request do
6
+
7
+ FIELDS = CloudFormation::Bridge::Names::FIELDS
8
+ TYPES = CloudFormation::Bridge::Names::TYPES
9
+ RESULTS = CloudFormation::Bridge::Names::RESULTS
10
+
11
+ let(:properties) do
12
+ {
13
+ "seleniumTester" => "SeleniumTest()",
14
+ "endpoints" => ["http://mysite.com", "http://myecommercesite.com/", "http://search.mysite.com"],
15
+ "frequencyOfTestsPerHour" => ["3", "2", "4"]
16
+ }
17
+ end
18
+
19
+ let(:request_hash) do
20
+ {
21
+ "RequestType" => "Create",
22
+ "ResponseURL" => "http://pre-signed-S3-url-for-response",
23
+ "StackId" => "arn:aws:cloudformation:us-east-1:EXAMPLE/stack-name/guid",
24
+ "RequestId" => "unique id for this create request",
25
+ "ResourceType" => "Custom::SeleniumTester",
26
+ "LogicalResourceId" => "MySeleniumTester",
27
+ "ResourceProperties" => properties,
28
+ }
29
+ end
30
+
31
+ let(:request) { CloudFormation::Bridge::Request.new(request_hash) }
32
+ let(:physical_id) { "sample-physical-id" }
33
+
34
+ context 'matching fields' do
35
+
36
+ it 'should correctly fill the values' do
37
+ expect(request).to be_create
38
+ expect(request.request_url).to eq("http://pre-signed-S3-url-for-response")
39
+ expect(request.stack_id).to eq("arn:aws:cloudformation:us-east-1:EXAMPLE/stack-name/guid")
40
+ expect(request.request_id).to eq("unique id for this create request")
41
+ expect(request.logical_resource_id).to eq("MySeleniumTester")
42
+ expect(request.resource_type).to eq("Custom::SeleniumTester")
43
+ expect(request.resource_properties).to eq(properties)
44
+ end
45
+
46
+ it 'should correctly detect an update request' do
47
+ request_hash["RequestType"] = 'Update'
48
+
49
+ expect(request).to be_update
50
+ end
51
+
52
+ it 'should correctly detect a delete request' do
53
+ request_hash["RequestType"] = 'Delete'
54
+
55
+ expect(request).to be_delete
56
+ end
57
+
58
+ end
59
+
60
+ context 'when building responses' do
61
+
62
+ it 'should correctly fill in the expected fields' do
63
+ response = request.build_response
64
+
65
+ expect(response).to include(
66
+ FIELDS::STATUS => RESULTS::SUCCESS,
67
+ FIELDS::STACK_ID => request.stack_id,
68
+ FIELDS::REQUEST_ID => request.request_id,
69
+ FIELDS::LOGICAL_RESOURCE_ID => request.logical_resource_id,
70
+ )
71
+
72
+ expect(response[FIELDS::PHYSICAL_RESOURCE_ID]).not_to be_empty
73
+ end
74
+
75
+ it 'should use the base response physical id if it was provided' do
76
+ base = {
77
+ FIELDS::PHYSICAL_RESOURCE_ID => physical_id,
78
+ }
79
+
80
+ response = request.build_response(base)
81
+
82
+ expect(response[FIELDS::PHYSICAL_RESOURCE_ID]).to eq(physical_id)
83
+ end
84
+
85
+ it 'should use the request physical id if the base response was not provided but request has it' do
86
+ request_hash[FIELDS::PHYSICAL_RESOURCE_ID] = physical_id
87
+ response = request.build_response
88
+ expect(response[FIELDS::PHYSICAL_RESOURCE_ID]).to eq(physical_id)
89
+ end
90
+
91
+ end
92
+
93
+ context 'when succeeding' do
94
+
95
+ it "should push the response with its own values" do
96
+ base = {
97
+ FIELDS::PHYSICAL_RESOURCE_ID => physical_id,
98
+ }
99
+
100
+ expect(CloudFormation::Bridge::HttpBridge).to receive(:put).with(request.request_url, request.build_response(base))
101
+
102
+ request.succeed!(base)
103
+ end
104
+
105
+ end
106
+
107
+ context 'when failing' do
108
+
109
+ it "should push the failure message with the known values" do
110
+ message = "failed to create resource"
111
+
112
+ expect(CloudFormation::Bridge::HttpBridge).to receive(:put) do |url, options|
113
+ expect(url).to eq(request.request_url)
114
+
115
+ response = request.build_response(
116
+ FIELDS::REASON => message,
117
+ FIELDS::STATUS => RESULTS::FAILED
118
+ )
119
+
120
+ response.delete(FIELDS::PHYSICAL_RESOURCE_ID)
121
+
122
+ expect(options).to include(response)
123
+ end
124
+
125
+ request.fail!(message)
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,36 @@
1
+ require 'cloud_formation/bridge/resources/cloud_formation_outputs'
2
+ require 'cloud_formation/bridge/poller'
3
+
4
+ describe CloudFormation::Bridge::Resources::CloudFormationOutputs do
5
+
6
+ include CloudFormationCreator
7
+
8
+ it 'should correctly pull the outputs from the CFN', integration: true do
9
+
10
+ with_main_formation do |stack, poller, outputs|
11
+ params = {
12
+ "Name" => stack.name,
13
+ "EntryTopic" => outputs["Topic"],
14
+ "EntryQueue" => outputs["Queue"],
15
+ }
16
+
17
+ with_cloud_formation('outputs-formation', params, false) do |outputs_stack|
18
+
19
+ wait_until "messages available" do
20
+ poller.visible_messages > 0
21
+ end
22
+
23
+ poller.poll
24
+
25
+ wait_until_complete(outputs_stack)
26
+
27
+ expected_outputs = stack_outputs(outputs_stack)
28
+
29
+ expect(outputs["Topic"]).to eq(expected_outputs["Topic"])
30
+ expect(outputs["Queue"]).to eq(expected_outputs["Queue"])
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,95 @@
1
+ require 'cloud_formation/bridge/resources/subscribe_queue_to_topic'
2
+ require 'cloud_formation/bridge/names'
3
+ require 'cloud_formation/bridge/poller'
4
+ require 'cloud_formation/bridge/request'
5
+
6
+ describe CloudFormation::Bridge::Resources::SubscribeQueueToTopic do
7
+
8
+ include CloudFormationCreator
9
+
10
+ context 'in isolation', integration: true do
11
+
12
+ let(:uuid) { SecureRandom.uuid }
13
+ let(:topic) { topics.create("cfn-test-topic-#{uuid}") }
14
+ let(:queue_name) { "cfn-test-queue-#{uuid}" }
15
+ let(:queue) { queues.create(queue_name) }
16
+
17
+ before do
18
+ @items = [topic, queue]
19
+ end
20
+
21
+ after do
22
+ @items.map(&:delete)
23
+ end
24
+
25
+ it 'should subscribe the the queue to the topic' do
26
+ request = CloudFormation::Bridge::Request.new(
27
+ CloudFormation::Bridge::Names::FIELDS::RESOURCE_PROPERTIES => {
28
+ CloudFormation::Bridge::Resources::SubscribeQueueToTopic::QUEUE_NAME => queue_name,
29
+ CloudFormation::Bridge::Resources::SubscribeQueueToTopic::TOPIC_ARN => topic.arn,
30
+ },
31
+ )
32
+
33
+ response = subject.create(request)
34
+ data = response[CloudFormation::Bridge::Names::FIELDS::DATA]
35
+
36
+ subscription = subscriptions[data[CloudFormation::Bridge::Resources::SubscribeQueueToTopic::ARN]]
37
+
38
+ payload = "this is the payload"
39
+
40
+ topic.publish( payload )
41
+
42
+ sleep(1)
43
+
44
+ message = queue.receive_message
45
+ body = JSON.parse(message.body)
46
+
47
+ expect(body["Message"]).to eq(payload)
48
+ expect(subscription.exists?).to eq(true)
49
+ end
50
+
51
+ end
52
+
53
+ context 'when creating the cloud formation' do
54
+
55
+ it "should accept a request and subscribe a queue to an SNS topic", integration: true do
56
+
57
+ with_main_formation do |_, poller, outputs|
58
+ params = {
59
+ "EntryTopic" => outputs["Topic"],
60
+ }
61
+
62
+ with_cloud_formation('subscribe-to-sns-formation', params, false) do |topics_stack|
63
+
64
+ wait_until "messages available" do
65
+ poller.visible_messages > 0
66
+ end
67
+
68
+ poller.poll
69
+
70
+ wait_until_complete(topics_stack)
71
+
72
+ message_body = "sample message"
73
+
74
+ topic = topics[topics_stack["FirstTopic"]]
75
+ topic.publish(message_body)
76
+
77
+ queue = queues.named(topics_stack["FirstQueue"])
78
+ message = queue.receive_message
79
+ message.delete
80
+
81
+ received_message = JSON.parse(message.body)["Message"]
82
+
83
+ expected_outputs = stack_outputs(topics_stack)
84
+
85
+ expect(received_message).to eq(message_body)
86
+ expect(expected_outputs["SubscriptionProtocol"]).to eq('sqs')
87
+ expect(expected_outputs["FirstQueueArn"]).to eq(expected_outputs["SubscriptionEndpoint"])
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+
95
+ end