aws-cfn-stacker 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,495 @@
1
+
2
+ module Aws
3
+ module Cfn
4
+ module Stacker
5
+
6
+ module Options
7
+ include ::DLDInternet::Mixlib::CLI
8
+
9
+ # --------------------------------------------------------------------------------
10
+ #
11
+ # Override Mixlib::CLI.parse_options and
12
+ # knowingly add extra paramater to track argument source
13
+ #
14
+ # We do this by monkey patching this override method at include time instead of statically declaring it
15
+ # in which case the Mixlib::CLI.parse_options seems to "win" most of the time OR the interpreter complains.
16
+ #
17
+ def parse_options(args,source=nil)
18
+ # noinspection RubySuperCallWithoutSuperclassInspection
19
+ argv = super(args)
20
+
21
+ prescreen_options(argv)
22
+
23
+ @config = parse_and_validate_options(@config,source ? source : "ARGV - #{__LINE__}")
24
+
25
+ unless @config[:actions]
26
+ @config[:actions] = [ argv[1].to_sym ]
27
+ end
28
+ @actors[argv[1].to_sym] = self
29
+ others = @config[:actions].select{|a|
30
+ a != argv[1].to_sym
31
+ }
32
+ index = args.index '--action'
33
+ argv
34
+ end
35
+
36
+ # --------------------------------------------------------------------------------------------------------------
37
+ #
38
+ # Do a quick prescreening of the arguments and bail early for some obvious cases
39
+ #
40
+ def prescreen_options(argv=ARGV)
41
+ # Checking ARGV validity *before* parse_options because parse_options
42
+ # mangles ARGV in some situations
43
+ if no_command_given?
44
+ print_help_and_exit(1, NO_COMMAND_GIVEN)
45
+ elsif no_subcommand_given?
46
+ if want_help? || want_version?
47
+ print_help_and_exit
48
+ else
49
+ print_help_and_exit(2, NO_COMMAND_GIVEN)
50
+ end
51
+ end
52
+ end
53
+
54
+ # --------------------------------------------------------------------------------------------------------------
55
+ # private
56
+ # --------------------------------------------------------------------------------------------------------------
57
+ def no_subcommand_given?(argv=ARGV)
58
+ argv[0] =~ /^--?[^a]/
59
+ end
60
+
61
+ def no_command_given?(argv=ARGV)
62
+ argv.empty?
63
+ end
64
+
65
+ def want_help?
66
+ ARGV[0] =~ /^(--help|-h)$/
67
+ end
68
+
69
+ def want_version?
70
+ ARGV[0] =~ /^(--version|-v)$/
71
+ end
72
+
73
+ def print_help_and_exit(exitcode=1, fatal_message=nil)
74
+ puts "FATAL: #{fatal_message}" if fatal_message
75
+
76
+ puts self.opt_parser
77
+ puts
78
+ exit exitcode
79
+ end
80
+
81
+ # --------------------------------------------------------------------------------
82
+ def parseActionSymbol(v)
83
+ if v.to_sym == :all
84
+ ::Aws::Cfn::Stacker::Application.allactions
85
+ else
86
+ s = v.to_sym
87
+ allactions = [::Aws::Cfn::Stacker::Application.allactions, :all].flatten
88
+ unless allactions.include?(s)
89
+ allactions.each{ |p|
90
+ s = p if p.match(%r/^#{s}/)
91
+ }
92
+ end
93
+ s = ::Aws::Cfn::Stacker::Application.allactions if s == :all
94
+ s
95
+ end
96
+ end
97
+
98
+ # --------------------------------------------------------------------------------
99
+ def parsePrecedence(v)
100
+ @prec_max += 1
101
+ match = v.match(%r/^(json|rb|yaml)$/i)
102
+ unless match
103
+ m = "ERROR: Invalid precedence argument: #{v}. Accept only from this set: [json,rb,yaml]"
104
+ puts m
105
+ raise Exception.new(m)
106
+ end
107
+ s = { v => @prec_max }
108
+ match = v.match(%r/^(\S+):(\d+)$/)
109
+ if match
110
+ begin
111
+ a = match[1]
112
+ i = match[2].to_i
113
+ s = { a => i }
114
+ rescue => e
115
+ puts "ERROR: Unable to match precedence #{v}"
116
+ raise e
117
+ end
118
+ end
119
+ s
120
+ end
121
+
122
+ # --------------------------------------------------------------------------------
123
+ def parseINIFile(options=nil)
124
+ options = @config unless options
125
+ if options.key?(:inifile)
126
+ logStep "Parse INI file - #{options[:inifile]}"
127
+ raise StackerError.new("Cannot find inifile (#{options[:inifile]})") unless File.exist?(options[:inifile])
128
+ raise StackerError.new("Recursive call to inifile == '#{options[:inifile]}'") if @inis.include?(options[:inifile])
129
+ ini = nil
130
+ begin
131
+ ini = IniFile.load(options[:inifile])
132
+ @inis << options[:inifile]
133
+ ini['global'].each { |key, value|
134
+ #puts "#{key}=#{value}"
135
+ ENV[key]=value
136
+ }
137
+ argv=[]
138
+ cli = ini['cli'] || []
139
+ cli.each{ |key,value|
140
+ argv << key.gsub(%r/:[0-9]+$/, '').gsub(%r/^([^-])/, '--\1')
141
+ argv << value
142
+ }
143
+ if argv.size > 0
144
+ parse_options(argv,"INI-#{options[:inifile]}")
145
+ end
146
+ rescue => e
147
+ puts e.message.light_red
148
+ raise e
149
+ end
150
+ end
151
+ options
152
+ end
153
+
154
+ # -----------------------------------------------------------------------------
155
+ def setDefaultOptions(options)
156
+ @options.each{|name,args|
157
+ if args[:default]
158
+ options[name] = args[:default] unless options[name]
159
+ end
160
+ }
161
+ setOrigins(options,'default')
162
+ end
163
+
164
+ # -----------------------------------------------------------------------------
165
+ def validate_options(options=nil)
166
+ options = @config unless options
167
+
168
+ # Check for the necessary environment variables
169
+ logStep ('Check ENVironment')
170
+ env = ENV.to_hash
171
+ missing = {}
172
+ %w(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY).each { |k|
173
+ missing[k] = true unless ENV.has_key?(k)
174
+ }
175
+ if options[:use_chef]
176
+ %w(KNIFE_CHEF_SERVER_URL KNIFE_CLIENT_KEY KNIFE_CLIENT_NAME).each { |k|
177
+ missing[k] = true unless ENV.has_key?(k)
178
+ }
179
+ end
180
+
181
+ if missing.count() > 0
182
+ #@logger.error "Missing keys: #{missing.keys.ai}"
183
+ raise StackerError.new("Missing environment variables: #{missing.keys}")
184
+ end
185
+ end
186
+
187
+ # -----------------------------------------------------------------------------
188
+ def parse_and_validate_options(options=nil,source='ARGV')
189
+ options = @config unless options
190
+ setOrigins(options,source)
191
+
192
+ #options = parseOptions(options,source)
193
+ unless @origins and @name_key_map
194
+ # These are the essential default options which things like parseOptions depend on
195
+ {
196
+ :verbosity => @verbosity,
197
+ :auto_purge => false,
198
+ }.each{ |k,v|
199
+ options[k] = v unless options[k]
200
+ }
201
+ setOrigins(options,'hardcoded-default')
202
+
203
+ @name_key_map = {} unless @name_key_map
204
+ @options.each{ |name,args|
205
+ @name_key_map[name] = {} unless @name_key_map[name]
206
+ [:short,:long,:description].each{|key|
207
+ @name_key_map[name][key] = args[key] if args[key]
208
+ }
209
+ }
210
+ end
211
+
212
+ begin
213
+ parseINIFile(options)
214
+ setDefaultOptions(options)
215
+ # Check for all the necessary options
216
+ validate_options(options)
217
+ checkArgsSources(options)
218
+ #findRootPath(options)
219
+ rescue StackerError => e
220
+ puts e.message.light_red
221
+ puts "#{__FILE__}::#{__LINE__} reraising ... "
222
+ raise e
223
+ exit -1
224
+ rescue Exception => e
225
+ puts e.message.light_red
226
+ puts "#{__FILE__}::#{__LINE__} reraising ... "
227
+ raise e
228
+ exit -2
229
+ end
230
+
231
+ options
232
+ end
233
+
234
+ # ---------------------------------------------------------------------------------------------------------------
235
+ def setOrigins(options,source)
236
+ @origins = {} unless @origins
237
+ options.each { |key, val|
238
+ @origins[key] = source unless (@origins[key])
239
+ }
240
+ end
241
+
242
+ # ---------------------------------------------------------------------------------------------------------------
243
+ def checkArgsSources(options)
244
+ if @origins
245
+ missing = @origins.select{ |k,v|
246
+ v.nil?
247
+ }.map{ |k,v| k }
248
+ raise StackerError.new("Missing origins: #{missing.ai}") if missing.size > 0
249
+ end
250
+ end
251
+
252
+ module ClassMethods
253
+
254
+ # noinspection RubyInstanceVariableNamingConvention
255
+ def defaultoptions=(options)
256
+ @DEFAULTOPTIONS = options || {}
257
+ end
258
+
259
+ def defaultoptions
260
+ @DEFAULTOPTIONS
261
+ end
262
+
263
+ end
264
+
265
+ # --------------------------------------------------------------------------------------------------------------
266
+ #
267
+ # If this module is included we inject this payload into the including class.
268
+ #
269
+ def self.included(includer)
270
+ includer.extend(::DLDInternet::Mixlib::CLI::ClassMethods)
271
+ includer.extend(ClassMethods)
272
+
273
+ includer.class_eval do
274
+ self.defaultoptions = {}
275
+
276
+ <<-EOF
277
+
278
+ -c CONFIG_FILE, --config-file CONFIG_FILE
279
+ An ini file to use that contains one section per stack, with all the parameters for the stack enumerated
280
+ -v, --verbose Increase verbosity, can be specified multiple times (currently just sets the debug level for AWS)
281
+ --debug Increase debug, can be specified multiple times
282
+ -r, --remove Delete the requested stack. WARNING: No second chances!
283
+ -d, --delete Delete the requested stack. WARNING: No second chances!
284
+ -l, --list-params List the parameters in the template, and show what values are supplied by your config file
285
+ -t TEMPLATE, --template TEMPLATE
286
+ Specify a different template to run. Note that specific outputs are expected, so results may vary.
287
+ --template_url TEMPLATE_URL
288
+ Specify the key of a template stored in an S3 bucket to run. This method assumes the template has already been uploaded.
289
+ --use_s3 Use an S3 bucket to upload the template to before attempting stack run. This method assumes the template needs to be uploaded and may overwrite a key with the same name.
290
+ -k KEYFILE, --keyfile KEYFILE
291
+ The path to the SSH key file to use for SSH to hosts
292
+ -b, --build Build configuration directory for use with Ansible.
293
+ -i INITIAL_SETUP, --initial_setup INITIAL_SETUP
294
+ An initial setup file for Ansible
295
+ -o OUTPUT, --output OUTPUT
296
+ Output folder for the Ansible build configuration
297
+ -R ROLES_PATH, --roles_path ROLES_PATH
298
+ Folder for the Ansible roles
299
+ --ssh_user SSH_USER SSH User name
300
+ --ssh_user_bastion SSH_USER_BASTION
301
+ Bastion SSH User name
302
+ -u, --update Update the configuration of an existing stack
303
+ -C, --configure Run pre-defined ansible playbooks against the given stack. Implies -b
304
+ -s, --status Print the current status of a given stack.
305
+ -p, --progress Print a progress indicator during stack create/update/delete.
306
+
307
+ EOF
308
+
309
+ option :help,
310
+ short: '-h',
311
+ long: '--help',
312
+ description: 'Show this message',
313
+ show_options: true,
314
+ exit: 1
315
+ # print the version.
316
+ option :version,
317
+ short: '-V',
318
+ long: '--version',
319
+ description: 'Show version',
320
+ proc: Proc.new{ puts ::Aws::Cfn::Stacker::Application::VERSION },
321
+ exit: 2
322
+ option :config_file_alt,
323
+ short: '-c',
324
+ long: '--config-file FILE',
325
+ description: 'A config file to use that contains one section per stack, with all the parameters for the stack enumerated. INI, YAML and JSON formats supported.',
326
+ proc: lambda{ |v|
327
+ @options[:config_file] = v
328
+ }
329
+ option :config_file,
330
+ short: '-c',
331
+ long: '--config_file FILE',
332
+ description: 'A config file to use that contains one section per stack, with all the parameters for the stack enumerated. INI, YAML and JSON formats supported.'
333
+ option :verbose,
334
+ short: '-v',
335
+ long: '--verbose',
336
+ description: 'Increase verbosity, can be specified multiple times',
337
+ proc: lambda {|v|
338
+ index = ::Aws::Cfn::Stacker::Application.loglevels.index(@options[:log_level]) || ::Aws::Cfn::Stacker::Application.loglevels.index(:warn)
339
+ index -= 1 if index > 0
340
+ @options[:log_level] = ::Aws::Cfn::Stacker::Application.loglevels[index]
341
+ }
342
+ option :log_level,
343
+ long: '--debug',
344
+ description: 'Set debug level logging. No effect if specified second time.',
345
+ value: :debug
346
+ option :action,
347
+ short: '-a',
348
+ long: '--action ACTION',
349
+ description: "Perform the requested action against the stack. (#{::Aws::Cfn::Stacker::Application.allactions.to_s})",
350
+ proc: lambda{|v|
351
+ actions = $STKR.parseOptionString(v,',', 'parseActionSymbol')
352
+ all = [::Aws::Cfn::Stacker::Application.allactions, :all].flatten
353
+ actions.each{ |act|
354
+ unless all.include?(act.to_sym)
355
+ raise ::OptionParser::InvalidOption.new("Invalid action: #{act.to_s}. Valid actions are: #{all.to_s}")
356
+ end
357
+ }
358
+ actions
359
+ }
360
+ option :build,
361
+ short: '-b',
362
+ long: '--build',
363
+ description: 'Build configuration directory for use with Ansible.',
364
+ proc: lambda { |v| @options[:action] = :build}
365
+
366
+ option :remove,
367
+ short: '-r',
368
+ long: '--remove',
369
+ description: 'Delete the requested stack. WARNING: No second chances!',
370
+ proc: lambda { |v| @options[:action] = :delete }
371
+ option :delete,
372
+ short: '-d',
373
+ long: '--delete',
374
+ description: 'Delete the requested stack. WARNING: No second chances!',
375
+ proc: lambda { |v| @options[:action] = :delete }
376
+ option :update,
377
+ short: '-u',
378
+ long: '--update',
379
+ description: 'Update the configuration of an existing stack',
380
+ proc: lambda { |v| @options[:action] = :update}
381
+ option :listparams,
382
+ short: '-l',
383
+ long: '--list-params',
384
+ description: 'List the parameters in the template, and show what values are supplied by your config file',
385
+ proc: lambda { |v| @options[:action] = :listparams}
386
+ option :configure,
387
+ short: '-C',
388
+ long: '--configure',
389
+ description: 'Run pre-defined ansible playbooks against the given stack. Implies -b',
390
+ proc: lambda { |v| @options[:action] = :configure}
391
+ option :status,
392
+ short: '-s',
393
+ long: '--status',
394
+ description: 'Run pre-defined ansible playbooks against the given stack. Implies -b',
395
+ proc: lambda { |v| @options[:action] = :status}
396
+ option :template,
397
+ short: '-t',
398
+ long: '--template FILE',
399
+ description: "Specify a template to run. Note that specific outputs are expected, so results may vary. Default #{::Aws::Cfn::Stacker::Application.defaultoptions[:template_file]}",
400
+ proc: lambda { |v| @options[:template_file] = v }
401
+ option :template_file,
402
+ long: '--template-file FILE',
403
+ description: "Specify a template to run. Note that specific outputs are expected, so results may vary. Default #{::Aws::Cfn::Stacker::Application.defaultoptions[:template_file]}"
404
+ option :template_file_alt,
405
+ long: '--template_file FILE',
406
+ description: "Specify a template to run. Note that specific outputs are expected, so results may vary. Default #{::Aws::Cfn::Stacker::Application.defaultoptions[:template_file]}",
407
+ proc: lambda { |v| @options[:template_file] = v }
408
+ option :template_url_alt,
409
+ long: '--template-url URL',
410
+ description: 'Specify the URL of a template stored in an S3 bucket to run. This method assumes the template has already been uploaded.',
411
+ proc: lambda { |v| @options[:template_url] = v }
412
+ option :template_url,
413
+ long: '--template_url URL',
414
+ description: 'Specify the URL of a template stored in an S3 bucket to run. This method assumes the template has already been uploaded.'
415
+ option :use_s3,
416
+ long: '--use_s3',
417
+ description: 'Use an S3 bucket to upload the template to before attempting stack run. This method assumes the template needs to be uploaded and may overwrite a key with the same name.'
418
+ option :ssh_keyfile,
419
+ short: '-k',
420
+ long: '--keyfile FILE',
421
+ description: 'The path to the SSH key file to use for SSH to hosts'
422
+ option :ssh_user,
423
+ long: '--ssh_user USER',
424
+ description: 'SSH User name'
425
+ option :ssh_user_bastion,
426
+ long: '--ssh_user_bastion USER',
427
+ description: 'Bastion SSH User name'
428
+ option :initial_setup,
429
+ short: '-i',
430
+ long: '--initial_setup FILE',
431
+ description: 'An initial setup file for Ansible'
432
+ option :output_path,
433
+ short: '-o',
434
+ long: '--output PATH',
435
+ description: 'Output folder for the Ansible build configuration'
436
+ option :roles_path,
437
+ short: '-r',
438
+ long: '--roles_path PATH',
439
+ description: 'Output folder for the Ansible build configuration'
440
+ option :progress,
441
+ short: '-p',
442
+ long: '--progress',
443
+ description: 'Print a progress indicator during stack create/update/delete.'
444
+ option :log_file_alt,
445
+ long: '--log-file PATH',
446
+ description: 'Log destination file',
447
+ proc: lambda { |v| @options[:log_file] = v }
448
+ option :log_file,
449
+ long: '--log_file PATH',
450
+ description: 'Log destination file'
451
+ option :log_level_alt,
452
+ long: '--log-level LEVEL',
453
+ description: 'Logging level',
454
+ proc: lambda{|v|
455
+ if ::Aws::Cfn::Stacker::Application.loglevels.include? v.to_sym
456
+ v.to_sym
457
+ else
458
+ level = ::Aws::Cfn::Stacker::Application.loglevels.select{|l| l.to_s.match(%r(^#{v}))}
459
+ unless level.size > 0
460
+ raise ::OptionParser::InvalidOption.new("Invalid log level: #{v}. Valid levels are #{::Aws::Cfn::Stacker::Application.loglevels.ai}")
461
+ end
462
+ level[0].to_sym
463
+ end
464
+ }
465
+ option :log_level,
466
+ long: '--log_level LEVEL',
467
+ description: 'Logging level',
468
+ proc: lambda{|v|
469
+ if ::Aws::Cfn::Stacker::Application.loglevels.include? v.to_sym
470
+ v.to_sym
471
+ else
472
+ level = ::Aws::Cfn::Stacker::Application.loglevels.select{|l| l.to_s.match(%r(^#{v}))}
473
+ unless level.size > 0
474
+ raise ::OptionParser::InvalidOption.new("Invalid log level: #{v}. Valid levels are #{::Aws::Cfn::Stacker::Application.loglevels.ai}")
475
+ end
476
+ level[0].to_sym
477
+ end
478
+ }
479
+ option :report_config_alt,
480
+ long: '--report-config',
481
+ description: 'Report Configuration',
482
+ proc: lambda { |v| @options[:report_config] = true }
483
+ option :report_config,
484
+ long: '--report-config',
485
+ description: 'Report Configuration'
486
+
487
+ end # included
488
+ # ------------------------------------------------------------------------------------------------------------
489
+
490
+ end
491
+
492
+ end
493
+ end
494
+ end
495
+ end
@@ -0,0 +1,46 @@
1
+ module Aws
2
+ module Cfn
3
+ module Stacker
4
+ module Patches
5
+
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ class ::TrueClass
12
+ def to_rb
13
+ to_s
14
+ end
15
+ def to_sym
16
+ :true
17
+ end
18
+ def yesno
19
+ "yes"
20
+ end
21
+ end
22
+
23
+ class ::FalseClass
24
+ def to_rb
25
+ to_s
26
+ end
27
+ def to_sym
28
+ :false
29
+ end
30
+ def yesno
31
+ "no"
32
+ end
33
+ end
34
+
35
+ module ::Logging
36
+ class << self
37
+ def levelnames=(lnames)
38
+ remove_const(:LNAMES)
39
+ const_set(:LNAMES, lnames)
40
+ end
41
+ def levelnames()
42
+ LNAMES
43
+ end
44
+ end
45
+ end
46
+