kumogata2 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/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/kumogata2 +42 -0
- data/kumogata2.gemspec +33 -0
- data/lib/kumogata2/cli/option_parser.rb +260 -0
- data/lib/kumogata2/client.rb +587 -0
- data/lib/kumogata2/ext/coderay_ext.rb +24 -0
- data/lib/kumogata2/ext/string_ext.rb +36 -0
- data/lib/kumogata2/logger.rb +28 -0
- data/lib/kumogata2/plugin/json.rb +15 -0
- data/lib/kumogata2/plugin.rb +48 -0
- data/lib/kumogata2/utils.rb +36 -0
- data/lib/kumogata2/version.rb +3 -0
- data/lib/kumogata2.rb +21 -0
- metadata +206 -0
@@ -0,0 +1,587 @@
|
|
1
|
+
class Kumogata2::Client
|
2
|
+
include Kumogata2::Logger::Helper
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@options = options.kind_of?(Hashie::Mash) ? options : Hashie::Mash.new(options)
|
6
|
+
@client = Aws::CloudFormation::Client.new
|
7
|
+
@resource = Aws::CloudFormation::Resource.new(client: @client)
|
8
|
+
@plugin_by_ext = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def create(path_or_url, stack_name = nil)
|
12
|
+
validate_stack_name(stack_name) if stack_name
|
13
|
+
template = open_template(path_or_url)
|
14
|
+
update_deletion_policy(template, delete_stack: !stack_name)
|
15
|
+
|
16
|
+
outputs = create_stack(template, stack_name)
|
17
|
+
|
18
|
+
unless @options.detach?
|
19
|
+
post_process(path_or_url, outputs)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def update(path_or_url, stack_name)
|
24
|
+
validate_stack_name(stack_name)
|
25
|
+
template = open_template(path_or_url)
|
26
|
+
update_deletion_policy(template, update_metadate: true)
|
27
|
+
|
28
|
+
outputs = update_stack(template, stack_name)
|
29
|
+
|
30
|
+
unless @options.detach?
|
31
|
+
post_process(path_or_url, outputs)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(stack_name)
|
36
|
+
validate_stack_name(stack_name)
|
37
|
+
@resource.stack(stack_name).stack_status
|
38
|
+
|
39
|
+
if @options.force? or agree("Are you sure you want to delete `#{stack_name}`? ".yellow)
|
40
|
+
delete_stack(stack_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate(path_or_url)
|
45
|
+
template = open_template(path_or_url)
|
46
|
+
validate_template(template)
|
47
|
+
end
|
48
|
+
|
49
|
+
def list(stack_name = nil)
|
50
|
+
validate_stack_name(stack_name) if stack_name
|
51
|
+
stacks = describe_stacks(stack_name)
|
52
|
+
JSON.pretty_generate(stacks).colorize_as(:json)
|
53
|
+
end
|
54
|
+
|
55
|
+
def export(stack_name)
|
56
|
+
validate_stack_name(stack_name)
|
57
|
+
template = export_template(stack_name)
|
58
|
+
convert0(template)
|
59
|
+
end
|
60
|
+
|
61
|
+
def convert(path_or_url)
|
62
|
+
template = open_template(path_or_url)
|
63
|
+
convert0(template)
|
64
|
+
end
|
65
|
+
|
66
|
+
def diff(path_or_url1, path_or_url2)
|
67
|
+
templates = [path_or_url1, path_or_url2].map do |path_or_url|
|
68
|
+
template = nil
|
69
|
+
|
70
|
+
if path_or_url =~ %r|\Astack://(.*)|
|
71
|
+
stack_name = $1 || ''
|
72
|
+
validate_stack_name(stack_name)
|
73
|
+
template = export_template(stack_name)
|
74
|
+
else
|
75
|
+
template = open_template(path_or_url)
|
76
|
+
end
|
77
|
+
|
78
|
+
JSON.pretty_generate(template)
|
79
|
+
end
|
80
|
+
|
81
|
+
diff_opts = @options.ignore_all_space? ? '-uw' : '-u'
|
82
|
+
opts = {:include_diff_info => true, :diff => diff_opts}
|
83
|
+
diff = Diffy::Diff.new(*templates, opts).to_s
|
84
|
+
|
85
|
+
diff.sub(/^(\e\[\d+m)?\-\-\-(\s+)(\S+)/m) { "#{$1}---#{$2}#{path_or_url1}"}
|
86
|
+
.sub(/^(\e\[\d+m)?\+\+\+(\s+)(\S+)/m) { "#{$1}+++#{$2}#{path_or_url2}"}
|
87
|
+
end
|
88
|
+
|
89
|
+
def dry_run(path_or_url, stack_name = nil)
|
90
|
+
validate_stack_name(stack_name) if stack_name
|
91
|
+
template = open_template(path_or_url)
|
92
|
+
update_deletion_policy(template, delete_stack: !stack_name)
|
93
|
+
changes = show_change_set(template, stack_name)
|
94
|
+
changes = JSON.pretty_generate(changes).colorize_as(:json) if changes
|
95
|
+
changes
|
96
|
+
end
|
97
|
+
|
98
|
+
def show_events(stack_name)
|
99
|
+
validate_stack_name(stack_name)
|
100
|
+
events = describe_events(stack_name)
|
101
|
+
JSON.pretty_generate(events).colorize_as(:json)
|
102
|
+
end
|
103
|
+
|
104
|
+
def show_outputs(stack_name)
|
105
|
+
validate_stack_name(stack_name)
|
106
|
+
outputs = describe_outputs(stack_name)
|
107
|
+
JSON.pretty_generate(outputs).colorize_as(:json)
|
108
|
+
end
|
109
|
+
|
110
|
+
def show_resources(stack_name)
|
111
|
+
validate_stack_name(stack_name)
|
112
|
+
resources = describe_resources(stack_name)
|
113
|
+
JSON.pretty_generate(resources).colorize_as(:json)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def create_stack(template, stack_name)
|
119
|
+
stack_will_be_deleted = !stack_name
|
120
|
+
|
121
|
+
unless stack_name
|
122
|
+
stack_name = random_stack_name
|
123
|
+
end
|
124
|
+
|
125
|
+
log(:info, "Creating stack: #{stack_name}", color: :cyan)
|
126
|
+
|
127
|
+
params = {
|
128
|
+
stack_name: stack_name,
|
129
|
+
template_body: template.to_json,
|
130
|
+
parameters: parameters_array,
|
131
|
+
}
|
132
|
+
|
133
|
+
set_api_params(params,
|
134
|
+
:disable_rollback,
|
135
|
+
:timeout_in_minutes,
|
136
|
+
:notification_arns,
|
137
|
+
:capabilities,
|
138
|
+
:resource_types,
|
139
|
+
:on_failure,
|
140
|
+
:stack_policy_body,
|
141
|
+
:stack_policy_url)
|
142
|
+
|
143
|
+
stack = @resource.create_stack(params)
|
144
|
+
|
145
|
+
return if @options.detach?
|
146
|
+
|
147
|
+
completed = wait(stack, 'CREATE_COMPLETE')
|
148
|
+
|
149
|
+
unless completed
|
150
|
+
raise_stack_error!(stack, 'Create failed')
|
151
|
+
end
|
152
|
+
|
153
|
+
outputs = outputs_for(stack)
|
154
|
+
summaries = resource_summaries_for(stack)
|
155
|
+
|
156
|
+
if stack_will_be_deleted
|
157
|
+
delete_stack(stack_name)
|
158
|
+
end
|
159
|
+
|
160
|
+
output_result(stack_name, outputs, summaries)
|
161
|
+
|
162
|
+
outputs
|
163
|
+
end
|
164
|
+
|
165
|
+
def update_stack(template, stack_name)
|
166
|
+
stack = @resource.stack(stack_name)
|
167
|
+
stack.stack_status
|
168
|
+
|
169
|
+
log(:info, "Updating stack: #{stack_name}", color: :green)
|
170
|
+
|
171
|
+
params = {
|
172
|
+
stack_name: stack_name,
|
173
|
+
template_body: template.to_json,
|
174
|
+
parameters: parameters_array,
|
175
|
+
}
|
176
|
+
|
177
|
+
set_api_params(params,
|
178
|
+
:use_previous_template,
|
179
|
+
:stack_policy_during_update_body,
|
180
|
+
:stack_policy_during_update_url,
|
181
|
+
:notification_arns,
|
182
|
+
:capabilities,
|
183
|
+
:resource_types,
|
184
|
+
:stack_policy_body,
|
185
|
+
:stack_policy_url)
|
186
|
+
|
187
|
+
event_log = create_event_log(stack)
|
188
|
+
stack.update(params)
|
189
|
+
|
190
|
+
return if @options.detach?
|
191
|
+
|
192
|
+
# XXX: Reacquire the stack
|
193
|
+
stack = @resource.stack(stack_name)
|
194
|
+
completed = wait(stack, 'UPDATE_COMPLETE', event_log)
|
195
|
+
|
196
|
+
unless completed
|
197
|
+
raise_stack_error!(stack, 'Update failed')
|
198
|
+
end
|
199
|
+
|
200
|
+
outputs = outputs_for(stack)
|
201
|
+
summaries = resource_summaries_for(stack)
|
202
|
+
|
203
|
+
output_result(stack_name, outputs, summaries)
|
204
|
+
|
205
|
+
outputs
|
206
|
+
end
|
207
|
+
|
208
|
+
def delete_stack(stack_name)
|
209
|
+
stack = @resource.stack(stack_name)
|
210
|
+
stack.stack_status
|
211
|
+
|
212
|
+
log(:info, "Deleting stack: #{stack_name}", color: :red)
|
213
|
+
event_log = create_event_log(stack)
|
214
|
+
stack.delete
|
215
|
+
|
216
|
+
return if @options.detach?
|
217
|
+
|
218
|
+
completed = false
|
219
|
+
|
220
|
+
begin
|
221
|
+
# XXX: Reacquire the stack
|
222
|
+
stack = @resource.stack(stack_name)
|
223
|
+
completed = wait(stack, 'DELETE_COMPLETE', event_log)
|
224
|
+
rescue Aws::CloudFormation::Errors::ValidationError
|
225
|
+
# Handle `Stack does not exist`
|
226
|
+
completed = true
|
227
|
+
end
|
228
|
+
|
229
|
+
unless completed
|
230
|
+
raise_stack_error!(stack, 'Delete failed')
|
231
|
+
end
|
232
|
+
|
233
|
+
log(:info, 'Success')
|
234
|
+
end
|
235
|
+
|
236
|
+
def validate_template(template)
|
237
|
+
@client.validate_template(template_body: template.to_json)
|
238
|
+
log(:info, 'Template validated successfully', color: :green)
|
239
|
+
end
|
240
|
+
|
241
|
+
def describe_stacks(stack_name)
|
242
|
+
params = {}
|
243
|
+
params[:stack_name] = stack_name if stack_name
|
244
|
+
|
245
|
+
@resource.stacks(params).map do |stack|
|
246
|
+
{
|
247
|
+
'StackName' => stack.name,
|
248
|
+
'CreationTime' => stack.creation_time,
|
249
|
+
'StackStatus' => stack.stack_status,
|
250
|
+
'Description' => stack.description,
|
251
|
+
}
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def export_template(stack_name)
|
256
|
+
stack = @resource.stack(stack_name)
|
257
|
+
stack.stack_status
|
258
|
+
template = stack.client.get_template(stack_name: stack_name).template_body
|
259
|
+
JSON.parse(template)
|
260
|
+
end
|
261
|
+
|
262
|
+
def show_change_set(template, stack_name)
|
263
|
+
output = nil
|
264
|
+
change_set_name = [stack_name, SecureRandom.uuid].join('-')
|
265
|
+
|
266
|
+
log(:info, "Creating ChangeSet: #{change_set_name}", color: :cyan)
|
267
|
+
|
268
|
+
params = {
|
269
|
+
stack_name: stack_name,
|
270
|
+
change_set_name: change_set_name,
|
271
|
+
template_body: template.to_json,
|
272
|
+
parameters: parameters_array,
|
273
|
+
}
|
274
|
+
|
275
|
+
set_api_params(params,
|
276
|
+
:use_previous_template,
|
277
|
+
:notification_arns,
|
278
|
+
:capabilities,
|
279
|
+
:resource_types)
|
280
|
+
|
281
|
+
resp = @client.create_change_set(params)
|
282
|
+
change_set_arn = resp.id
|
283
|
+
|
284
|
+
completed, change_set = wait_change_set(change_set_arn, 'CREATE_COMPLETE')
|
285
|
+
|
286
|
+
if completed
|
287
|
+
output = changes_for(change_set)
|
288
|
+
else
|
289
|
+
log(:error, "Create ChangeSet failed: #{change_set.status_reason}", color: :red)
|
290
|
+
end
|
291
|
+
|
292
|
+
log(:info, "Deleting ChangeSet: #{change_set_name}", color: :red)
|
293
|
+
|
294
|
+
@client.delete_change_set(change_set_name: change_set_arn)
|
295
|
+
|
296
|
+
begin
|
297
|
+
completed, _ = wait_change_set(change_set_arn, 'DELETE_COMPLETE')
|
298
|
+
rescue Aws::CloudFormation::Errors::ChangeSetNotFound
|
299
|
+
# Handle `ChangeSet does not exist`
|
300
|
+
completed = true
|
301
|
+
end
|
302
|
+
|
303
|
+
unless completed
|
304
|
+
log(:error, "Delete ChangeSet failed: #{change_set.status_reason}", color: :red)
|
305
|
+
end
|
306
|
+
|
307
|
+
output
|
308
|
+
end
|
309
|
+
|
310
|
+
def describe_events(stack_name)
|
311
|
+
stack = @resource.stack(stack_name)
|
312
|
+
stack.stack_status
|
313
|
+
events_for(stack)
|
314
|
+
end
|
315
|
+
|
316
|
+
def describe_outputs(stack_name)
|
317
|
+
stack = @resource.stack(stack_name)
|
318
|
+
stack.stack_status
|
319
|
+
outputs_for(stack)
|
320
|
+
end
|
321
|
+
|
322
|
+
def describe_resources(stack_name)
|
323
|
+
stack = @resource.stack(stack_name)
|
324
|
+
stack.stack_status
|
325
|
+
resource_summaries_for(stack)
|
326
|
+
end
|
327
|
+
|
328
|
+
def convert0(template)
|
329
|
+
ext = @options.output_format || 'template'
|
330
|
+
plugin = find_or_create_plugin('xxx.' + ext)
|
331
|
+
|
332
|
+
if plugin
|
333
|
+
plugin.dump(template)
|
334
|
+
else
|
335
|
+
raise "Unknown format: #{ext}"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def open_template(path_or_url)
|
340
|
+
plugin = find_or_create_plugin(path_or_url)
|
341
|
+
|
342
|
+
if plugin
|
343
|
+
@options.path_or_url = path_or_url
|
344
|
+
plugin.parse(open(path_or_url, &:read))
|
345
|
+
else
|
346
|
+
raise "Unknown format: #{path_or_url}"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def find_or_create_plugin(path_or_url)
|
351
|
+
ext = File.extname(path_or_url).sub(/\A\./, '')
|
352
|
+
|
353
|
+
if @plugin_by_ext.has_key?(ext)
|
354
|
+
return @plugin_by_ext.fetch(ext)
|
355
|
+
end
|
356
|
+
|
357
|
+
plugin_class = Kumogata2::Plugin.find(ext)
|
358
|
+
plugin = plugin_class ? plugin_class.new(@options) : nil
|
359
|
+
@plugin_by_ext[ext] = plugin
|
360
|
+
end
|
361
|
+
|
362
|
+
def update_deletion_policy(template, options = {})
|
363
|
+
if options[:delete_stack] or @options.deletion_policy_retain?
|
364
|
+
template['Resources'].each do |k, v|
|
365
|
+
next if /\AAWS::CloudFormation::/ =~ v['Type']
|
366
|
+
v['DeletionPolicy'] ||= 'Retain'
|
367
|
+
|
368
|
+
if options[:update_metadate]
|
369
|
+
v['Metadata'] ||= {}
|
370
|
+
v['Metadata']['DeletionPolicyUpdateKeyForKumogata'] = "DeletionPolicyUpdateValueForKumogata#{Time.now.to_i}"
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def validate_stack_name(stack_name)
|
377
|
+
unless /\A[a-zA-Z][-a-zA-Z0-9]*\Z/i =~ stack_name
|
378
|
+
raise "1 validation error detected: Value '#{stack_name}' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def parameters_array
|
383
|
+
@options.parameters.map do |key, value|
|
384
|
+
{parameter_key: key, parameter_value: value}
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def set_api_params(params, *keys)
|
389
|
+
keys.each do |k|
|
390
|
+
opts[k] = @options[k] if @options[k]
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def wait(stack, complete_status, event_log = {})
|
395
|
+
before_wait = proc do |attempts, response|
|
396
|
+
print_event_log(stack, event_log)
|
397
|
+
end
|
398
|
+
|
399
|
+
stack.wait_until(before_wait: before_wait, max_attempts: nil, delay: 1) do |s|
|
400
|
+
s.stack_status !~ /_IN_PROGRESS\z/
|
401
|
+
end
|
402
|
+
|
403
|
+
print_event_log(stack, event_log)
|
404
|
+
|
405
|
+
completed = (stack.stack_status == complete_status)
|
406
|
+
log(:info, completed ? 'Success' : 'Failure')
|
407
|
+
|
408
|
+
completed
|
409
|
+
end
|
410
|
+
|
411
|
+
def wait_change_set(change_set_name, complete_status)
|
412
|
+
change_set = nil
|
413
|
+
|
414
|
+
loop do
|
415
|
+
change_set = @client.describe_change_set(change_set_name: change_set_name)
|
416
|
+
|
417
|
+
if change_set.status !~ /(_PENDING|_IN_PROGRESS)\z/
|
418
|
+
break
|
419
|
+
end
|
420
|
+
|
421
|
+
sleep 1
|
422
|
+
end
|
423
|
+
|
424
|
+
completed = (change_set.status == complete_status)
|
425
|
+
[completed, change_set]
|
426
|
+
end
|
427
|
+
|
428
|
+
def print_event_log(stack, event_log)
|
429
|
+
events_for(stack).sort_by {|i| i['Timestamp'] }.each do |event|
|
430
|
+
event_id = event['EventId']
|
431
|
+
|
432
|
+
unless event_log[event_id]
|
433
|
+
event_log[event_id] = event
|
434
|
+
|
435
|
+
timestamp = event['Timestamp']
|
436
|
+
summary = {}
|
437
|
+
|
438
|
+
['LogicalResourceId', 'ResourceStatus', 'ResourceStatusReason'].map do |k|
|
439
|
+
summary[k] = event[k]
|
440
|
+
end
|
441
|
+
|
442
|
+
puts [
|
443
|
+
timestamp.getlocal.strftime('%Y/%m/%d %H:%M:%S %Z'),
|
444
|
+
summary.to_json.colorize_as(:json),
|
445
|
+
].join(': ')
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def create_event_log(stack)
|
451
|
+
event_log = {}
|
452
|
+
|
453
|
+
events_for(stack).sort_by {|i| i['Timestamp'] }.each do |event|
|
454
|
+
event_id = event['EventId']
|
455
|
+
event_log[event_id] = event
|
456
|
+
end
|
457
|
+
|
458
|
+
return event_log
|
459
|
+
end
|
460
|
+
|
461
|
+
def events_for(stack)
|
462
|
+
stack.events.map do |event|
|
463
|
+
event_hash = {}
|
464
|
+
|
465
|
+
[
|
466
|
+
:event_id,
|
467
|
+
:logical_resource_id,
|
468
|
+
:physical_resource_id,
|
469
|
+
:resource_properties,
|
470
|
+
:resource_status,
|
471
|
+
:resource_status_reason,
|
472
|
+
:resource_type,
|
473
|
+
:stack_id,
|
474
|
+
:stack_name,
|
475
|
+
:timestamp,
|
476
|
+
].each do |k|
|
477
|
+
event_hash[Kumogata2::Utils.camelize(k)] = event.send(k)
|
478
|
+
end
|
479
|
+
|
480
|
+
event_hash
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def outputs_for(stack)
|
485
|
+
outputs_hash = {}
|
486
|
+
|
487
|
+
stack.outputs.each do |output|
|
488
|
+
outputs_hash[output.output_key] = output.output_value
|
489
|
+
end
|
490
|
+
|
491
|
+
outputs_hash
|
492
|
+
end
|
493
|
+
|
494
|
+
def resource_summaries_for(stack)
|
495
|
+
stack.resource_summaries.map do |summary|
|
496
|
+
summary_hash = {}
|
497
|
+
|
498
|
+
[
|
499
|
+
:logical_resource_id,
|
500
|
+
:physical_resource_id,
|
501
|
+
:resource_type,
|
502
|
+
:resource_status,
|
503
|
+
:resource_status_reason,
|
504
|
+
:last_updated_timestamp
|
505
|
+
].each do |k|
|
506
|
+
summary_hash[Kumogata2::Utils.camelize(k)] = summary.send(k)
|
507
|
+
end
|
508
|
+
|
509
|
+
summary_hash
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
def changes_for(change_set)
|
514
|
+
change_set.changes.map do |change|
|
515
|
+
resource_change = change.resource_change
|
516
|
+
change_hash = {}
|
517
|
+
|
518
|
+
[
|
519
|
+
:action,
|
520
|
+
:logical_resource_id,
|
521
|
+
:physical_resource_id,
|
522
|
+
:resource_type,
|
523
|
+
].each do |k|
|
524
|
+
change_hash[Kumogata2::Utils.camelize(k)] = resource_change[k]
|
525
|
+
end
|
526
|
+
|
527
|
+
change_hash['Details'] = resource_change.details.map do |detail|
|
528
|
+
{
|
529
|
+
attribute: detail.target.attribute,
|
530
|
+
name: detail.target.name,
|
531
|
+
}
|
532
|
+
end
|
533
|
+
|
534
|
+
change_hash
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def output_result(stack_name, outputs, summaries)
|
539
|
+
puts <<-EOS
|
540
|
+
|
541
|
+
Stack Resource Summaries:
|
542
|
+
#{JSON.pretty_generate(summaries).colorize_as(:json)}
|
543
|
+
|
544
|
+
Outputs:
|
545
|
+
#{JSON.pretty_generate(outputs).colorize_as(:json)}
|
546
|
+
EOS
|
547
|
+
|
548
|
+
if @options.result_log?
|
549
|
+
puts <<-EOS
|
550
|
+
|
551
|
+
(Save to `#{@options.result_log}`)
|
552
|
+
EOS
|
553
|
+
|
554
|
+
open(@options.result_log, 'wb') do |f|
|
555
|
+
f.puts JSON.pretty_generate({
|
556
|
+
'StackName' => stack_name,
|
557
|
+
'StackResourceSummaries' => summaries,
|
558
|
+
'Outputs' => outputs,
|
559
|
+
})
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
def post_process(path_or_url, outputs)
|
565
|
+
plugin = find_or_create_plugin(path_or_url)
|
566
|
+
|
567
|
+
if plugin and plugin.respond_to?(:post)
|
568
|
+
plugin.post(outputs)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def raise_stack_error!(stack, message)
|
573
|
+
errmsgs = [message]
|
574
|
+
errmsgs << stack.name
|
575
|
+
errmsgs << stack.stack_status_reason if stack.stack_status_reason
|
576
|
+
raise errmsgs.join(': ')
|
577
|
+
end
|
578
|
+
|
579
|
+
def random_stack_name
|
580
|
+
stack_name = ['kumogata']
|
581
|
+
user_host = Kumogata2::Utils.get_user_host
|
582
|
+
stack_name << user_host if user_host
|
583
|
+
stack_name << SecureRandom.uuid
|
584
|
+
stack_name = stack_name.join('-')
|
585
|
+
stack_name.gsub(/[^-a-zA-Z0-9]+/, '-').gsub(/-+/, '-')
|
586
|
+
end
|
587
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
constant: "\e[1;34m",
|
3
|
+
float: "\e[36m",
|
4
|
+
integer: "\e[36m",
|
5
|
+
keyword: "\e[1;31m",
|
6
|
+
|
7
|
+
key: {
|
8
|
+
self: "\e[1;34m",
|
9
|
+
char: "\e[1;34m",
|
10
|
+
delimiter: "\e[1;34m",
|
11
|
+
},
|
12
|
+
|
13
|
+
string: {
|
14
|
+
self: "\e[32m",
|
15
|
+
modifier: "\e[1;32m",
|
16
|
+
char: "\e[1;32m",
|
17
|
+
delimiter: "\e[1;32m",
|
18
|
+
escape: "\e[1;32m",
|
19
|
+
},
|
20
|
+
|
21
|
+
error: "\e[0m",
|
22
|
+
}.each do |key, value|
|
23
|
+
CodeRay::Encoders::Terminal::TOKEN_COLORS[key] = value
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Kumogata2::Ext
|
2
|
+
module StringExt
|
3
|
+
module ClassMethods
|
4
|
+
def colorize=(value)
|
5
|
+
@colorize = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def colorize
|
9
|
+
@colorize
|
10
|
+
end
|
11
|
+
end # ClassMethods
|
12
|
+
|
13
|
+
Term::ANSIColor::Attribute.named_attributes.each do |attribute|
|
14
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
15
|
+
def #{attribute.name}
|
16
|
+
if String.colorize
|
17
|
+
Term::ANSIColor.send(#{attribute.name.inspect}, self)
|
18
|
+
else
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
|
25
|
+
def colorize_as(lang)
|
26
|
+
if String.colorize
|
27
|
+
CodeRay.scan(self, lang).terminal
|
28
|
+
else
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end # StringExt
|
33
|
+
end # Kumogata2::Ext
|
34
|
+
|
35
|
+
String.include(Kumogata2::Ext::StringExt)
|
36
|
+
String.extend(Kumogata2::Ext::StringExt::ClassMethods)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Kumogata2::Logger < ::Logger
|
2
|
+
include Singleton
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super($stdout)
|
6
|
+
|
7
|
+
self.formatter = proc do |severity, datetime, progname, msg|
|
8
|
+
"#{msg}\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
self.level = Logger::INFO
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_debug(value)
|
15
|
+
self.level = value ? Logger::DEBUG : Logger::INFO
|
16
|
+
end
|
17
|
+
|
18
|
+
module Helper
|
19
|
+
def log(level, message, log_options = {})
|
20
|
+
globa_options = @options || {}
|
21
|
+
message = "[#{level.to_s.upcase}] #{message}" unless level == :info
|
22
|
+
message = message.send(log_options[:color]) if log_options[:color]
|
23
|
+
logger = globa_options[:logger] || Kumogata2::Logger.instance
|
24
|
+
logger.send(level, message)
|
25
|
+
end
|
26
|
+
module_function :log
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Kumogata2::Plugin::JSON
|
2
|
+
Kumogata2::Plugin.register(:json, ['json', 'js', 'template'], self)
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(str)
|
9
|
+
JSON.parse(str)
|
10
|
+
end
|
11
|
+
|
12
|
+
def dump(hash)
|
13
|
+
JSON.pretty_generate(hash).colorize_as(:json)
|
14
|
+
end
|
15
|
+
end
|