aws-cfn-stacker 0.0.1 → 0.0.5

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.
@@ -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
+