stack_master 1.6.0-x64-mingw32

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +548 -0
  3. data/bin/stack_master +17 -0
  4. data/lib/stack_master.rb +159 -0
  5. data/lib/stack_master/aws_driver/cloud_formation.rb +41 -0
  6. data/lib/stack_master/aws_driver/s3.rb +68 -0
  7. data/lib/stack_master/change_set.rb +109 -0
  8. data/lib/stack_master/cli.rb +208 -0
  9. data/lib/stack_master/command.rb +57 -0
  10. data/lib/stack_master/commands/apply.rb +221 -0
  11. data/lib/stack_master/commands/delete.rb +53 -0
  12. data/lib/stack_master/commands/diff.rb +31 -0
  13. data/lib/stack_master/commands/events.rb +39 -0
  14. data/lib/stack_master/commands/init.rb +111 -0
  15. data/lib/stack_master/commands/list_stacks.rb +20 -0
  16. data/lib/stack_master/commands/outputs.rb +31 -0
  17. data/lib/stack_master/commands/resources.rb +33 -0
  18. data/lib/stack_master/commands/status.rb +46 -0
  19. data/lib/stack_master/commands/terminal_helper.rb +28 -0
  20. data/lib/stack_master/commands/validate.rb +17 -0
  21. data/lib/stack_master/config.rb +133 -0
  22. data/lib/stack_master/ctrl_c.rb +4 -0
  23. data/lib/stack_master/paged_response_accumulator.rb +29 -0
  24. data/lib/stack_master/parameter_loader.rb +49 -0
  25. data/lib/stack_master/parameter_resolver.rb +98 -0
  26. data/lib/stack_master/parameter_resolvers/ami_finder.rb +36 -0
  27. data/lib/stack_master/parameter_resolvers/env.rb +18 -0
  28. data/lib/stack_master/parameter_resolvers/latest_ami.rb +19 -0
  29. data/lib/stack_master/parameter_resolvers/latest_ami_by_tags.rb +18 -0
  30. data/lib/stack_master/parameter_resolvers/parameter_store.rb +31 -0
  31. data/lib/stack_master/parameter_resolvers/secret.rb +52 -0
  32. data/lib/stack_master/parameter_resolvers/security_group.rb +22 -0
  33. data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +31 -0
  34. data/lib/stack_master/parameter_resolvers/stack_output.rb +76 -0
  35. data/lib/stack_master/prompter.rb +21 -0
  36. data/lib/stack_master/resolver_array.rb +35 -0
  37. data/lib/stack_master/security_group_finder.rb +28 -0
  38. data/lib/stack_master/sns_topic_finder.rb +26 -0
  39. data/lib/stack_master/sparkle_formation/compile_time/allowed_pattern_validator.rb +35 -0
  40. data/lib/stack_master/sparkle_formation/compile_time/allowed_values_validator.rb +37 -0
  41. data/lib/stack_master/sparkle_formation/compile_time/definitions_validator.rb +33 -0
  42. data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +32 -0
  43. data/lib/stack_master/sparkle_formation/compile_time/max_length_validator.rb +36 -0
  44. data/lib/stack_master/sparkle_formation/compile_time/max_size_validator.rb +36 -0
  45. data/lib/stack_master/sparkle_formation/compile_time/min_length_validator.rb +36 -0
  46. data/lib/stack_master/sparkle_formation/compile_time/min_size_validator.rb +36 -0
  47. data/lib/stack_master/sparkle_formation/compile_time/number_validator.rb +35 -0
  48. data/lib/stack_master/sparkle_formation/compile_time/parameters_validator.rb +27 -0
  49. data/lib/stack_master/sparkle_formation/compile_time/state_builder.rb +32 -0
  50. data/lib/stack_master/sparkle_formation/compile_time/string_validator.rb +33 -0
  51. data/lib/stack_master/sparkle_formation/compile_time/value_builder.rb +40 -0
  52. data/lib/stack_master/sparkle_formation/compile_time/value_validator.rb +40 -0
  53. data/lib/stack_master/sparkle_formation/compile_time/value_validator_factory.rb +41 -0
  54. data/lib/stack_master/sparkle_formation/template_file.rb +115 -0
  55. data/lib/stack_master/stack.rb +105 -0
  56. data/lib/stack_master/stack_definition.rb +103 -0
  57. data/lib/stack_master/stack_differ.rb +111 -0
  58. data/lib/stack_master/stack_events/fetcher.rb +38 -0
  59. data/lib/stack_master/stack_events/presenter.rb +27 -0
  60. data/lib/stack_master/stack_events/streamer.rb +68 -0
  61. data/lib/stack_master/stack_states.rb +34 -0
  62. data/lib/stack_master/stack_status.rb +61 -0
  63. data/lib/stack_master/template_compiler.rb +30 -0
  64. data/lib/stack_master/template_compilers/cfndsl.rb +13 -0
  65. data/lib/stack_master/template_compilers/json.rb +22 -0
  66. data/lib/stack_master/template_compilers/sparkle_formation.rb +71 -0
  67. data/lib/stack_master/template_compilers/yaml.rb +14 -0
  68. data/lib/stack_master/template_utils.rb +31 -0
  69. data/lib/stack_master/test_driver/cloud_formation.rb +193 -0
  70. data/lib/stack_master/test_driver/s3.rb +34 -0
  71. data/lib/stack_master/testing.rb +9 -0
  72. data/lib/stack_master/utils.rb +50 -0
  73. data/lib/stack_master/validator.rb +33 -0
  74. data/lib/stack_master/version.rb +3 -0
  75. metadata +457 -0
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'stack_master'
5
+
6
+ if ENV['STUB_AWS'] == 'true'
7
+ require 'stack_master/testing'
8
+ end
9
+
10
+ trap("SIGINT") { raise StackMaster::CtrlC }
11
+
12
+ begin
13
+ result = StackMaster::CLI.new(ARGV.dup).execute!
14
+ exit !!result
15
+ rescue StackMaster::CtrlC
16
+ StackMaster.stdout.puts "Exiting..."
17
+ end
@@ -0,0 +1,159 @@
1
+ require 'commander'
2
+ require 'yaml'
3
+ require "aws-sdk-cloudformation"
4
+ require "aws-sdk-ec2"
5
+ require "aws-sdk-s3"
6
+ require "aws-sdk-sns"
7
+ require "aws-sdk-ssm"
8
+ require "colorize"
9
+ require 'active_support/core_ext/string'
10
+ require 'multi_json'
11
+
12
+ MultiJson.use :json_gem
13
+
14
+ module StackMaster
15
+ extend self
16
+
17
+ autoload :Initializable, 'stack_master/utils/initializable'
18
+ autoload :ChangeSet, 'stack_master/change_set'
19
+ autoload :CLI, 'stack_master/cli'
20
+ autoload :CtrlC, 'stack_master/ctrl_c'
21
+ autoload :Command, 'stack_master/command'
22
+ autoload :VERSION, 'stack_master/version'
23
+ autoload :Stack, 'stack_master/stack'
24
+ autoload :Prompter, 'stack_master/prompter'
25
+ autoload :StackStates, 'stack_master/stack_states'
26
+ autoload :StackStatus, 'stack_master/stack_status'
27
+ autoload :SnsTopicFinder, 'stack_master/sns_topic_finder'
28
+ autoload :SecurityGroupFinder, 'stack_master/security_group_finder'
29
+ autoload :ParameterLoader, 'stack_master/parameter_loader'
30
+ autoload :ParameterResolver, 'stack_master/parameter_resolver'
31
+ autoload :ResolverArray, 'stack_master/resolver_array'
32
+ autoload :Resolver, 'stack_master/resolver_array'
33
+ autoload :Utils, 'stack_master/utils'
34
+ autoload :TemplateUtils, 'stack_master/template_utils'
35
+ autoload :Config, 'stack_master/config'
36
+ autoload :PagedResponseAccumulator, 'stack_master/paged_response_accumulator'
37
+ autoload :StackDefinition, 'stack_master/stack_definition'
38
+ autoload :TemplateCompiler, 'stack_master/template_compiler'
39
+
40
+ autoload :StackDiffer, 'stack_master/stack_differ'
41
+ autoload :Validator, 'stack_master/validator'
42
+
43
+ require 'stack_master/template_compilers/sparkle_formation'
44
+ require 'stack_master/template_compilers/json'
45
+ require 'stack_master/template_compilers/yaml'
46
+ require 'stack_master/template_compilers/cfndsl'
47
+
48
+ module Commands
49
+ autoload :TerminalHelper, 'stack_master/commands/terminal_helper'
50
+ autoload :Apply, 'stack_master/commands/apply'
51
+ autoload :Events, 'stack_master/commands/events'
52
+ autoload :Outputs, 'stack_master/commands/outputs'
53
+ autoload :Init, 'stack_master/commands/init'
54
+ autoload :Diff, 'stack_master/commands/diff'
55
+ autoload :ListStacks, 'stack_master/commands/list_stacks'
56
+ autoload :Validate, 'stack_master/commands/validate'
57
+ autoload :Resources, 'stack_master/commands/resources'
58
+ autoload :Delete, 'stack_master/commands/delete'
59
+ autoload :Status, 'stack_master/commands/status'
60
+ end
61
+
62
+ module ParameterResolvers
63
+ autoload :AmiFinder, 'stack_master/parameter_resolvers/ami_finder'
64
+ autoload :StackOutput, 'stack_master/parameter_resolvers/stack_output'
65
+ autoload :Secret, 'stack_master/parameter_resolvers/secret'
66
+ autoload :SnsTopicName, 'stack_master/parameter_resolvers/sns_topic_name'
67
+ autoload :SecurityGroup, 'stack_master/parameter_resolvers/security_group'
68
+ autoload :LatestAmiByTags, 'stack_master/parameter_resolvers/latest_ami_by_tags'
69
+ autoload :LatestAmi, 'stack_master/parameter_resolvers/latest_ami'
70
+ autoload :Env, 'stack_master/parameter_resolvers/env'
71
+ autoload :ParameterStore, 'stack_master/parameter_resolvers/parameter_store'
72
+ end
73
+
74
+ module AwsDriver
75
+ autoload :CloudFormation, 'stack_master/aws_driver/cloud_formation'
76
+ autoload :S3, 'stack_master/aws_driver/s3'
77
+ end
78
+
79
+ module TestDriver
80
+ autoload :CloudFormation, 'stack_master/test_driver/cloud_formation'
81
+ autoload :S3, 'stack_master/test_driver/s3'
82
+ end
83
+
84
+ module StackEvents
85
+ autoload :Fetcher, 'stack_master/stack_events/fetcher'
86
+ autoload :Presenter, 'stack_master/stack_events/presenter'
87
+ autoload :Streamer, 'stack_master/stack_events/streamer'
88
+ end
89
+
90
+ def interactive?
91
+ !non_interactive?
92
+ end
93
+
94
+ def non_interactive?
95
+ @non_interactive
96
+ end
97
+ @non_interactive = false
98
+
99
+ def non_interactive!
100
+ @non_interactive = true
101
+ end
102
+
103
+ def debug!
104
+ @debug = true
105
+ end
106
+ @debug = false
107
+
108
+ def debug?
109
+ @debug
110
+ end
111
+
112
+ def debug(message)
113
+ return unless debug?
114
+ stderr.puts "[DEBUG] #{message}".colorize(:green)
115
+ end
116
+
117
+ attr_accessor :non_interactive_answer
118
+ @non_interactive_answer = 'y'
119
+
120
+ def base_dir
121
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
122
+ end
123
+
124
+ def cloud_formation_driver
125
+ @cloud_formation_driver ||= AwsDriver::CloudFormation.new
126
+ end
127
+
128
+ def cloud_formation_driver=(value)
129
+ @cloud_formation_driver = value
130
+ end
131
+
132
+ def s3_driver
133
+ @s3_driver ||= AwsDriver::S3.new
134
+ end
135
+
136
+ def s3_driver=(value)
137
+ @s3_driver = value
138
+ end
139
+
140
+ def stdout
141
+ @stdout || $stdout
142
+ end
143
+
144
+ def stdout=(io)
145
+ @stdout = io
146
+ end
147
+
148
+ def stdin
149
+ $stdin
150
+ end
151
+
152
+ def stderr
153
+ @stderr || $stderr
154
+ end
155
+
156
+ def stderr=(io)
157
+ @stderr = io
158
+ end
159
+ end
@@ -0,0 +1,41 @@
1
+ module StackMaster
2
+ module AwsDriver
3
+ class CloudFormation
4
+ extend Forwardable
5
+
6
+ def region
7
+ @region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region
8
+ end
9
+
10
+ def set_region(value)
11
+ if region != value
12
+ @region = value
13
+ @cf = nil
14
+ end
15
+ end
16
+
17
+ def_delegators :cf, :create_change_set,
18
+ :describe_change_set,
19
+ :execute_change_set,
20
+ :delete_change_set,
21
+ :delete_stack,
22
+ :cancel_update_stack,
23
+ :describe_stack_resources,
24
+ :get_template,
25
+ :get_stack_policy,
26
+ :set_stack_policy,
27
+ :describe_stack_events,
28
+ :update_stack,
29
+ :create_stack,
30
+ :validate_template,
31
+ :describe_stacks
32
+
33
+ private
34
+
35
+ def cf
36
+ @cf ||= Aws::CloudFormation::Client.new(region: region, retry_limit: 10)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,68 @@
1
+ require 'digest/md5'
2
+
3
+ module StackMaster
4
+ module AwsDriver
5
+ class S3ConfigurationError < StandardError; end
6
+
7
+ class S3
8
+ def set_region(region)
9
+ @region = region
10
+ @s3 = nil
11
+ end
12
+
13
+ def upload_files(bucket: nil, prefix: nil, region: nil, files: {})
14
+ raise StackMaster::AwsDriver::S3ConfigurationError, 'A bucket must be specified in order to use S3' unless bucket
15
+
16
+ return if files.empty?
17
+
18
+ s3 = new_s3_client(region: region)
19
+
20
+ current_objects = s3.list_objects(
21
+ prefix: prefix,
22
+ bucket: bucket
23
+ ).map(&:contents).flatten.inject({}){|h,obj|
24
+ h.merge(obj.key => obj)
25
+ }
26
+
27
+ StackMaster.stdout.puts "Uploading files to S3:"
28
+
29
+ files.each do |template, file|
30
+ body = file.fetch(:body)
31
+ path = file.fetch(:path)
32
+ object_key = template.dup
33
+ object_key.prepend("#{prefix}/") if prefix
34
+ compiled_template_md5 = Digest::MD5.hexdigest(body).to_s
35
+ s3_md5 = current_objects[object_key] ? current_objects[object_key].etag.gsub("\"", '') : nil
36
+
37
+ next if compiled_template_md5 == s3_md5
38
+ s3_uri = "s3://#{bucket}/#{object_key}"
39
+ StackMaster.stdout.print "- #{File.basename(path)} => #{s3_uri} "
40
+
41
+ s3.put_object(
42
+ bucket: bucket,
43
+ key: object_key,
44
+ body: body,
45
+ metadata: { md5: compiled_template_md5 }
46
+ )
47
+ StackMaster.stdout.puts "done."
48
+ end
49
+ end
50
+
51
+ def url(bucket:, prefix:, region:, template:)
52
+ if region == 'us-east-1'
53
+ ["https://s3.amazonaws.com", bucket, prefix, template].compact.join('/')
54
+ elsif region.start_with? "cn-"
55
+ ["https://s3.#{region}.amazonaws.com.cn", bucket, prefix, template].compact.join('/')
56
+ else
57
+ ["https://s3-#{region}.amazonaws.com", bucket, prefix, template].compact.join('/')
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def new_s3_client(region: nil)
64
+ Aws::S3::Client.new(region: region || @region)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,109 @@
1
+ module StackMaster
2
+ class ChangeSet
3
+ END_STATES = [
4
+ 'CREATE_COMPLETE',
5
+ 'DELETE_COMPLETE',
6
+ 'FAILED'
7
+ ]
8
+
9
+ def self.generate_change_set_name(stack_name)
10
+ stack_name + '-StackMaster' + Time.now.strftime('%Y-%m-%d-%H%M-%s')
11
+ end
12
+
13
+ def self.create(create_options)
14
+ cf = StackMaster.cloud_formation_driver
15
+ change_set_name = generate_change_set_name(create_options.fetch(:stack_name))
16
+ change_set_id = cf.create_change_set(create_options.merge(change_set_name: change_set_name)).id
17
+ find(change_set_id)
18
+ end
19
+
20
+ def self.find(id)
21
+ begin
22
+ response = PagedResponseAccumulator.call(cf, :describe_change_set, { change_set_name: id }, :changes)
23
+ end while !END_STATES.include?(response.status)
24
+ new(response)
25
+ end
26
+
27
+ def self.delete(id)
28
+ cf.delete_change_set(change_set_name: id)
29
+ end
30
+
31
+ def self.execute(id, stack_name)
32
+ cf.execute_change_set(change_set_name: id,
33
+ stack_name: stack_name)
34
+ end
35
+
36
+ def self.cf
37
+ StackMaster.cloud_formation_driver
38
+ end
39
+
40
+ def initialize(describe_change_set_response)
41
+ @response = describe_change_set_response
42
+ end
43
+
44
+ def display(io)
45
+ io.puts <<-EOL
46
+
47
+ ========================================
48
+ Proposed change set:
49
+ EOL
50
+ @response.changes.each do |change|
51
+ display_resource_change(io, change.resource_change)
52
+ end
53
+ io.puts "========================================"
54
+ end
55
+
56
+ def failed?
57
+ @response.status == 'FAILED'
58
+ end
59
+
60
+ def status_reason
61
+ @response.status_reason
62
+ end
63
+
64
+ def id
65
+ @response.change_set_id
66
+ end
67
+
68
+ private
69
+
70
+ def display_resource_change(io, resource_change)
71
+ action_name = if resource_change.replacement == 'True'
72
+ 'Replace'
73
+ else
74
+ resource_change.action
75
+ end
76
+ message = "#{action_name} #{resource_change.resource_type} #{resource_change.logical_resource_id}"
77
+ color = action_color(action_name)
78
+ io.puts message.colorize(color)
79
+ resource_change.details.each do |detail|
80
+ display_resource_change_detail(io, action_name, color, detail)
81
+ end
82
+ end
83
+
84
+ def display_resource_change_detail(io, action_name, color, detail)
85
+ target_name = [detail.target.attribute, detail.target.name].compact.join('.')
86
+ detail_messages = [target_name]
87
+ if action_name == 'Replace'
88
+ detail_messages << "#{detail.target.requires_recreation} requires recreation"
89
+ end
90
+ triggered_by = [detail.change_source, detail.causing_entity].compact.join('.')
91
+ if detail.evaluation != 'Static'
92
+ triggered_by << "(#{detail.evaluation})"
93
+ end
94
+ detail_messages << "Triggered by: #{triggered_by}"
95
+ io.puts "- #{detail_messages.join('. ')}. ".colorize(color)
96
+ end
97
+
98
+ def action_color(action_name)
99
+ case action_name
100
+ when 'Add'
101
+ :green
102
+ when 'Modify'
103
+ :yellow
104
+ when 'Remove', 'Replace'
105
+ :red
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,208 @@
1
+ require 'commander'
2
+ require 'table_print'
3
+
4
+ module StackMaster
5
+ class CLI
6
+ include Commander::Methods
7
+
8
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
9
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
10
+ Commander::Runner.instance_variable_set('@singleton', Commander::Runner.new(argv))
11
+ StackMaster.stdout = @stdout
12
+ StackMaster.stderr = @stderr
13
+ TablePrint::Config.io = StackMaster.stdout
14
+ end
15
+
16
+ def default_config_file
17
+ "stack_master.yml"
18
+ end
19
+
20
+ def execute!
21
+ program :name, 'StackMaster'
22
+ program :version, StackMaster::VERSION
23
+ program :description, 'AWS Stack Management'
24
+
25
+ global_option '-c', '--config FILE', String, 'Config file to use'
26
+ global_option '--changed', 'filter stack selection to only ones that have changed'
27
+ global_option '-y', '--yes', 'Run in non-interactive mode answering yes to any prompts' do
28
+ StackMaster.non_interactive!
29
+ StackMaster.non_interactive_answer = 'y'
30
+ end
31
+ global_option '-n', '--no', 'Run in non-interactive mode answering no to any prompts' do
32
+ StackMaster.non_interactive!
33
+ StackMaster.non_interactive_answer = 'n'
34
+ end
35
+ global_option '-d', '--debug', 'Run in debug mode' do
36
+ StackMaster.debug!
37
+ end
38
+
39
+ command :apply do |c|
40
+ c.syntax = 'stack_master apply [region_or_alias] [stack_name]'
41
+ c.summary = 'Creates or updates a stack'
42
+ c.description = "Creates or updates a stack. Shows a diff of the proposed stack's template and parameters. Tails stack events until CloudFormation has completed."
43
+ c.example 'update a stack named myapp-vpc in us-east-1', 'stack_master apply us-east-1 myapp-vpc'
44
+ c.option '--on-failure ACTION', String, "Action to take on CREATE_FAILURE. Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. Default: ROLLBACK\nNote: You cannot use this option with Serverless Application Model (SAM) templates."
45
+ c.action do |args, options|
46
+ options.defaults config: default_config_file
47
+ execute_stacks_command(StackMaster::Commands::Apply, args, options)
48
+ end
49
+ end
50
+
51
+ command :outputs do |c|
52
+ c.syntax = 'stack_master outputs [region_or_alias] [stack_name]'
53
+ c.summary = 'Displays outputs for a stack'
54
+ c.description = "Displays outputs for a stack"
55
+ c.action do |args, options|
56
+ options.defaults config: default_config_file
57
+ execute_stacks_command(StackMaster::Commands::Outputs, args, options)
58
+ end
59
+ end
60
+
61
+ command :init do |c|
62
+ c.syntax = 'stack_master init [region_or_alias] [stack_name]'
63
+ c.summary = 'Initialises the expected directory structure and stack_master.yml file'
64
+ c.description = 'Initialises the expected directory structure and stack_master.yml file'
65
+ c.option('--overwrite', 'Overwrite existing files')
66
+ c.action do |args, options|
67
+ options.defaults config: default_config_file
68
+ unless args.size == 2
69
+ say "Invalid arguments. stack_master init [region] [stack_name]"
70
+ else
71
+ StackMaster::Commands::Init.perform(options.overwrite, *args)
72
+ end
73
+ end
74
+ end
75
+
76
+ command :diff do |c|
77
+ c.syntax = 'stack_master diff [region_or_alias] [stack_name]'
78
+ c.summary = "Shows a diff of the proposed stack's template and parameters"
79
+ c.description = "Shows a diff of the proposed stack's template and parameters"
80
+ c.example 'diff a stack named myapp-vpc in us-east-1', 'stack_master diff us-east-1 myapp-vpc'
81
+ c.action do |args, options|
82
+ options.defaults config: default_config_file
83
+ execute_stacks_command(StackMaster::Commands::Diff, args, options)
84
+ end
85
+ end
86
+
87
+ command :events do |c|
88
+ c.syntax = 'stack_master events [region_or_alias] [stack_name]'
89
+ c.summary = "Shows events for a stack"
90
+ c.description = "Shows events for a stack"
91
+ c.example 'show events for myapp-vpc in us-east-1', 'stack_master events us-east-1 myapp-vpc'
92
+ c.option '--number Integer', Integer, 'Number of recent events to show'
93
+ c.option '--all', 'Show all events'
94
+ c.option '--tail', 'Tail events'
95
+ c.action do |args, options|
96
+ options.defaults config: default_config_file
97
+ execute_stacks_command(StackMaster::Commands::Events, args, options)
98
+ end
99
+ end
100
+
101
+ command :resources do |c|
102
+ c.syntax = 'stack_master resources [region] [stack_name]'
103
+ c.summary = "Shows stack resources"
104
+ c.description = "Shows stack resources"
105
+ c.action do |args, options|
106
+ options.defaults config: default_config_file
107
+ execute_stacks_command(StackMaster::Commands::Resources, args, options)
108
+ end
109
+ end
110
+
111
+ command :list do |c|
112
+ c.syntax = 'stack_master list'
113
+ c.summary = 'List stack definitions'
114
+ c.description = 'List stack definitions'
115
+ c.action do |args, options|
116
+ options.defaults config: default_config_file
117
+ say "Invalid arguments." if args.size > 0
118
+ config = load_config(options.config)
119
+ StackMaster::Commands::ListStacks.perform(config)
120
+ end
121
+ end
122
+
123
+ command :validate do |c|
124
+ c.syntax = 'stack_master validate [region_or_alias] [stack_name]'
125
+ c.summary = 'Validate a template'
126
+ c.description = 'Validate a template'
127
+ c.example 'validate a stack named myapp-vpc in us-east-1', 'stack_master validate us-east-1 myapp-vpc'
128
+ c.action do |args, options|
129
+ options.defaults config: default_config_file
130
+ execute_stacks_command(StackMaster::Commands::Validate, args, options)
131
+ end
132
+ end
133
+
134
+ command :status do |c|
135
+ c.syntax = 'stack_master status'
136
+ c.summary = 'Check the current status stacks.'
137
+ c.description = 'Checks the status of all stacks defined in the stack_master.yml file. Warning this operation can be somewhat slow.'
138
+ c.example 'description', 'Check the status of all stack definitions'
139
+ c.action do |args, options|
140
+ options.defaults config: default_config_file
141
+ say "Invalid arguments. stack_master status" and return unless args.size == 0
142
+ config = load_config(options.config)
143
+ StackMaster::Commands::Status.perform(config)
144
+ end
145
+ end
146
+
147
+ command :delete do |c|
148
+ c.syntax = 'stack_master delete [region] [stack_name]'
149
+ c.summary = 'Delete an existing stack'
150
+ c.description = 'Deletes a stack. The stack does not necessarily have to appear in the stack_master.yml file.'
151
+ c.example 'description', 'Delete a stack'
152
+ c.action do |args, options|
153
+ options.default config: default_config_file
154
+ unless args.size == 2
155
+ say "Invalid arguments. stack_master delete [region] [stack_name]"
156
+ return
157
+ end
158
+
159
+ # Because delete can work without a stack_master.yml
160
+ if options.config and File.file?(options.config)
161
+ config = load_config(options.config)
162
+ region = Utils.underscore_to_hyphen(config.unalias_region(args[0]))
163
+ else
164
+ region = args[0]
165
+ end
166
+
167
+ StackMaster.cloud_formation_driver.set_region(region)
168
+ StackMaster::Commands::Delete.perform(region, args[1])
169
+ end
170
+ end
171
+
172
+ run!
173
+ end
174
+
175
+ def load_config(file)
176
+ stack_file = file || default_config_file
177
+ StackMaster::Config.load!(stack_file)
178
+ rescue Errno::ENOENT => e
179
+ say "Failed to load config file #{stack_file}"
180
+ exit 1
181
+ end
182
+
183
+ def execute_stacks_command(command, args, options)
184
+ command_results = []
185
+ config = load_config(options.config)
186
+ args = [nil, nil] if args.size == 0
187
+ args.each_slice(2) do |aliased_region, stack_name|
188
+ region = Utils.underscore_to_hyphen(config.unalias_region(aliased_region))
189
+ stack_name = Utils.underscore_to_hyphen(stack_name)
190
+ stack_definitions = config.filter(region, stack_name)
191
+ if stack_definitions.empty?
192
+ StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
193
+ end
194
+ stack_definitions = stack_definitions.select do |stack_definition|
195
+ StackStatus.new(config, stack_definition).changed?
196
+ end if options.changed
197
+ stack_definitions.each do |stack_definition|
198
+ StackMaster.cloud_formation_driver.set_region(stack_definition.region)
199
+ StackMaster.stdout.puts "Executing #{command.command_name} on #{stack_definition.stack_name} in #{stack_definition.region}"
200
+ command_results.push command.perform(config, stack_definition, options).success?
201
+ end
202
+ end
203
+
204
+ # Return success/failure
205
+ command_results.all?
206
+ end
207
+ end
208
+ end