jackal-cfn 0.2.8 → 0.2.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f9789fbb8ee01ff50a47c77185056c6e52c8937
4
- data.tar.gz: 179c2cc85930686d222e3928f05d6e1ec6408a01
3
+ metadata.gz: 9fddaa1ca5447da20d6c856d2ccdf3ba4d3c89d9
4
+ data.tar.gz: acddccbce932860d09c048ba04627df5744cd4b3
5
5
  SHA512:
6
- metadata.gz: 6f9ea369c29581843ea001e10b38747e3b76e12f1cd3756c38f5c5115615380a9922ddb6201bf99ab384da638e3daae677a433482f90b783923eddb96fc86712
7
- data.tar.gz: 93ce81bd66869a378ae651b5fe1ccc6dbce264d52535a9ce66e995834de57722ba8aad980f9b7675786fc3f24a0e433daaa385b24d0325ce4e12e82636e61919
6
+ metadata.gz: 6d5f2187fab6e8d42da3ab3351b53720b1cd351a95df76ffe40390e50786a4f457fef306f6b00b93de1dabba807bcdb1f87e1c0c5b08b67d3f9dd8985d6c5b86
7
+ data.tar.gz: 9b5b9c770c12c9158bdc2ad3bf03041b7200b3aacdaf35f2bbec8c561673646d0b1a2ec63d1acea196e8b246b29468397497291f6b7fb006e6ac96359e2447f6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # v0.2.10
2
+ * Properly handle no parameters in properties
3
+ * Add new JackalStack resource
4
+
1
5
  # v0.2.8
2
6
  * Fix optional instance halt to wait for ami AVAILABLE state
3
7
 
data/README.md CHANGED
@@ -1,23 +1,319 @@
1
1
  # Jackal CFN
2
2
 
3
- CFN proxy to the jackal subsystem.
3
+ Provides jackal integration for AWS CloudFormation custom
4
+ resources and stack event notifications.
4
5
 
5
- ## Supported payload generation
6
+ ## Requirements
6
7
 
7
- ### Events
8
+ This library currently uses the `patron` gem for sending
9
+ notifications to AWS S3. It requires the curl development
10
+ libraries to be available, so ensure it is installed.
8
11
 
9
- Stack events are injected with the following
10
- payload structure:
12
+ ## Usage
13
+
14
+ There are two ways to use this library. The first is to process
15
+ events and resources into proper payloads and inject them into the
16
+ pipeline. The other is to re-process a formatted payload.
17
+
18
+ ### Pipeline Injection
19
+
20
+ Configuration for pipeline injection of resource and event notifications
21
+ is very straightforward:
22
+
23
+ ```json
24
+ {
25
+ "jackal": {
26
+ "cfn": {
27
+ "config": {
28
+ },
29
+ "sources": {
30
+ "input": {
31
+ "type": "sqs",
32
+ "args": {
33
+ SQS_CONFIG
34
+ }
35
+ },
36
+ "output": {
37
+ OUTPUT_SOURCE
38
+ }
39
+ },
40
+ "callbacks": [
41
+ "Jackal::Cfn::Resource",
42
+ "Jackal::Cfn::Event"
43
+ ]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ With this configuration in place resources and events will be received,
50
+ formatted, and delivered to the `OUTPUT_SOURCE`. It will be the job of
51
+ a later service to handle reply notifications (for resources) in this
52
+ style of usage.
53
+
54
+ ### Message Reprocessing
55
+
56
+ When using the re-processing configuration, messages do not continue
57
+ down the pipeline. Instead, the original message is received, formatted,
58
+ and then re-delivered to the originating source (generally an SQS queue).
59
+ The message will be fetched again (this time properly formatted) and
60
+ any matching callbacks will be executed. A sample configuration may look
61
+ something like this:
62
+
63
+ ```json
64
+ {
65
+ "jackal": {
66
+ "cfn": {
67
+ "config": {
68
+ "reprocess": true,
69
+ "ami": {
70
+ "credentials": {
71
+ CREDENTIALS
72
+ }
73
+ }
74
+ },
75
+ "sources": {
76
+ "input": {
77
+ "type": "sqs",
78
+ "args": {
79
+ SQS_CONFIG
80
+ }
81
+ },
82
+ "output": {
83
+ OUTPUT_SOURCE
84
+ }
85
+ },
86
+ "callbacks": [
87
+ "Jackal::Cfn::Resource",
88
+ "Jackal::Cfn::AmiRegister"
89
+ ]
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ The important item to note is the `"reprocess": true` which enables the
96
+ automatic re-processing of messages.
97
+
98
+ ## Custom Resources
99
+
100
+ This library provides support for creating new custom resources. Creation
101
+ is as simple as subclassing:
102
+
103
+ ```ruby
104
+ module Jackal
105
+ module Cfn
106
+ class LocalPrinter < Jackal::Cfn::Resource
107
+
108
+ def execute(message)
109
+ failure_wrap(message) do |payload|
110
+ cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
111
+ cfn_response = build_response(cfn_resource)
112
+ info "CFN Resource: #{cfn_resource.inspect}"
113
+ info "CFN Response: #{cfn_response.inspect}"
114
+ respond_to_stack(cfn_response, cfn_resource[:response_url])
115
+ end
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+ ```
122
+
123
+ This will match `Custom::LocalPrinter` resource requests that are received. It
124
+ will print information into the log about the request and then send a successful
125
+ response back to the stack.
126
+
127
+ ### Builtin Custom Resources
128
+
129
+ This library provides a few custom resources builtin. These can be used directly or
130
+ as examples for building new custom resources.
131
+
132
+ #### `Jackal::Cfn::AmiRegister`
133
+
134
+ Generates and registers an AMI based on an EC2 resource. This allows for building a
135
+ single EC2 resource within a stack that is fully configured, creating and registering
136
+ an AMI based on that EC2 resource, and using the new AMI for instances created within
137
+ an ASG. It is an integrated way to speed up ASG instance launches, but keep all resources
138
+ for the stack fully managed.
139
+
140
+ Resource usage:
11
141
 
12
142
  ```json
143
+ {
144
+ "Type": "Custom::AmiRegister",
145
+ "Properties": {
146
+ "Parameters": {
147
+ "Name": String,
148
+ "InstanceId": String,
149
+ "Description": String,
150
+ "NoReboot": Boolean,
151
+ "BlockDeviceMappings": Array,
152
+ "HaltInstance": Boolean,
153
+ "Region": String
154
+ }
155
+ }
156
+ }
13
157
  ```
14
158
 
15
- ### Resources
159
+ Resource Response:
160
+
161
+ ```json
162
+ {
163
+ "AmiId": String
164
+ }
165
+ ```
166
+
167
+ Configuration:
168
+
169
+ ```json
170
+ {
171
+ "jackal": {
172
+ "cfn": {
173
+ "config": {
174
+ "ami": {
175
+ "credentials": {
176
+ "compute": {
177
+ FOG_CREDENTIALS
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ #### `Jackal::Cfn::AmiManager`
188
+
189
+ This resource is a simplification of the `AmiRegister` resource. The
190
+ `AmiManager` is used to ensure an AMI is removed from the system when
191
+ a stack is destroyed. This allows for customized AMI generation to be
192
+ integrated and ensure that once the stack is destroyed all custom AMIs
193
+ for that stack are destroyed as well.
194
+
195
+ Resource usage:
196
+
197
+ ```json
198
+ {
199
+ "Type": "Custom::AmiManager",
200
+ "Properties": {
201
+ "Parameters": {
202
+ "AmiId": "",
203
+ "Region": ""
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ Resource Response:
210
+
211
+ ```json
212
+ {
213
+ }
214
+ ```
215
+
216
+ Configuration:
217
+
218
+ ```json
219
+ {
220
+ "jackal": {
221
+ "cfn": {
222
+ "config": {
223
+ "ami": {
224
+ "credentials": {
225
+ "compute": {
226
+ FOG_CREDENTIALS
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ #### `Jackal::Cfn::HashExtractor`
237
+
238
+ This resource will extract a nested hash value from a JSON string. Useful
239
+ for when a result may be serialized JSON and a value from that structure
240
+ is required elsewhere.
241
+
242
+ Resource usage:
243
+
244
+ ```json
245
+ {
246
+ "Type": "Custom::HashExtractor",
247
+ "Properties": {
248
+ "Parameters": {
249
+ "Key": "path.to.value.in.hash",
250
+ "Value": JSON
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ Resource Response:
257
+
258
+ ```json
259
+ {
260
+ "Payload": VALUE
261
+ }
262
+ ```
263
+
264
+ #### `Jackal::Cfn::JackalStack`
265
+
266
+ This resource provides an integration point for building stacks
267
+ on remote endpoints. Remote end points are provided via configuration
268
+ and referenced via the `Location` property in the custom resource.
269
+
270
+ Resource usage:
271
+
272
+ ```json
273
+ {
274
+ "Type": "Custom::JackalStack",
275
+ "Properties": {
276
+ "Parameters": {
277
+ STACK_PARAMETERS
278
+ },
279
+ "Location": LOCATION,
280
+ "TemplateURL": URL
281
+ }
282
+ }
283
+ ```
284
+
285
+ Resource Response:
286
+
287
+ The outputs of the stack will be proxied:
288
+
289
+ ```json
290
+ {
291
+ "Outputs.OUTPUT_NAME": "OUTPUT_VALUE"
292
+ }
293
+ ```
16
294
 
17
- Custom stack resources are injected with the
18
- following structure:
295
+ Configuration:
19
296
 
20
297
  ```json
298
+ {
299
+ "jackal": {
300
+ "cfn": {
301
+ "config": {
302
+ "jackal_stack": {
303
+ "credentials": {
304
+ "storage": {
305
+ TEMPLATE_S3_CREDENTIALS
306
+ },
307
+ LOCATION: {
308
+ "provider": NAME,
309
+ MIASMA_CREDENTIALS
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
21
317
  ```
22
318
 
23
319
  ## Info
data/jackal-cfn.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.require_path = 'lib'
12
12
  s.license = 'Apache 2.0'
13
13
  s.add_dependency 'jackal'
14
+ s.add_dependency 'miasma'
14
15
  s.add_dependency 'patron'
15
16
  s.files = Dir['lib/**/*'] + %w(jackal-cfn.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
16
17
  end
@@ -9,9 +9,7 @@ module Jackal
9
9
  # "Type": "Custom::AmiManager",
10
10
  # "Properties": {
11
11
  # "Parameters": {
12
- # "InstanceId": "",
13
12
  # "AmiId": "",
14
- # "Action": "",
15
13
  # "Region": ""
16
14
  # }
17
15
  # }
@@ -10,7 +10,7 @@ module Jackal
10
10
  # "Properties": {
11
11
  # "Parameters": {
12
12
  # "Key": "path.to.value.in.hash",
13
- # "Value": Hash_or_JSON_string
13
+ # "Value": JSON
14
14
  # }
15
15
  # }
16
16
  # }
@@ -0,0 +1,248 @@
1
+ require 'jackal-cfn'
2
+
3
+ module Jackal
4
+ module Cfn
5
+ # Manage AMI Resources
6
+ #
7
+ # Expected resource:
8
+ # {
9
+ # "Type": "Custom::JackalStack",
10
+ # "Properties": {
11
+ # "Parameters": {
12
+ # STACK_PARAMETERS
13
+ # },
14
+ # "Location": LOCATION,
15
+ # "TemplateURL": "URL"
16
+ # }
17
+ # }
18
+ #
19
+ # Required configuration:
20
+ # {
21
+ # "config": {
22
+ # "jackal_stack": {
23
+ # "credentials": {
24
+ # "storage": {
25
+ # AWS_CREDENTIALS
26
+ # },
27
+ # LOCATION: {
28
+ # "provider": "NAME",
29
+ # MIAMSA_CREDENTIALS
30
+ # }
31
+ # }
32
+ # }
33
+ # }
34
+ # }
35
+ class JackalStack < Jackal::Cfn::Resource
36
+
37
+ # Load miasma for stack building
38
+ def setup(*_)
39
+ require 'miasma'
40
+ end
41
+
42
+ # Perform requested stack action
43
+ #
44
+ # @param message [Carnivore::Message]
45
+ def execute(message)
46
+ failure_wrap(message) do |payload|
47
+ cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
48
+ properties = rekey_hash(cfn_resource[:resource_properties])
49
+ parameters = rekey_hash(properties[:parameters])
50
+ cfn_response = build_response(cfn_resource)
51
+ case cfn_resource[:request_type].to_sym
52
+ when :create
53
+ create_stack(cfn_response, cfn_resource, properties, parameters, message)
54
+ when :update
55
+ update_stack(cfn_response, cfn_resource, properties, parameters, message)
56
+ when :delete
57
+ destroy_stack(cfn_response, cfn_resource, message)
58
+ else
59
+ error "Unknown request type received: #{cfn_resource[:request_type].inspect}"
60
+ cfn_response['Status'] = 'FAILED'
61
+ cfn_response['Reason'] = 'Unknown request type received'
62
+ end
63
+ respond_to_stack(cfn_response, cfn_resource[:response_url])
64
+ job_completed(:jackal_cfn, payload, message)
65
+ end
66
+ end
67
+
68
+ # Build API connection to base template storage bucket
69
+ #
70
+ # @param bucket_region [String] location of bucket
71
+ # @return [Miasma::Models::Storage]
72
+ def storage_api(bucket_region)
73
+ Miasma.api(
74
+ :type => :storage,
75
+ :provider => :aws,
76
+ :credentials => config.get(:jackal_stack, :credentials, :storage).merge(
77
+ :aws_bucket_region => bucket_region
78
+ )
79
+ )
80
+ end
81
+
82
+ # Build orchestration API connection for provided location
83
+ #
84
+ # @param location [String, Symbol]
85
+ # @return [Miasma::Models::Orchestration]
86
+ def remote_api(location)
87
+ l_config = config.get(:jackal_stack, :credentials, location)
88
+ if(l_config)
89
+ Miasma.api(
90
+ :type => :orchestration,
91
+ :provider => l_config[:provider],
92
+ :credentials => l_config
93
+ )
94
+ else
95
+ raise ArgumentError.new "Unknown target location provided `#{location}`!"
96
+ end
97
+ end
98
+
99
+ # Fetch a template from a storage bucket
100
+ #
101
+ # @param endpoint [String] URL to template
102
+ # @return [Hash] loaded template data
103
+ def fetch_template(endpoint)
104
+ url = URI.parse(endpoint)
105
+ region = url.host.split('.').first.split('-', 2).last
106
+ if(region == 's3')
107
+ region = 'us-east-1'
108
+ end
109
+ bucket, path = url.path.sub('/', '').split('/', 2)
110
+ MultiJson.load(
111
+ storage_api(region).buckets.get(
112
+ bucket.sub('/', '')
113
+ ).files.get(path).body.read
114
+ )
115
+ end
116
+
117
+ # Create a new stack and update the response values
118
+ #
119
+ # @param response [Hash] response data of action
120
+ # @param resource [Hash] request resource
121
+ # @param properties [Hash] properties of request resource
122
+ # @param parameters [Hash] parmeters provided via properties
123
+ # @param message [Carnivore::Message] original message
124
+ # @return [TrueClass, FalseClass]
125
+ def create_stack(response, resource, properties, parameters, message)
126
+ stack = remote_api(properties[:location]).stacks.build(
127
+ :name => [
128
+ 'JackalStack',
129
+ resource[:logical_resource_id],
130
+ resource[:stack_id].split('/').last
131
+ ].join('-'),
132
+ :template => properties.fetch(:stack, fetch_template(properties[:template_url])),
133
+ :parameters => Hash[parameters.map{|k,v| [Bogo::Utility.camel(k), v] }]
134
+ )
135
+ stack.save
136
+ until(stack.state.to_s.end_with?('complete'))
137
+ message.touch!
138
+ debug "Waiting for created stack to reach completion..."
139
+ sleep 5
140
+ stack.reload
141
+ end
142
+ if(stack.state.to_s.end_with?('complete') || stack.state.to_s.end_with?('failed'))
143
+ stack.outputs.each do |output|
144
+ response['Data']["Outputs.#{output.key}"] = output.value
145
+ end
146
+ response['PhysicalResourceId'] = "#{properties[:location]}-#{stack.id}"
147
+ true
148
+ else
149
+ response['Status'] = 'FAILED'
150
+ response['Reason'] = 'Stack creation failed!'
151
+ stack.destroy
152
+ false
153
+ end
154
+ end
155
+
156
+ # Update an existing stack and update the response values
157
+ #
158
+ # @param response [Hash] response data of action
159
+ # @param resource [Hash] request resource
160
+ # @param properties [Hash] properties of request resource
161
+ # @param parameters [Hash] parmeters provided via properties
162
+ # @param message [Carnivore::Message] original message
163
+ # @return [TrueClass, FalseClass]
164
+ def update_stack(response, resource, properties, parameters, message)
165
+ c_location, stack_id = resource[:physical_resource_id].split('-', 2)
166
+ if(c_location != properties[:location])
167
+ warn "Stack resource has changed location! #{c_location} -> #{properties[:location]}"
168
+ warn "Starting destruction of existing resource: #{stack_id}"
169
+ if(destroy_stack(response, resource, message))
170
+ info "Destruction of stack `#{stack_id}` complete. Creating replacement stack."
171
+ create_stack(response, resource, properties, parameters, message)
172
+ else
173
+ error "Failed to destroy existing stack for replacement `#{stack_id}`"
174
+ end
175
+ else
176
+ stack = remote_api(c_location).stacks.get(stack_id)
177
+ if(stack)
178
+ info "Stack resource update on: #{stack_id}"
179
+ stack.template = fetch_template(properties['TemplateURL'])
180
+ stack.parameters = Hash[parameters.map{|k,v| [Bogo::Utility.camel(k), v] }]
181
+ stack.save
182
+ until(stack.state.to_s.end_with?('complete') || stack.state.to_s.end_with?('failed'))
183
+ debug "Waiting for created stack to reach completion..."
184
+ sleep 5
185
+ stack.reload
186
+ end
187
+ if(stack.state.to_s.end_with?('complete'))
188
+ stack.outputs.each do |output|
189
+ response['Data']["Outputs.#{output.key}"] = output.value
190
+ end
191
+ response['PhysicalResourceId'] = stack.id
192
+ else
193
+ response['Status'] = 'FAILED'
194
+ response['Reason'] = 'Stack update failed!'
195
+ end
196
+ else
197
+ response['Status'] = 'FAILED'
198
+ response['Reason'] = "No stack was found matching request: #{stack_id}"
199
+ end
200
+ end
201
+ end
202
+
203
+ # Destroy the stack
204
+ #
205
+ # @param response [Hash] response data of action
206
+ # @param resource [Hash] request resource
207
+ # @param message [Carnivore::Message] original message
208
+ def destroy_stack(response, resource, message)
209
+ stack = request_destroy(resource[:physical_resource_id])
210
+ if(stack)
211
+ until(stack.state.nil? || stack.state.to_s.end_with?('complete') || stack.state.to_s.end_with?('failed'))
212
+ info "Waiting for stack destruction (#{stack.name})..."
213
+ message.touch!
214
+ sleep 5
215
+ stack.reload
216
+ end
217
+ if(stack.state.to_s.end_with?('failed'))
218
+ response['Status'] = 'FAILED'
219
+ response['Reason'] = 'Failed to delete remote stack!'
220
+ end
221
+ end
222
+ end
223
+
224
+ # Send a stack delete request
225
+ #
226
+ # @param stack_resource_id [String] physical resource ID
227
+ # @return [Miasma::Models::Orchestration::Stack, FalseClass]
228
+ def request_destroy(stack_resource_id)
229
+ location, stack_id = stack_resource_id.split('-', 2)
230
+ if(stack_id)
231
+ begin
232
+ info "Sending stack destruction request to: #{stack_id} in: #{location}"
233
+ stack = remote_api(location).stacks.get(stack_id)
234
+ stack.destroy
235
+ stack
236
+ rescue => e
237
+ error "Stack destruction request failed! #{e.class}: #{e.message}"
238
+ false
239
+ end
240
+ else
241
+ warn "No stack ID registered in resource. Skipping destroy: #{stack_resource_id}"
242
+ false
243
+ end
244
+ end
245
+
246
+ end
247
+ end
248
+ end
@@ -175,12 +175,12 @@ module Jackal
175
175
  payload = unpack(message)
176
176
  yield payload
177
177
  rescue => e
178
- error "Unexpected error encountered processing custom resource - #{e.class}: #{e}"
178
+ error "Unexpected error encountered processing custom resource - #{e.class}: #{e.message}"
179
179
  debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
180
180
  cfn_resource = payload.get(:data, :cfn_resource)
181
181
  cfn_response = build_response(cfn_resource)
182
182
  cfn_response['Status'] = 'FAILED'
183
- cfn_response['Reason'] = "Unexpected error encountered [#{e}]"
183
+ cfn_response['Reason'] = "Unexpected error encountered [#{e.message}]"
184
184
  respond_to_stack(cfn_response, cfn_resource[:response_url])
185
185
  message.confirm!
186
186
  end
@@ -13,7 +13,7 @@ module Jackal
13
13
  # @return [Hash] new hash with snake cased toplevel keys
14
14
  def transform_parameters(params)
15
15
  Smash.new.tap do |new_hash|
16
- params.each do |key, value|
16
+ (params || []).each do |key, value|
17
17
  new_hash[snakecase(key)] = value
18
18
  end
19
19
  end
@@ -1,6 +1,6 @@
1
1
  module Jackal
2
2
  module Cfn
3
3
  # Current version
4
- VERSION = Gem::Version.new('0.2.8')
4
+ VERSION = Gem::Version.new('0.2.10')
5
5
  end
6
6
  end
data/lib/jackal-cfn.rb CHANGED
@@ -12,5 +12,6 @@ module Jackal
12
12
  autoload :HashExtractor, 'jackal-cfn/resource/hash_extractor'
13
13
  autoload :AmiManager, 'jackal-cfn/resource/ami_manager'
14
14
  autoload :AmiRegister, 'jackal-cfn/resource/ami_register'
15
+ autoload :JackalStack, 'jackal-cfn/resource/jackal_stack'
15
16
  end
16
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jackal-cfn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-21 00:00:00.000000000 Z
11
+ date: 2015-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jackal
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: miasma
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: patron
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -56,6 +70,7 @@ files:
56
70
  - lib/jackal-cfn/resource/ami_manager.rb
57
71
  - lib/jackal-cfn/resource/ami_register.rb
58
72
  - lib/jackal-cfn/resource/hash_extractor.rb
73
+ - lib/jackal-cfn/resource/jackal_stack.rb
59
74
  - lib/jackal-cfn/utils.rb
60
75
  - lib/jackal-cfn/utils/fog.rb
61
76
  - lib/jackal-cfn/utils/http.rb