-
Notifications
You must be signed in to change notification settings - Fork 0
/
vic.inc.rb
404 lines (346 loc) · 10.7 KB
/
vic.inc.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
require 'bundler/setup'
require 'cloudformation-ruby-dsl/cfntemplate'
require 'cloudformation-ruby-dsl/spotprice'
require 'cloudformation-ruby-dsl/table'
require 'diw/config'
# First, we extend the cloudformation-ruby-dsl TemplateDSL class, to add in some
# utility methods, and parameter handling
class TemplateDSL < JsonObjectDSL
def exec!()
if ARGV[0] == "parameters"
STDOUT.puts(JSON.generate((@dict[:Parameters] || []).map {|k,definition|
if $cfg.has_var?(k) || !self.parameters[k].empty?
{
ParameterKey: k,
ParameterValue: (
if $cfg.has_var?(k)
$cfg.var(k)
else
self.parameters[k]
end
)
}
end
}.to_a.compact))
else
cfn(self)
end
end
def prefixed_name(name)
[$cfg.environment, name].join('-')
end
def name_tag(name, options = {})
{ :Key => "Name", :Value => prefixed_name(name) }.
merge(options)
end
def export_value(name)
{ :Name => [stack_name, name].join(':') }
end
def import_stack_export(external_stack_name, name, environment: nil)
{
"Fn::ImportValue" => [
$cfg.stacks.var(external_stack_name.to_sym, environment: environment),
name
].join(':')
}
end
def import_stack_output(external_stack_name, name, environment: nil)
@stack_values = {} if @stack_values.nil?
stack_name = $cfg.stacks.var(external_stack_name.to_sym, environment: environment)
if @stack_values.has_key? stack_name
unless @stack_values[stack_name].has_key? name
raise "Unknown stack value #{stack_name}::#{name}"
end
return @stack_values[stack_name][name]
end
cfnclient = Aws::CloudFormation::Client.new()
stack = cfnclient.describe_stacks({stack_name: stack_name}).stacks[0]
@stack_values[stack_name] = {}
stack.outputs.each{|output|
@stack_values[stack_name][output.output_key] = output.output_value
}
return self.import_stack_output(external_stack_name, name, environment: environment)
end
def load_from_file(filename, *vargs, **hargs)
file = File.open(filename)
begin
# Figure out what the file extension is and process accordingly.
contents = case File.extname(filename)
when ".rb"; eval(file.read, nil, filename)
when ".json"; JSON.load(file)
when ".yaml"; YAML::load(file)
else; raise("Do not recognize extension of #{filename}.")
end
ensure
file.close
end
if contents.is_a? Proc
args[:block] = Proc.new if block_given?
contents = instance_exec(*vargs, **hargs, &contents)
end
end
def _ucword(word)
return word.upcase if word.length < 2
word[0].upcase + word[1..-1]
end
def _snakeify(camel, glue = '_')
camel.
gsub(/(.)([A-Z][^A-Z])/,'\1' + glue + '\2').
gsub(/([^A-Z])([A-Z])/,'\1' + glue + '\2').
squeeze(glue).
gsub(/([A-Z])#{glue}([A-Z])/, '\1\2')
end
def camelify(s)
s.to_s.split(/[^a-zA-Z0-9]+/).collect(&:capitalize).join
end
def define_output(resource_name, resource_type, attribute, spec, export_default)
unless spec.is_a? Hash
spec = attribute.to_s if spec == true
spec = {:ShortName => spec}
end
name = nil
description = nil
name = spec[:Name].to_s if spec.has_key?(:Name)
description = spec[:Description].to_s if spec.has_key?(:Description)
if spec.has_key?(:ShortName)
compact_name = _snakeify(spec[:ShortName].to_s).split(/_/).map{|s|_ucword(s)}.join('').gsub(/[^_a-zA-Z0-9]/, '')
name = resource_name.to_s + compact_name unless spec.has_key?(:Name)
end
if description.nil?
resource_description = resource_name.to_s
if resource_name.to_s.end_with? resource_type.split('::').last
resource_description = (
resource_name[0..-((resource_type.split('::').last.length)+1)] +
" " +
_snakeify(resource_type.split('::').last, ' ')
)
end
short_description = attribute.to_s
if spec.has_key?(:ShortDescription)
short_description = spec[:ShortDescription].to_s
elsif spec.has_key?(:ShortName)
short_description = spec[:ShortName].to_s
end
description = "The #{short_description} of the #{resource_description}"
end
name = attribute.to_s if name.nil?
export = if export_default then export_value(name) else nil end
if spec.has_key?(:Export)
if !!spec[:Export] == spec[:Export]
export = nil if !spec[:Export]
else
export = spec[:Export].to_s
end
end
if attribute == :Ref
value = ref(resource_name.to_s)
else
value = get_att(resource_name.to_s, attribute.to_s)
end
output name, (
{}.merge(
{:Value => value}
).merge(
(if description.nil? then {} else {:Description => description} end)
).merge(
(if export.nil? then {} else {:Export => export} end)
)
)
end
alias resource_orig resource
def resource(name, options)
[:Output, :Export].each do |option|
if options.has_key?(option)
type = if options.has_key?(:Type) then options[:Type] else nil end
export_default = (option == :Export)
attributes = options[option]
if attributes.is_a? Hash
if attributes.has_key? :Export then export_default = attributes[:Export] end
if attributes.has_key? :Attributes then attributes = attributes[:Attributes] end
end
if attributes.is_a? Array
attributes.each do |attribute|
if attribute.is_a? Hash and attribute.has_key?(:Attribute)
spec = attribute
attribute = spec[:Attribute]
spec.delete(:Attribute)
define_output(name, type, attribute, spec, export_default)
else
spec = if attribute == :Ref then "Id" else attribute.to_s end
define_output(name, type, attribute, spec, export_default)
end
end
elsif attributes.is_a? Hash
attributes.each do |attribute, spec|
next if attribute == :Export and attributes == options[option]
define_output(name, type, attribute, spec, export_default)
end
elsif attributes.is_a? Symbol
spec = if attributes == :Ref then "Id" else attributes.to_s end
define_output(name, type, attributes, spec, export_default)
elsif !!attributes == attributes
define_output(name, type, :Ref, "Id", export_default)
else
define_output(name, type, :Ref, attributes, export_default)
end
end
end
resource_orig(name, options.tap{|o| [:Output,:Export].each{|k| o.delete(k)} })
end
end
# Define "S3Frame", which quacks like a diw/config "Frame", so that we can read
# secret data from S3 (using AWS credentials)
class S3Frame
def initialize(bucket, s3prefix = "", configprefix = [])
@bucket = bucket
@s3prefix = s3prefix
@configprefix = configprefix
@section_cache = {}
@var_cache = {}
end
def s3client
return @s3client unless @s3client.nil?
@s3client = Aws::S3::Client.new()
end
def maybe_prefix?(section_path)
if section_path.length > @configprefix.length
section_path[[email protected]] == @configprefix
else
@configprefix[0..section_path.length-1] == section_path
end
end
def strip_prefix(section_path)
return section_path if @configprefix.length == 0
return nil if section_path.length < @configprefix.length
return nil if section_path[[email protected] - 1] != @configprefix
section_path[(@configprefix.length)..(section_path.length - 1)]
end
def has_section?(path)
return !!@section_cache[path.join "."] if @section_cache.has_key?(path.join ".")
key = strip_prefix(path)
if key.nil?
if maybe_prefix?(path)
@section_cache[path.join "."] = { }
return true
end
return false
end
if key.length == 0 then
@section_cache[path.join "."] = { }
return true
end
begin
s3client.head_object(
bucket: @bucket,
key: @s3prefix + key.join("/") + "/",
if_match: "d41d8cd98f00b204e9800998ecf8427e"
)
@section_cache[path.join "."] = { }
return true
rescue Aws::S3::Errors::NotFound
@section_cache[path.join "."] = false
return false
end
end
def set_section(path, vars = {})
raise NoMethodError.new("set_section not implemented for S3Frame")
end
def has_var?(name)
return false if @configprefix.length > 0
return !@var_cache[name].nil? if @var_cache.has_key?(name)
begin
s3client.head_object(
bucket: @bucket,
key: @s3prefix + name.to_s
)
@var_cache[name] = :NOT_FILLED
return true
rescue Aws::S3::Errors::NotFound
@var_cache[name] = nil
return false
end
end
def get_var(name)
return @var_cache[name] if @var_cache.has_key?(name) && @var_cache[name] != :NOT_FILLED
result = s3client.get_object(
bucket: @bucket,
key: @s3prefix + name.to_s
)
@var_cache[name] = result.body.string.strip
end
def set_var(name, value)
raise NoMethodError.new("set_var not implemented for S3Frame")
end
def has_section_var?(section_path, name)
return false if !has_section?(section_path)
return !@section_cache[section_path.join "."][name].nil? if @section_cache[section_path.join "."].has_key?(name)
key = strip_prefix(section_path)
return false if key.nil?
begin
s3client.head_object(
bucket: @bucket,
key: @s3prefix + (key + [ name.to_s ]).join("/")
)
@section_cache[section_path.join "."][name] = :NOT_FILLED
return true
rescue Aws::S3::Errors::NotFound
@section_cache[section_path.join "."][name] = nil
return false
end
end
def get_section_var(section_path, name)
return nil if !has_section?(section_path)
if @section_cache[section_path.join "."].has_key?(name) &&
@section_cache[section_path.join "."][name] != :NOT_FILLED
return @section_cache[section_path.join "."][name]
end
key = strip_prefix(section_path)
return nil if key.nil?
result = s3client.get_object(
bucket: @bucket,
key: @s3prefix + (key + [ name.to_s ]).join("/")
)
@section_cache[section_path.join "."][name] = result.body.string.strip
end
def set_section_var(section_path, name, value)
raise NoMethodError.new("set_section_var not implemented for S3Frame")
end
end
# Define the default $cfg.environment and $cfg.stacks.<stack name> to obtain
# environment-specific stack names based on a generic template name.
$cfg = ::DIW::Config::Config.new do
var \
environment: 'live',
stacks: Proc.new {|cfg|
Module.new {
def self.method_missing(method, *args)
return super if args.length > 0
self.var(method.to_s.gsub(/_/, '-'))
end
def self.var(name, environment: nil)
environment = $cfg.environment if environment.nil?
case name
when /-(?:dns|registry)$/
if name =~ /-private-dns$/
[environment, name].join('-')
else
name
end
else
[environment, name].join('-')
end
end
}}
end
# Override $cfg.environment based on the --environment argument (if applicable)
$cfg.push do
ARGV.slice_before(/^--/).each do |name, value|
case name
when /--environment=(.*)$/
var environment: $1
end
end
end
# Pull in defaults and environment-specific configuration
require ('./_default') if File.exists?('./_default.rb')
require ('./_' + $cfg.environment) if File.exists?('./_' + $cfg.environment + '.rb')