miasma-terraform 0.1.0
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 +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +88 -0
- data/lib/miasma-terraform.rb +3 -0
- data/lib/miasma-terraform/stack.rb +579 -0
- data/lib/miasma-terraform/version.rb +4 -0
- data/lib/miasma/contrib/terraform.rb +52 -0
- data/lib/miasma/contrib/terraform/orchestration.rb +441 -0
- data/miasma-terraform.gemspec +16 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5c46a5855cfe72db0105261f00f0f48e36916aaa
|
4
|
+
data.tar.gz: 5fb33600986aa116c94a7ab1b7eb17abee71912a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ee7697f996ecf4816f33c43fc26ab203db407347cd82a7725d4ed26368eca99ae9b894f69de6a41fc41137635400d39ab4ac4c98258641467530e3eb0ac60029
|
7
|
+
data.tar.gz: ee293821c6ae291fdcfe35a7cd391fa90d8c7b1e0a8cd24899a7a7633a01fb9943d20beca18767f513d857da17047821601889a031bd469842e970538d9f9398
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2017 Chris Roberts
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Miasma Terraform
|
2
|
+
|
3
|
+
Terraform API plugin for the miasma cloud library
|
4
|
+
|
5
|
+
## Supported credential attributes:
|
6
|
+
|
7
|
+
Supported attributes used in the credentials section of API
|
8
|
+
configurations:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
Miasma.api(
|
12
|
+
:type => :orchestration,
|
13
|
+
:provider => :terraform,
|
14
|
+
:credentials => {
|
15
|
+
...
|
16
|
+
}
|
17
|
+
)
|
18
|
+
```
|
19
|
+
|
20
|
+
### Common attributes
|
21
|
+
|
22
|
+
* `terraform_driver` - Interface to use (`atlas`, `boule`, `local`)
|
23
|
+
|
24
|
+
### Atlas attributes
|
25
|
+
|
26
|
+
_NOTE: Atlas support not yet available_
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Miasma.api(
|
30
|
+
:type => :orchestration,
|
31
|
+
:provider => :terraform,
|
32
|
+
:credentials => {
|
33
|
+
:terraform_driver => :atlas
|
34
|
+
...
|
35
|
+
}
|
36
|
+
)
|
37
|
+
```
|
38
|
+
|
39
|
+
* `terraform_atlas_endpoint` - Atlas URL
|
40
|
+
* `terraform_atlas_token` - Atlas token
|
41
|
+
|
42
|
+
### Boule attributes
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Miasma.api(
|
46
|
+
:type => :orchestration,
|
47
|
+
:provider => :terraform,
|
48
|
+
:credentials => {
|
49
|
+
:terraform_driver => :boule
|
50
|
+
...
|
51
|
+
}
|
52
|
+
)
|
53
|
+
```
|
54
|
+
|
55
|
+
* `terraform_boule_endpoint` - Boule URL
|
56
|
+
|
57
|
+
### Local attributes
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
Miasma.api(
|
61
|
+
:type => :orchestration,
|
62
|
+
:provider => :terraform,
|
63
|
+
:credentials => {
|
64
|
+
:terraform_driver => :local
|
65
|
+
...
|
66
|
+
}
|
67
|
+
)
|
68
|
+
```
|
69
|
+
|
70
|
+
* `terraform_local_directory` - Path to store stack data
|
71
|
+
* `terraform_local_scrub_destroyed` - Delete stack data directory on destroy
|
72
|
+
|
73
|
+
## Current support matrix
|
74
|
+
|
75
|
+
|Model |Create|Read|Update|Delete|
|
76
|
+
|--------------|------|----|------|------|
|
77
|
+
|AutoScale | | | | |
|
78
|
+
|BlockStorage | | | | |
|
79
|
+
|Compute | | | | |
|
80
|
+
|DNS | | | | |
|
81
|
+
|LoadBalancer | | | | |
|
82
|
+
|Network | | | | |
|
83
|
+
|Orchestration | X | X | X | X |
|
84
|
+
|Queues | | | | |
|
85
|
+
|Storage | | | | |
|
86
|
+
|
87
|
+
## Info
|
88
|
+
* Repository: https://github.com/miasma-rb/miasma-terraform
|
@@ -0,0 +1,579 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'stringio'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module MiasmaTerraform
|
7
|
+
class Stack
|
8
|
+
|
9
|
+
@@running_actions = []
|
10
|
+
|
11
|
+
# Register action to be cleaned up at exit
|
12
|
+
#
|
13
|
+
# @param action [Action]
|
14
|
+
# @return [NilClass]
|
15
|
+
def self.register_action(action)
|
16
|
+
unless(@@running_actions.include?(action))
|
17
|
+
@@running_actions.push(action)
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Deregister action from at exit cleanup
|
23
|
+
#
|
24
|
+
# @param action [Action]
|
25
|
+
# @return [NilClass]
|
26
|
+
def self.deregister_action(action)
|
27
|
+
@@running_actions.delete(action)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Wait for all actions to complete
|
32
|
+
def self.cleanup_actions!
|
33
|
+
@@running_actions.map(&:complete!)
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Array<String>]
|
38
|
+
def self.list(container)
|
39
|
+
if(container.to_s.empty?)
|
40
|
+
raise ArgumentError.new 'Container directory must be set!'
|
41
|
+
end
|
42
|
+
if(File.directory?(container))
|
43
|
+
Dir.new(container).map do |entry|
|
44
|
+
next if entry.start_with?('.')
|
45
|
+
entry if File.directory?(File.join(container, entry))
|
46
|
+
end.compact
|
47
|
+
else
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Error < StandardError
|
53
|
+
class Busy < Error; end
|
54
|
+
class NotFound < Error; end
|
55
|
+
class CommandFailed < Error; end
|
56
|
+
class ValidateError < Error; end
|
57
|
+
end
|
58
|
+
|
59
|
+
REQUIRED_ATTRIBUTES = [:name, :container]
|
60
|
+
|
61
|
+
class Action
|
62
|
+
attr_reader :stdin, :waiter, :command, :options
|
63
|
+
|
64
|
+
# Create a new action to run
|
65
|
+
#
|
66
|
+
# @param command [String]
|
67
|
+
# @param opts [Hash]
|
68
|
+
# @return [self]
|
69
|
+
def initialize(command, opts={})
|
70
|
+
@command = command.dup.freeze
|
71
|
+
@options = opts.to_smash
|
72
|
+
@io_callbacks = []
|
73
|
+
@complete_callbacks = []
|
74
|
+
@start_callbacks = []
|
75
|
+
@cached_output = Smash.new(
|
76
|
+
:stdout => StringIO.new(''),
|
77
|
+
:stderr => StringIO.new('')
|
78
|
+
)
|
79
|
+
if(@options.delete(:auto_start))
|
80
|
+
start!
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Start the process
|
85
|
+
def start!
|
86
|
+
opts = Hash[@options.map{|k,v| [k.to_sym,v]}]
|
87
|
+
MiasmaTerraform::Stack.register_action(self)
|
88
|
+
@stdin, @stdout, @stderr, @waiter = Open3.popen3(@command, **opts)
|
89
|
+
@start_callbacks.each do |callback|
|
90
|
+
callback.call(self)
|
91
|
+
end
|
92
|
+
unless(@io_callbacks.empty? && @complete_callbacks.empty?)
|
93
|
+
manage_process!
|
94
|
+
end
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# Wait for the process to complete
|
99
|
+
#
|
100
|
+
# @return [Process::Status]
|
101
|
+
def complete!
|
102
|
+
start! unless waiter
|
103
|
+
if(@process_manager)
|
104
|
+
@process_manager.join
|
105
|
+
end
|
106
|
+
result = waiter.value
|
107
|
+
MiasmaTerraform::Stack.deregister_action(self)
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [IO] stderr stream
|
112
|
+
def stderr
|
113
|
+
if(@stderr == :managed_io)
|
114
|
+
@cached_output[:stderr]
|
115
|
+
else
|
116
|
+
@stderr
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [IO] stdout stream
|
121
|
+
def stdout
|
122
|
+
if(@stdout == :managed_io)
|
123
|
+
@cached_output[:stdout]
|
124
|
+
else
|
125
|
+
@stdout
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Register a block to be run when process output
|
130
|
+
# is received
|
131
|
+
#
|
132
|
+
# @yieldparam line [String] output line
|
133
|
+
# @yieldparam type [Symbol] output type (:stdout or :stderr)
|
134
|
+
def on_io(&block)
|
135
|
+
@io_callbacks << block
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# Register a block to be run when a process completes
|
140
|
+
#
|
141
|
+
# @yieldparam result [Process::Status]
|
142
|
+
# @yieldparam self [Action]
|
143
|
+
def on_complete(&block)
|
144
|
+
@complete_callbacks << block
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# Register a block to be run when a process starts
|
149
|
+
#
|
150
|
+
# @yieldparam self [Action]
|
151
|
+
def on_start(&block)
|
152
|
+
@start_callbacks << block
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
protected
|
157
|
+
|
158
|
+
# Start reader thread for handling managed process output
|
159
|
+
def manage_process!
|
160
|
+
unless(@process_manager)
|
161
|
+
unless(@io_callbacks.empty?)
|
162
|
+
io_stdout = @stdout
|
163
|
+
io_stderr = @stderr
|
164
|
+
@stdout = @stderr = :managed_io
|
165
|
+
end
|
166
|
+
@process_manager = Thread.new do
|
167
|
+
if(io_stdout && io_stderr)
|
168
|
+
while(waiter.alive?)
|
169
|
+
IO.select([io_stdout, io_stderr])
|
170
|
+
[io_stdout, io_stderr].each do |io|
|
171
|
+
begin
|
172
|
+
content = io.read_nonblock(102400)
|
173
|
+
type = io == io_stdout ? :stdout : :stderr
|
174
|
+
@cached_output[type] << content
|
175
|
+
content = content.split("\n")
|
176
|
+
@io_callbacks.each do |callback|
|
177
|
+
content.each do |line|
|
178
|
+
callback.call(line, type)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
rescue IO::WaitReadable, EOFError
|
182
|
+
# ignore
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
result = waiter.value
|
188
|
+
@complete_callbacks.each do |callback|
|
189
|
+
callback.call(result, self)
|
190
|
+
end
|
191
|
+
MiasmaTerraform::Stack.deregister_action(self)
|
192
|
+
result
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
attr_reader :actions
|
199
|
+
attr_reader :directory
|
200
|
+
attr_reader :container
|
201
|
+
attr_reader :name
|
202
|
+
attr_reader :bin
|
203
|
+
attr_reader :scrub_destroyed
|
204
|
+
|
205
|
+
def initialize(opts={})
|
206
|
+
@options = opts.to_smash
|
207
|
+
init!
|
208
|
+
@actions = []
|
209
|
+
@name = @options[:name]
|
210
|
+
@container = @options[:container]
|
211
|
+
@scrub_destroyed = @options.fetch(:scrub_destroyed, false)
|
212
|
+
@directory = File.join(container, name)
|
213
|
+
@bin = @options.fetch(:bin, 'terraform')
|
214
|
+
end
|
215
|
+
|
216
|
+
# @return [TrueClass, FalseClass] stack currently exists
|
217
|
+
def exists?
|
218
|
+
File.directory?(directory)
|
219
|
+
end
|
220
|
+
|
221
|
+
# @return [TrueClass, FalseClass] stack is currently active
|
222
|
+
def active?
|
223
|
+
actions.any? do |action|
|
224
|
+
action.waiter.alive?
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Save the TF stack
|
229
|
+
def save(opts={})
|
230
|
+
save_opts = opts.to_smash
|
231
|
+
type = exists? ? "update" : "create"
|
232
|
+
lock_stack
|
233
|
+
write_file(tf_path, save_opts[:template].to_json)
|
234
|
+
write_file(tfvars_path, save_opts[:parameters].to_json)
|
235
|
+
action = run_action('apply')
|
236
|
+
store_events(action)
|
237
|
+
action.on_start do |_|
|
238
|
+
update_info do |info|
|
239
|
+
info["state"] = "#{type}_in_progress"
|
240
|
+
info
|
241
|
+
end
|
242
|
+
end
|
243
|
+
action.on_complete do |status, this_action|
|
244
|
+
update_info do |info|
|
245
|
+
if(type == "create")
|
246
|
+
info["created_at"] = (Time.now.to_f * 1000).floor
|
247
|
+
end
|
248
|
+
info["updated_at"] = (Time.now.to_f * 1000).floor
|
249
|
+
info["state"] = status.success? ? "#{type}_complete" : "#{type}_failed"
|
250
|
+
info
|
251
|
+
end
|
252
|
+
unlock_stack
|
253
|
+
end
|
254
|
+
action.start!
|
255
|
+
true
|
256
|
+
end
|
257
|
+
|
258
|
+
# @return [Array<Hash>] resource list
|
259
|
+
def resources
|
260
|
+
must_exist do
|
261
|
+
if(has_state?)
|
262
|
+
action = run_action('state list', :auto_start)
|
263
|
+
# wait for action to complete
|
264
|
+
action.complete!
|
265
|
+
successful_action(action) do
|
266
|
+
resource_lines = action.stdout.read.split("\n").find_all do |line|
|
267
|
+
line.match(/^[^\s]/)
|
268
|
+
end
|
269
|
+
resource_lines.map do |line|
|
270
|
+
parts = line.split('.')
|
271
|
+
resource_info = Smash.new(
|
272
|
+
:type => parts[0],
|
273
|
+
:name => parts[1],
|
274
|
+
:status => 'UPDATE_COMPLETE'
|
275
|
+
)
|
276
|
+
action = run_action("state show #{line}", :auto_start)
|
277
|
+
action.complete!
|
278
|
+
successful_action(action) do
|
279
|
+
info = Smash.new
|
280
|
+
action.stdout.read.split("\n").each do |line|
|
281
|
+
parts = line.split('=').map(&:strip)
|
282
|
+
next if parts.size != 2
|
283
|
+
info[parts[0]] = parts[1]
|
284
|
+
end
|
285
|
+
resource_info[:physical_id] = info[:id] if info[:id]
|
286
|
+
end
|
287
|
+
resource_info
|
288
|
+
end
|
289
|
+
end
|
290
|
+
else
|
291
|
+
[]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# @return [Array<Hash>] events list
|
297
|
+
def events
|
298
|
+
must_exist do
|
299
|
+
load_info.fetch(:events, []).map do |item|
|
300
|
+
new_item = item.dup
|
301
|
+
parts = item[:resource_name].to_s.split('.')
|
302
|
+
new_item[:resource_name] = parts[1]
|
303
|
+
new_item[:resource_type] = parts[0]
|
304
|
+
new_item
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# @return [Hash] stack outputs
|
310
|
+
def outputs
|
311
|
+
must_exist do
|
312
|
+
if(has_state?)
|
313
|
+
action = run_action('output -json', :auto_start)
|
314
|
+
action.complete!
|
315
|
+
successful_action(action) do
|
316
|
+
result = JSON.parse(action.stdout.read).to_smash.map do |key, info|
|
317
|
+
[key, info[:value]]
|
318
|
+
end
|
319
|
+
Smash[result]
|
320
|
+
end
|
321
|
+
else
|
322
|
+
Smash.new
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# @return [String] current stack template
|
328
|
+
def template
|
329
|
+
must_exist do
|
330
|
+
if(File.exists?(tf_path))
|
331
|
+
File.read(tf_path)
|
332
|
+
else
|
333
|
+
"{}"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# @return [Hash] stack information
|
339
|
+
def info
|
340
|
+
must_exist do
|
341
|
+
stack_data = load_info
|
342
|
+
Smash.new(
|
343
|
+
:id => name,
|
344
|
+
:name => name,
|
345
|
+
:state => stack_data[:state].to_s,
|
346
|
+
:status => stack_data[:state].to_s.upcase,
|
347
|
+
:updated_time => stack_data[:updated_at],
|
348
|
+
:creation_time => stack_data[:created_at],
|
349
|
+
:outputs => outputs
|
350
|
+
)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def validate(template)
|
355
|
+
errors = []
|
356
|
+
root_path = Dir.mktmpdir('miasma-')
|
357
|
+
template_path = File.join(root_path, 'main.tf')
|
358
|
+
File.write(template_path, template)
|
359
|
+
action = run_action('validate')
|
360
|
+
action.options[:chdir] = root_path
|
361
|
+
action.on_io do |line, type|
|
362
|
+
if(line.start_with?('*'))
|
363
|
+
errors << line
|
364
|
+
end
|
365
|
+
end.on_complete do |_|
|
366
|
+
FileUtils.rm_rf(root_path)
|
367
|
+
end
|
368
|
+
action.complete!
|
369
|
+
errors
|
370
|
+
end
|
371
|
+
|
372
|
+
# @return [TrueClass] destroy this stack
|
373
|
+
def destroy!
|
374
|
+
must_exist do
|
375
|
+
lock_stack
|
376
|
+
action = run_action('destroy -force')
|
377
|
+
action.on_start do |_|
|
378
|
+
update_info do |info|
|
379
|
+
info[:state] = "delete_in_progress"
|
380
|
+
info
|
381
|
+
end
|
382
|
+
end
|
383
|
+
action.on_complete do |*_|
|
384
|
+
unlock_stack
|
385
|
+
end
|
386
|
+
action.on_complete do |result, _|
|
387
|
+
unless(result.success?)
|
388
|
+
update_info do |info|
|
389
|
+
info[:state] = "delete_failed"
|
390
|
+
info
|
391
|
+
end
|
392
|
+
else
|
393
|
+
update_info do |info|
|
394
|
+
info[:state] = "delete_complete"
|
395
|
+
info
|
396
|
+
end
|
397
|
+
FileUtils.rm_rf(directory) if scrub_destroyed
|
398
|
+
end
|
399
|
+
end
|
400
|
+
action.start!
|
401
|
+
end
|
402
|
+
true
|
403
|
+
end
|
404
|
+
|
405
|
+
protected
|
406
|
+
|
407
|
+
# Start running a terraform process
|
408
|
+
def run_action(cmd, auto_start=false)
|
409
|
+
action = Action.new("#{bin} #{cmd} -no-color", :chdir => directory)
|
410
|
+
action.on_start do |this_action|
|
411
|
+
actions << this_action
|
412
|
+
end
|
413
|
+
action.on_complete do |_, this_action|
|
414
|
+
actions.delete(this_action)
|
415
|
+
end
|
416
|
+
action.start! if auto_start
|
417
|
+
action
|
418
|
+
end
|
419
|
+
|
420
|
+
# Validate stack exists before running block
|
421
|
+
def must_exist(lock=false)
|
422
|
+
if(exists?)
|
423
|
+
if(lock)
|
424
|
+
lock_stack do
|
425
|
+
yield
|
426
|
+
end
|
427
|
+
else
|
428
|
+
yield
|
429
|
+
end
|
430
|
+
else
|
431
|
+
raise Error::NotFound.new "Stack does not exist `#{name}`"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Lock stack and run block
|
436
|
+
def lock_stack
|
437
|
+
FileUtils.mkdir_p(directory)
|
438
|
+
@lock_file = File.open(lock_path, File::RDWR|File::CREAT)
|
439
|
+
if(@lock_file.flock(File::LOCK_EX | File::LOCK_NB))
|
440
|
+
if(block_given?)
|
441
|
+
result = yield
|
442
|
+
@lock_file.flock(File::LOCK_UN)
|
443
|
+
@lock_file = nil
|
444
|
+
result
|
445
|
+
else
|
446
|
+
true
|
447
|
+
end
|
448
|
+
else
|
449
|
+
raise Error::Busy.new "Failed to aquire process lock for `#{name}`. Stack busy."
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# Unlock stack
|
454
|
+
def unlock_stack
|
455
|
+
if(@lock_file)
|
456
|
+
@lock_file.flock(File::LOCK_UN)
|
457
|
+
@lock_file = nil
|
458
|
+
true
|
459
|
+
else
|
460
|
+
false
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
# @return [String] path to template file
|
465
|
+
def tf_path
|
466
|
+
File.join(directory, 'main.tf')
|
467
|
+
end
|
468
|
+
|
469
|
+
# @return [String] path to variables file
|
470
|
+
def tfvars_path
|
471
|
+
File.join(directory, 'terraform.tfvars')
|
472
|
+
end
|
473
|
+
|
474
|
+
# @return [String] path to internal info file
|
475
|
+
def info_path
|
476
|
+
File.join(directory, '.info.json')
|
477
|
+
end
|
478
|
+
|
479
|
+
# @return [String] path to state file
|
480
|
+
def tfstate_path
|
481
|
+
File.join(directory, 'terraform.tfstate')
|
482
|
+
end
|
483
|
+
|
484
|
+
# @return [TrueClass, FalseClass] stack has state
|
485
|
+
def has_state?
|
486
|
+
File.exists?(tfstate_path)
|
487
|
+
end
|
488
|
+
|
489
|
+
# @return [String] path to lock file
|
490
|
+
def lock_path
|
491
|
+
File.join(directory, '.lck')
|
492
|
+
end
|
493
|
+
|
494
|
+
# @return [Smash] stack info
|
495
|
+
def load_info
|
496
|
+
if(File.exists?(info_path))
|
497
|
+
result = JSON.parse(File.read(info_path)).to_smash
|
498
|
+
else
|
499
|
+
result = Smash.new
|
500
|
+
end
|
501
|
+
result[:created_at] = (Time.now.to_f * 1000).floor unless result[:created_at]
|
502
|
+
result[:state] = 'unknown' unless result[:state]
|
503
|
+
result
|
504
|
+
end
|
505
|
+
|
506
|
+
# @return [TrueClass]
|
507
|
+
def update_info
|
508
|
+
result = yield(load_info)
|
509
|
+
write_file(info_path, result.to_json)
|
510
|
+
true
|
511
|
+
end
|
512
|
+
|
513
|
+
# Raise exception if action was not completed successfully
|
514
|
+
def successful_action(action)
|
515
|
+
status = action.complete!
|
516
|
+
unless(status.success?)
|
517
|
+
raise Error::CommandFailed.new "Command failed `#{action.command}` - #{action.stderr.read}"
|
518
|
+
else
|
519
|
+
yield
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
# Store stack events generated by action
|
524
|
+
#
|
525
|
+
# @param action [Action]
|
526
|
+
def store_events(action)
|
527
|
+
action.on_io do |line, type|
|
528
|
+
result = line.match(/^(\*\s+)?(?<name>[^\s]+): (?<status>.+)$/)
|
529
|
+
if(result)
|
530
|
+
resource_name = result["name"]
|
531
|
+
resource_status = result["status"]
|
532
|
+
event = Smash.new(
|
533
|
+
:timestamp => (Time.now.to_f * 1000).floor,
|
534
|
+
:resource_name => resource_name,
|
535
|
+
:resource_status => resource_status,
|
536
|
+
:id => SecureRandom.uuid
|
537
|
+
)
|
538
|
+
update_info do |info|
|
539
|
+
info[:events] ||= []
|
540
|
+
info[:events].unshift(event)
|
541
|
+
info
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# Validate initialization
|
548
|
+
def init!
|
549
|
+
missing_attrs = REQUIRED_ATTRIBUTES.find_all do |key|
|
550
|
+
!@options[key]
|
551
|
+
end
|
552
|
+
unless(missing_attrs.empty?)
|
553
|
+
raise ArgumentError.new("Missing required attributes: #{missing_attrs.sort}")
|
554
|
+
end
|
555
|
+
# TODO: Add tf bin check
|
556
|
+
end
|
557
|
+
|
558
|
+
# File write helper that proxies via temporary file
|
559
|
+
# to prevent corrupted writes on unexpected interrupt
|
560
|
+
#
|
561
|
+
# @param path [String] path to file
|
562
|
+
# @param contents [String] contents of file
|
563
|
+
# @return [TrueClass]
|
564
|
+
def write_file(path, contents=nil)
|
565
|
+
tmp_file = Tempfile.new('miasma')
|
566
|
+
yield(tmp_file) if block_given?
|
567
|
+
tmp_file.print(contents.to_s) if contents
|
568
|
+
tmp_file.close
|
569
|
+
FileUtils.mv(tmp_file.path, path)
|
570
|
+
true
|
571
|
+
end
|
572
|
+
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# TODO: Don't be lazy and remove this setting
|
577
|
+
Thread.abort_on_exception = true
|
578
|
+
|
579
|
+
Kernel.at_exit{ MiasmaTerraform::Stack.cleanup_actions! }
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
require 'miasma-terraform'
|
3
|
+
|
4
|
+
module Miasma
|
5
|
+
module Contrib
|
6
|
+
|
7
|
+
# Terraform API core helper
|
8
|
+
class TerraformApiCore
|
9
|
+
|
10
|
+
# Common API methods
|
11
|
+
module ApiCommon
|
12
|
+
|
13
|
+
# Set attributes into model
|
14
|
+
#
|
15
|
+
# @param klass [Class]
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
attribute :terraform_driver, String, :required => true,
|
19
|
+
:allowed_values => ['atlas', 'boule', 'local'], :default => 'local',
|
20
|
+
:coerce => lambda{|v| v.to_s }
|
21
|
+
# Attributes required for Atlas driver
|
22
|
+
attribute :terraform_atlas_endpoint, String
|
23
|
+
attribute :terraform_atlas_token, String
|
24
|
+
# Attributes required for Boule driver
|
25
|
+
attribute :terraform_boule_endpoint, String
|
26
|
+
# Attributes required for local driver
|
27
|
+
attribute :terraform_local_directory, String
|
28
|
+
attribute :terraform_local_scrub_destroyed, [TrueClass, FalseClass], :default => false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def custom_setup(creds)
|
33
|
+
begin
|
34
|
+
driver_module = Miasma::Models::Orchestration::Terraform.const_get(
|
35
|
+
Bogo::Utility.camel(creds[:terraform_driver].to_s)
|
36
|
+
)
|
37
|
+
extend driver_module
|
38
|
+
rescue NameError
|
39
|
+
raise NotImplementedError.new "Requested driver not implemented `#{creds[:terraform_driver]}`"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def endpoint
|
44
|
+
terraform_boule_endpoint
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Models::Orchestration.autoload :Terraform, 'miasma/contrib/terraform/orchestration'
|
52
|
+
end
|
@@ -0,0 +1,441 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Orchestration
|
6
|
+
class Terraform < Orchestration
|
7
|
+
|
8
|
+
include Miasma::Contrib::TerraformApiCore::ApiCommon
|
9
|
+
|
10
|
+
module Local
|
11
|
+
|
12
|
+
# Generate wrapper stack
|
13
|
+
def terraform_stack(stack)
|
14
|
+
if(terraform_local_directory.to_s.empty?)
|
15
|
+
raise ArgumentError.new 'Attribute `terraform_local_directory` must be set for local mode usage'
|
16
|
+
end
|
17
|
+
tf_stack = memoize(stack.name, :direct) do
|
18
|
+
MiasmaTerraform::Stack.new(
|
19
|
+
:name => stack.name,
|
20
|
+
:container => terraform_local_directory,
|
21
|
+
:scrub_destroyed => terraform_local_scrub_destroyed
|
22
|
+
)
|
23
|
+
end
|
24
|
+
if(block_given?)
|
25
|
+
begin
|
26
|
+
yield(tf_stack)
|
27
|
+
rescue MiasmaTerraform::Stack::Error::NotFound
|
28
|
+
raise Miasma::Error::ApiError::RequestError.new(
|
29
|
+
"Failed to locate stack `#{stack.name}`",
|
30
|
+
:response => OpenStruct.new(:code => 404)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
tf_stack
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Save the stack
|
39
|
+
#
|
40
|
+
# @param stack [Models::Orchestration::Stack]
|
41
|
+
# @return [Models::Orchestration::Stack]
|
42
|
+
def stack_save(stack)
|
43
|
+
tf_stack = nil
|
44
|
+
begin
|
45
|
+
tf_stack = terraform_stack(stack) do |tf|
|
46
|
+
tf.info
|
47
|
+
tf
|
48
|
+
end
|
49
|
+
rescue Miasma::Error::ApiError::RequestError
|
50
|
+
if(stack.persisted?)
|
51
|
+
raise
|
52
|
+
else
|
53
|
+
tf_stack = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if(!stack.persisted? && tf_stack)
|
57
|
+
raise Miasma::Error::ApiError::RequestError.new(
|
58
|
+
"Stack already exists `#{stack.name}`",
|
59
|
+
:response => OpenStruct.new(:code => 405)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
tf_stack = terraform_stack(stack) unless tf_stack
|
63
|
+
tf_stack.save(
|
64
|
+
:template => stack.template,
|
65
|
+
:parameters => stack.parameters || {}
|
66
|
+
)
|
67
|
+
stack.id = stack.name
|
68
|
+
stack.valid_state
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reload the stack data from the API
|
72
|
+
#
|
73
|
+
# @param stack [Models::Orchestration::Stack]
|
74
|
+
# @return [Models::Orchestration::Stack]
|
75
|
+
def stack_reload(stack)
|
76
|
+
if(stack.persisted?)
|
77
|
+
terraform_stack(stack) do |tf_stack|
|
78
|
+
s = tf_stack.info
|
79
|
+
stack.load_data(
|
80
|
+
:id => s[:id],
|
81
|
+
:created => s[:creation_time].to_s.empty? ? nil : Time.at(s[:creation_time].to_i / 1000.0),
|
82
|
+
:description => s[:description],
|
83
|
+
:name => s[:name],
|
84
|
+
:state => s[:status].downcase.to_sym,
|
85
|
+
:status => s[:status],
|
86
|
+
:status_reason => s[:stack_status_reason],
|
87
|
+
:updated => s[:updated_time].to_s.empty? ? nil : Time.at(s[:updated_time].to_i / 1000.0),
|
88
|
+
:outputs => s[:outputs].map{|k,v| {:key => k, :value => v}}
|
89
|
+
).valid_state
|
90
|
+
end
|
91
|
+
end
|
92
|
+
stack
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delete the stack
|
96
|
+
#
|
97
|
+
# @param stack [Models::Orchestration::Stack]
|
98
|
+
# @return [TrueClass, FalseClass]
|
99
|
+
def stack_destroy(stack)
|
100
|
+
if(stack.persisted?)
|
101
|
+
terraform_stack(stack).destroy!
|
102
|
+
true
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fetch stack template
|
109
|
+
#
|
110
|
+
# @param stack [Stack]
|
111
|
+
# @return [Smash] stack template
|
112
|
+
def stack_template_load(stack)
|
113
|
+
if(stack.persisted?)
|
114
|
+
JSON.load(terraform_stack(stack).template).to_smash
|
115
|
+
else
|
116
|
+
Smash.new
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Validate stack template
|
121
|
+
#
|
122
|
+
# @param stack [Stack]
|
123
|
+
# @return [NilClass, String] nil if valid, string error message if invalid
|
124
|
+
def stack_template_validate(stack)
|
125
|
+
begin
|
126
|
+
terraform_stack(stack).validate(stack.template)
|
127
|
+
nil
|
128
|
+
rescue MiasmaTerraform::Error::Validation => e
|
129
|
+
MultiJson.load(e.response.body.to_s).to_smash.get(:error, :message)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return all stacks
|
134
|
+
#
|
135
|
+
# @param options [Hash] filter
|
136
|
+
# @return [Array<Models::Orchestration::Stack>]
|
137
|
+
# @todo check if we need any mappings on state set
|
138
|
+
def stack_all(options={})
|
139
|
+
MiasmaTerraform::Stack.list(terraform_local_directory).map do |stack_name|
|
140
|
+
s = terraform_stack(Stack.new(self, :name => stack_name)).info
|
141
|
+
Stack.new(
|
142
|
+
self,
|
143
|
+
:id => s[:id],
|
144
|
+
:created => s[:creation_time].to_s.empty? ? nil : Time.at(s[:creation_time].to_i / 1000.0),
|
145
|
+
:description => s[:description],
|
146
|
+
:name => s[:name],
|
147
|
+
:state => s[:status].downcase.to_sym,
|
148
|
+
:status => s[:status],
|
149
|
+
:status_reason => s[:stack_status_reason],
|
150
|
+
:updated => s[:updated_time].to_s.empty? ? nil : Time.at(s[:updated_time].to_i / 1000.0)
|
151
|
+
).valid_state
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return all resources for stack
|
156
|
+
#
|
157
|
+
# @param stack [Models::Orchestration::Stack]
|
158
|
+
# @return [Array<Models::Orchestration::Stack::Resource>]
|
159
|
+
def resource_all(stack)
|
160
|
+
terraform_stack(stack) do |tf_stack|
|
161
|
+
tf_stack.resources.map do |resource|
|
162
|
+
Stack::Resource.new(
|
163
|
+
stack,
|
164
|
+
:id => resource[:physical_id],
|
165
|
+
:name => resource[:name],
|
166
|
+
:type => resource[:type],
|
167
|
+
:logical_id => resource[:name],
|
168
|
+
:state => resource[:status].downcase.to_sym,
|
169
|
+
:status => resource[:status],
|
170
|
+
:status_reason => resource[:resource_status_reason],
|
171
|
+
:updated => resource[:updated_time].to_s.empty? ? Time.now : Time.parse(resource[:updated_time])
|
172
|
+
).valid_state
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Reload the stack resource data from the API
|
178
|
+
#
|
179
|
+
# @param resource [Models::Orchestration::Stack::Resource]
|
180
|
+
# @return [Models::Orchestration::Resource]
|
181
|
+
def resource_reload(resource)
|
182
|
+
resource.stack.resources.reload
|
183
|
+
resource.stack.resources.get(resource.id)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return all events for stack
|
187
|
+
#
|
188
|
+
# @param stack [Models::Orchestration::Stack]
|
189
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
190
|
+
def event_all(stack, marker = nil)
|
191
|
+
params = marker ? {:marker => marker} : {}
|
192
|
+
terraform_stack(stack) do |tf_stack|
|
193
|
+
tf_stack.events.map do |event|
|
194
|
+
Stack::Event.new(
|
195
|
+
stack,
|
196
|
+
:id => event[:id],
|
197
|
+
:resource_id => event[:physical_resource_id],
|
198
|
+
:resource_name => event[:resource_name],
|
199
|
+
:resource_logical_id => event[:resource_name],
|
200
|
+
:resource_state => event[:resource_status].downcase.to_sym,
|
201
|
+
:resource_status => event[:resource_status],
|
202
|
+
:resource_status_reason => event[:resource_status_reason],
|
203
|
+
:time => Time.at(event[:timestamp] / 1000.0)
|
204
|
+
).valid_state
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return all new events for event collection
|
210
|
+
#
|
211
|
+
# @param events [Models::Orchestration::Stack::Events]
|
212
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
213
|
+
def event_all_new(events)
|
214
|
+
event_all(events.stack, events.all.first.id)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Reload the stack event data from the API
|
218
|
+
#
|
219
|
+
# @param resource [Models::Orchestration::Stack::Event]
|
220
|
+
# @return [Models::Orchestration::Event]
|
221
|
+
def event_reload(event)
|
222
|
+
event.stack.events.reload
|
223
|
+
event.stack.events.get(event.id)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
module Boule
|
228
|
+
# Save the stack
|
229
|
+
#
|
230
|
+
# @param stack [Models::Orchestration::Stack]
|
231
|
+
# @return [Models::Orchestration::Stack]
|
232
|
+
def stack_save(stack)
|
233
|
+
if(stack.persisted?)
|
234
|
+
stack.load_data(stack.attributes)
|
235
|
+
result = request(
|
236
|
+
:method => :put,
|
237
|
+
:path => "/terraform/stack/#{stack.name}",
|
238
|
+
:json => {
|
239
|
+
:template => MultiJson.dump(stack.template),
|
240
|
+
:parameters => stack.parameters || {}
|
241
|
+
}
|
242
|
+
)
|
243
|
+
stack.valid_state
|
244
|
+
else
|
245
|
+
stack.load_data(stack.attributes)
|
246
|
+
result = request(
|
247
|
+
:method => :post,
|
248
|
+
:path => "/terraform/stack/#{stack.name}",
|
249
|
+
:json => {
|
250
|
+
:template => MultiJson.dump(stack.template),
|
251
|
+
:parameters => stack.parameters || {}
|
252
|
+
}
|
253
|
+
)
|
254
|
+
stack.id = result.get(:body, :stack, :id)
|
255
|
+
stack.valid_state
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Reload the stack data from the API
|
260
|
+
#
|
261
|
+
# @param stack [Models::Orchestration::Stack]
|
262
|
+
# @return [Models::Orchestration::Stack]
|
263
|
+
def stack_reload(stack)
|
264
|
+
if(stack.persisted?)
|
265
|
+
result = request(
|
266
|
+
:method => :get,
|
267
|
+
:path => "/terraform/stack/#{stack.name}"
|
268
|
+
)
|
269
|
+
s = result.get(:body, :stack)
|
270
|
+
stack.load_data(
|
271
|
+
:id => s[:id],
|
272
|
+
:created => s[:creation_time].to_s.empty? ? nil : Time.at(s[:creation_time].to_i / 1000.0),
|
273
|
+
:description => s[:description],
|
274
|
+
:name => s[:name],
|
275
|
+
:state => s[:status].downcase.to_sym,
|
276
|
+
:status => s[:status],
|
277
|
+
:status_reason => s[:stack_status_reason],
|
278
|
+
:updated => s[:updated_time].to_s.empty? ? nil : Time.at(s[:updated_time].to_i / 1000.0),
|
279
|
+
:outputs => s[:outputs].map{|k,v| {:key => k, :value => v}}
|
280
|
+
).valid_state
|
281
|
+
end
|
282
|
+
stack
|
283
|
+
end
|
284
|
+
|
285
|
+
# Delete the stack
|
286
|
+
#
|
287
|
+
# @param stack [Models::Orchestration::Stack]
|
288
|
+
# @return [TrueClass, FalseClass]
|
289
|
+
def stack_destroy(stack)
|
290
|
+
if(stack.persisted?)
|
291
|
+
request(
|
292
|
+
:method => :delete,
|
293
|
+
:path => "/terraform/stack/#{stack.name}"
|
294
|
+
)
|
295
|
+
true
|
296
|
+
else
|
297
|
+
false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Fetch stack template
|
302
|
+
#
|
303
|
+
# @param stack [Stack]
|
304
|
+
# @return [Smash] stack template
|
305
|
+
def stack_template_load(stack)
|
306
|
+
if(stack.persisted?)
|
307
|
+
result = request(
|
308
|
+
:method => :get,
|
309
|
+
:path => "/terraform/template/#{stack.name}"
|
310
|
+
)
|
311
|
+
result.fetch(:body, Smash.new)
|
312
|
+
else
|
313
|
+
Smash.new
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Validate stack template
|
318
|
+
#
|
319
|
+
# @param stack [Stack]
|
320
|
+
# @return [NilClass, String] nil if valid, string error message if invalid
|
321
|
+
def stack_template_validate(stack)
|
322
|
+
begin
|
323
|
+
result = request(
|
324
|
+
:method => :post,
|
325
|
+
:path => '/terraform/validate',
|
326
|
+
:json => Smash.new(
|
327
|
+
:template => stack.template
|
328
|
+
)
|
329
|
+
)
|
330
|
+
nil
|
331
|
+
rescue Error::ApiError::RequestError => e
|
332
|
+
MultiJson.load(e.response.body.to_s).to_smash.get(:error, :message)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Return all stacks
|
337
|
+
#
|
338
|
+
# @param options [Hash] filter
|
339
|
+
# @return [Array<Models::Orchestration::Stack>]
|
340
|
+
# @todo check if we need any mappings on state set
|
341
|
+
def stack_all(options={})
|
342
|
+
result = request(
|
343
|
+
:method => :get,
|
344
|
+
:path => '/terraform/stacks'
|
345
|
+
)
|
346
|
+
result.fetch(:body, :stacks, []).map do |s|
|
347
|
+
Stack.new(
|
348
|
+
self,
|
349
|
+
:id => s[:id],
|
350
|
+
:created => s[:creation_time].to_s.empty? ? nil : Time.at(s[:creation_time].to_i / 1000.0),
|
351
|
+
:description => s[:description],
|
352
|
+
:name => s[:name],
|
353
|
+
:state => s[:status].downcase.to_sym,
|
354
|
+
:status => s[:status],
|
355
|
+
:status_reason => s[:stack_status_reason],
|
356
|
+
:updated => s[:updated_time].to_s.empty? ? nil : Time.at(s[:updated_time].to_i / 1000.0)
|
357
|
+
).valid_state
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Return all resources for stack
|
362
|
+
#
|
363
|
+
# @param stack [Models::Orchestration::Stack]
|
364
|
+
# @return [Array<Models::Orchestration::Stack::Resource>]
|
365
|
+
def resource_all(stack)
|
366
|
+
result = request(
|
367
|
+
:method => :get,
|
368
|
+
:path => "/terraform/resources/#{stack.name}"
|
369
|
+
)
|
370
|
+
result.fetch(:body, :resources, []).map do |resource|
|
371
|
+
Stack::Resource.new(
|
372
|
+
stack,
|
373
|
+
:id => resource[:physical_id],
|
374
|
+
:name => resource[:name],
|
375
|
+
:type => resource[:type],
|
376
|
+
:logical_id => resource[:name],
|
377
|
+
:state => resource[:status].downcase.to_sym,
|
378
|
+
:status => resource[:status],
|
379
|
+
:status_reason => resource[:resource_status_reason],
|
380
|
+
:updated => resource[:updated_time].to_s.empty? ? Time.now : Time.parse(resource[:updated_time])
|
381
|
+
).valid_state
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Reload the stack resource data from the API
|
386
|
+
#
|
387
|
+
# @param resource [Models::Orchestration::Stack::Resource]
|
388
|
+
# @return [Models::Orchestration::Resource]
|
389
|
+
def resource_reload(resource)
|
390
|
+
resource.stack.resources.reload
|
391
|
+
resource.stack.resources.get(resource.id)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Return all events for stack
|
395
|
+
#
|
396
|
+
# @param stack [Models::Orchestration::Stack]
|
397
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
398
|
+
def event_all(stack, marker = nil)
|
399
|
+
params = marker ? {:marker => marker} : {}
|
400
|
+
result = request(
|
401
|
+
:path => "/terraform/events/#{stack.name}",
|
402
|
+
:method => :get,
|
403
|
+
:params => params
|
404
|
+
)
|
405
|
+
result.fetch(:body, :events, []).map do |event|
|
406
|
+
Stack::Event.new(
|
407
|
+
stack,
|
408
|
+
:id => event[:id],
|
409
|
+
:resource_id => event[:physical_resource_id],
|
410
|
+
:resource_name => event[:resource_name],
|
411
|
+
:resource_logical_id => event[:resource_name],
|
412
|
+
:resource_state => event[:resource_status].downcase.to_sym,
|
413
|
+
:resource_status => event[:resource_status],
|
414
|
+
:resource_status_reason => event[:resource_status_reason],
|
415
|
+
:time => Time.at(event[:timestamp] / 1000.0)
|
416
|
+
).valid_state
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Return all new events for event collection
|
421
|
+
#
|
422
|
+
# @param events [Models::Orchestration::Stack::Events]
|
423
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
424
|
+
def event_all_new(events)
|
425
|
+
event_all(events.stack, events.all.first.id)
|
426
|
+
end
|
427
|
+
|
428
|
+
# Reload the stack event data from the API
|
429
|
+
#
|
430
|
+
# @param resource [Models::Orchestration::Stack::Event]
|
431
|
+
# @return [Models::Orchestration::Event]
|
432
|
+
def event_reload(event)
|
433
|
+
event.stack.events.reload
|
434
|
+
event.stack.events.get(event.id)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'miasma-terraform/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'miasma-terraform'
|
5
|
+
s.version = MiasmaTerraform::VERSION.version
|
6
|
+
s.summary = 'Smoggy Terraform API'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'code@chrisroberts.org'
|
9
|
+
s.homepage = 'https://github.com/miasma-rb/miasma-terraform'
|
10
|
+
s.description = 'Smoggy Terraform API'
|
11
|
+
s.license = 'Apache 2.0'
|
12
|
+
s.require_path = 'lib'
|
13
|
+
s.add_development_dependency 'pry'
|
14
|
+
s.add_development_dependency 'minitest'
|
15
|
+
s.files = Dir['lib/**/*'] + %w(miasma-terraform.gemspec README.md CHANGELOG.md LICENSE)
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: miasma-terraform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pry
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Smoggy Terraform API
|
42
|
+
email: code@chrisroberts.org
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- CHANGELOG.md
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- lib/miasma-terraform.rb
|
51
|
+
- lib/miasma-terraform/stack.rb
|
52
|
+
- lib/miasma-terraform/version.rb
|
53
|
+
- lib/miasma/contrib/terraform.rb
|
54
|
+
- lib/miasma/contrib/terraform/orchestration.rb
|
55
|
+
- miasma-terraform.gemspec
|
56
|
+
homepage: https://github.com/miasma-rb/miasma-terraform
|
57
|
+
licenses:
|
58
|
+
- Apache 2.0
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.4.8
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Smoggy Terraform API
|
80
|
+
test_files: []
|