cfn-bridge 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/bin/cfn-bridge +5 -0
- data/cfn-bridge.gemspec +33 -0
- data/lib/cfn-bridge.rb +1 -0
- data/lib/cloud_formation/bridge/cli.rb +16 -0
- data/lib/cloud_formation/bridge/errors.rb +5 -0
- data/lib/cloud_formation/bridge/exception_notifier.rb +59 -0
- data/lib/cloud_formation/bridge/executor.rb +50 -0
- data/lib/cloud_formation/bridge/http_bridge.rb +30 -0
- data/lib/cloud_formation/bridge/names.rb +34 -0
- data/lib/cloud_formation/bridge/poller.rb +67 -0
- data/lib/cloud_formation/bridge/request.rb +94 -0
- data/lib/cloud_formation/bridge/resources/base.rb +31 -0
- data/lib/cloud_formation/bridge/resources/cloud_formation_outputs.rb +44 -0
- data/lib/cloud_formation/bridge/resources/subscribe_queue_to_topic.rb +66 -0
- data/lib/cloud_formation/bridge/version.rb +5 -0
- data/spec/files/outputs-formation.json +48 -0
- data/spec/files/sample-create-message.json +12 -0
- data/spec/files/sample-delete-message.json +12 -0
- data/spec/files/subscribe-to-sns-formation.json +79 -0
- data/spec/files/test-formation.json +75 -0
- data/spec/lib/cloud_formation/bridge/executor_spec.rb +66 -0
- data/spec/lib/cloud_formation/bridge/poller_spec.rb +88 -0
- data/spec/lib/cloud_formation/bridge/request_spec.rb +130 -0
- data/spec/lib/cloud_formation/bridge/resources/cloud_formation_outputs_spec.rb +36 -0
- data/spec/lib/cloud_formation/bridge/resources/subscribe_queue_to_topic_spec.rb +95 -0
- data/spec/spec_helper.rb +92 -0
- data/spec/support/cloud_formation_creator.rb +104 -0
- data/spec/support/file_support.rb +7 -0
- 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
|