cfn-bridge 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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