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,57 @@
1
+ module StackMaster
2
+ module Command
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.prepend Perform
6
+ end
7
+
8
+ module ClassMethods
9
+ def perform(*args)
10
+ new(*args).tap do |command|
11
+ command.perform
12
+ end
13
+ end
14
+
15
+ def command_name
16
+ name.split('::').last.underscore
17
+ end
18
+ end
19
+
20
+ module Perform
21
+ def perform
22
+ catch(:halt) do
23
+ super
24
+ end
25
+ rescue Aws::CloudFormation::Errors::ServiceError, TemplateCompiler::TemplateCompilationFailed => e
26
+ failed error_message(e)
27
+ end
28
+ end
29
+
30
+ def success?
31
+ @failed != true
32
+ end
33
+
34
+ private
35
+
36
+ def error_message(e)
37
+ msg = "#{e.class} #{e.message}"
38
+ msg << "\n Caused by: #{e.cause.class} #{e.cause.message}" if e.cause
39
+ msg
40
+ end
41
+
42
+ def failed(message = nil)
43
+ StackMaster.stderr.puts(message) if message
44
+ @failed = true
45
+ end
46
+
47
+ def failed!(message = nil)
48
+ failed(message)
49
+ halt!
50
+ end
51
+
52
+ def halt!(message = nil)
53
+ StackMaster.stdout.puts(message) if message
54
+ throw :halt
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,221 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Apply
4
+ include Command
5
+ include Commander::UI
6
+ include StackMaster::Prompter
7
+ TEMPLATE_TOO_LARGE_ERROR_MESSAGE = 'The (space compressed) stack is larger than the limit set by AWS. See http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html'.freeze
8
+
9
+ def initialize(config, stack_definition, options = Commander::Command::Options.new)
10
+ @config = config
11
+ @s3_config = stack_definition.s3
12
+ @stack_definition = stack_definition
13
+ @from_time = Time.now
14
+ @options = options
15
+ @options.on_failure ||= nil
16
+ end
17
+
18
+ def perform
19
+ diff_stacks
20
+ ensure_valid_parameters!
21
+ ensure_valid_template_body_size!
22
+ create_or_update_stack
23
+ tail_stack_events
24
+ set_stack_policy
25
+ end
26
+
27
+ private
28
+
29
+ def cf
30
+ @cf ||= StackMaster.cloud_formation_driver
31
+ end
32
+
33
+ def s3
34
+ @s3 ||= StackMaster.s3_driver
35
+ end
36
+
37
+ def stack
38
+ @stack ||= Stack.find(region, stack_name)
39
+ end
40
+
41
+ def proposed_stack
42
+ @proposed_stack ||= Stack.generate(@stack_definition, @config)
43
+ end
44
+
45
+ def stack_exists?
46
+ !stack.nil?
47
+ end
48
+
49
+ def use_s3?
50
+ !@s3_config.empty?
51
+ end
52
+
53
+ def diff_stacks
54
+ StackDiffer.new(proposed_stack, stack).output_diff
55
+ end
56
+
57
+ def create_or_update_stack
58
+ if stack_exists?
59
+ update_stack
60
+ else
61
+ create_stack
62
+ end
63
+ end
64
+
65
+ def create_stack
66
+ upload_files
67
+ if use_change_set?
68
+ create_stack_by_change_set
69
+ else
70
+ create_stack_directly
71
+ end
72
+ end
73
+
74
+ def use_change_set?
75
+ @options.on_failure.nil?
76
+ end
77
+
78
+ def create_stack_by_change_set
79
+ begin
80
+ @change_set = ChangeSet.create(stack_options.merge(change_set_type: 'CREATE'))
81
+ if @change_set.failed?
82
+ ChangeSet.delete(@change_set.id)
83
+ halt!(@change_set.status_reason)
84
+ end
85
+
86
+ @change_set.display(StackMaster.stdout)
87
+ unless ask?('Create stack (y/n)? ')
88
+ cf.delete_stack(stack_name: stack_name)
89
+ halt!('Stack creation aborted')
90
+ end
91
+ rescue StackMaster::CtrlC
92
+ cf.delete_stack(stack_name: stack_name)
93
+ raise
94
+ end
95
+
96
+ execute_change_set
97
+ end
98
+
99
+ def create_stack_directly
100
+ failed!('Stack creation aborted') unless ask?('Create stack (y/n)? ')
101
+ cf.create_stack(stack_options.merge(on_failure: @options.on_failure))
102
+ end
103
+
104
+ def ask_to_cancel_stack_update
105
+ if ask?("Cancel stack update?")
106
+ StackMaster.stdout.puts "Attempting to cancel stack update"
107
+ cf.cancel_update_stack(stack_name: stack_name)
108
+ tail_stack_events
109
+ end
110
+ end
111
+
112
+ def update_stack
113
+ upload_files
114
+ @change_set = ChangeSet.create(stack_options)
115
+ if @change_set.failed?
116
+ ChangeSet.delete(@change_set.id)
117
+ halt!(@change_set.status_reason)
118
+ end
119
+
120
+ @change_set.display(StackMaster.stdout)
121
+ unless ask?("Apply change set (y/n)? ")
122
+ ChangeSet.delete(@change_set.id)
123
+ halt! "Stack update aborted"
124
+ end
125
+ execute_change_set
126
+ end
127
+
128
+ def upload_files
129
+ return unless use_s3?
130
+ s3.upload_files(s3_options)
131
+ end
132
+
133
+ def template_method
134
+ use_s3? ? :template_url : :template_body
135
+ end
136
+
137
+ def template_value
138
+ if use_s3?
139
+ s3.url(bucket: @s3_config['bucket'], prefix: @s3_config['prefix'], region: @s3_config['region'], template: @stack_definition.s3_template_file_name)
140
+ else
141
+ proposed_stack.template
142
+ end
143
+ end
144
+
145
+ def files_to_upload
146
+ return {} unless use_s3?
147
+ @stack_definition.s3_files.tap do |files|
148
+ files[@stack_definition.s3_template_file_name] = {
149
+ path: @stack_definition.template_file_path,
150
+ body: proposed_stack.template
151
+ }
152
+ end
153
+ end
154
+
155
+ def stack_options
156
+ {
157
+ stack_name: stack_name,
158
+ parameters: proposed_stack.aws_parameters,
159
+ tags: proposed_stack.aws_tags,
160
+ capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
161
+ role_arn: proposed_stack.role_arn,
162
+ notification_arns: proposed_stack.notification_arns,
163
+ template_method => template_value
164
+ }
165
+ end
166
+
167
+ def s3_options
168
+ {
169
+ bucket: @s3_config['bucket'],
170
+ prefix: @s3_config['prefix'],
171
+ region: @s3_config['region'],
172
+ files: files_to_upload
173
+ }
174
+ end
175
+
176
+ def tail_stack_events
177
+ StackEvents::Streamer.stream(stack_name, region, io: StackMaster.stdout, from: @from_time)
178
+ rescue StackMaster::CtrlC
179
+ ask_to_cancel_stack_update
180
+ end
181
+
182
+ def execute_change_set
183
+ ChangeSet.execute(@change_set.id, stack_name)
184
+ rescue StackMaster::CtrlC
185
+ ask_to_cancel_stack_update
186
+ end
187
+
188
+ def ensure_valid_parameters!
189
+ if @proposed_stack.missing_parameters?
190
+ StackMaster.stderr.puts "Empty/blank parameters detected, ensure values exist for those parameters. Parameters will be read from the following locations:"
191
+ @stack_definition.parameter_files.each do |parameter_file|
192
+ StackMaster.stderr.puts " - #{parameter_file}"
193
+ end
194
+ halt!
195
+ end
196
+ end
197
+
198
+ def ensure_valid_template_body_size!
199
+ if proposed_stack.too_big?(use_s3?)
200
+ failed! TEMPLATE_TOO_LARGE_ERROR_MESSAGE
201
+ end
202
+ end
203
+
204
+ def set_stack_policy
205
+ current_policy = stack && stack.stack_policy_body
206
+ proposed_policy = proposed_stack.stack_policy_body
207
+ # No need to reset a stack policy if it's nil or not changed
208
+ return if proposed_policy.nil? || proposed_policy == current_policy
209
+ StackMaster.stdout.print 'Setting a stack policy...'
210
+ cf.set_stack_policy(
211
+ stack_name: stack_name,
212
+ stack_policy_body: proposed_policy
213
+ )
214
+ StackMaster.stdout.puts 'done.'
215
+ end
216
+
217
+ extend Forwardable
218
+ def_delegators :@stack_definition, :stack_name, :region
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,53 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Delete
4
+ include Command
5
+ include StackMaster::Prompter
6
+
7
+ def initialize(region, stack_name)
8
+ @region = region
9
+ @stack_name = stack_name
10
+ @from_time = Time.now
11
+ end
12
+
13
+ def perform
14
+
15
+ return unless check_exists
16
+
17
+ unless ask?("Really delete stack #{@stack_name} (y/n)? ")
18
+ StackMaster.stdout.puts "Stack update aborted"
19
+ return
20
+ end
21
+
22
+ delete_stack
23
+ tail_stack_events
24
+ end
25
+
26
+ private
27
+
28
+ def delete_stack
29
+ cf.delete_stack({stack_name: @stack_name})
30
+ end
31
+
32
+ def check_exists
33
+ cf.describe_stacks({stack_name: @stack_name})
34
+ true
35
+ rescue Aws::CloudFormation::Errors::ValidationError
36
+ StackMaster.stdout.puts "Stack does not exist"
37
+ false
38
+ end
39
+
40
+ def cf
41
+ StackMaster.cloud_formation_driver
42
+ end
43
+
44
+ def tail_stack_events
45
+ StackEvents::Streamer.stream(@stack_name, @region, io: StackMaster.stdout, from: @from_time)
46
+ StackMaster.stdout.puts "Stack deleted"
47
+ rescue Aws::CloudFormation::Errors::ValidationError
48
+ # Unfortunately the stack as a tendency of going away before we get the final delete event.
49
+ StackMaster.stdout.puts "Stack deleted"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Diff
4
+ include Command
5
+ include Commander::UI
6
+
7
+ def initialize(config, stack_definition, options = {})
8
+ @config = config
9
+ @stack_definition = stack_definition
10
+ end
11
+
12
+ def perform
13
+ StackMaster::StackDiffer.new(proposed_stack, stack).output_diff
14
+ end
15
+
16
+ private
17
+
18
+ def stack_definition
19
+ @stack_definition ||= @config.find_stack(@region, @stack_name)
20
+ end
21
+
22
+ def stack
23
+ @stack ||= Stack.find(@stack_definition.region, @stack_definition.stack_name)
24
+ end
25
+
26
+ def proposed_stack
27
+ @proposed_stack ||= Stack.generate(stack_definition, @config)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Events
4
+ include Command
5
+ include Commander::UI
6
+
7
+ def initialize(config, stack_definition, options = {})
8
+ @config = config
9
+ @stack_definition = stack_definition
10
+ @options = options
11
+ end
12
+
13
+ def perform
14
+ events = StackEvents::Fetcher.fetch(@stack_definition.stack_name, @stack_definition.region)
15
+ filter_events(events).each do |event|
16
+ StackEvents::Presenter.print_event(StackMaster.stdout, event)
17
+ end
18
+ if @options.tail
19
+ StackEvents::Streamer.stream(@stack_definition.stack_name, @stack_definition.region, io: StackMaster.stdout)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def filter_events(events)
26
+ if @options.all
27
+ events
28
+ else
29
+ n = @options.number || 25
30
+ from = events.count - n
31
+ if from < 0
32
+ from = 0
33
+ end
34
+ events[from..-1]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,111 @@
1
+ require "erb"
2
+
3
+ module StackMaster
4
+ module Commands
5
+ class Init
6
+ include Command
7
+
8
+ def initialize(overwrite, region, stack_name)
9
+ @overwrite = overwrite
10
+ @region = region
11
+ @stack_name = stack_name
12
+ end
13
+
14
+ def perform
15
+ if check_files
16
+ create_stack_master_yml
17
+ create_stack_json_yml
18
+ create_parameters_yml
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def check_files
25
+ @stack_master_filename = "stack_master.yml"
26
+ @stack_json_filename = "templates/#{@stack_name}.json"
27
+ @parameters_filename = File.join("parameters", "#{underscored_stack_name}.yml")
28
+ @region_parameters_filename = File.join("parameters", @region, "#{underscored_stack_name}.yml")
29
+
30
+ if !@overwrite
31
+ [@stack_master_filename, @stack_json_filename, @parameters_filename, @region_parameters_filename].each do |filename|
32
+ if File.exists?(filename)
33
+ StackMaster.stderr.puts("Aborting: #{filename} already exists. Use --overwrite to force overwriting file.")
34
+ return false
35
+ end
36
+ end
37
+ end
38
+ true
39
+ end
40
+
41
+ def create_stack_json_yml
42
+ StackMaster.stdout.puts "Writing #{@stack_json_filename}"
43
+ FileUtils.mkdir_p(File.dirname(@stack_json_filename))
44
+ IO.write(@stack_json_filename, stack_json_output)
45
+ end
46
+
47
+ def stack_json_output
48
+ render ERB.new(File.read(stack_json_template))
49
+ end
50
+
51
+ def stack_json_template
52
+ File.join(StackMaster.base_dir, "stacktemplates", "stack.json.erb")
53
+ end
54
+
55
+ def create_stack_master_yml
56
+ StackMaster.stdout.puts "Writing #{@stack_master_filename}"
57
+ IO.write("#{@stack_master_filename}", stack_master_yml_output)
58
+ end
59
+
60
+ def stack_master_yml_output
61
+ render ERB.new(File.read(stack_master_template))
62
+ end
63
+
64
+ def stack_master_template
65
+ File.join(StackMaster.base_dir, "stacktemplates", "stack_master.yml.erb")
66
+ end
67
+
68
+ def create_parameters_yml
69
+ StackMaster.stdout.puts "Writing #{@parameters_filename}"
70
+ StackMaster.stdout.puts "Writing #{@region_parameters_filename}"
71
+ FileUtils.mkdir_p("parameters/#{@region}")
72
+ IO.write(@parameters_filename, parameter_stack_name_yml_output)
73
+ IO.write(@region_parameters_filename, parameter_region_yml_output)
74
+ end
75
+
76
+ def parameter_stack_name_yml_output
77
+ File.read(parameter_stack_name_template)
78
+ end
79
+
80
+ def parameter_region_yml_output
81
+ File.read(parameter_region_template)
82
+ end
83
+
84
+ def parameter_stack_name_template
85
+ File.join(StackMaster.base_dir, "stacktemplates", "parameter_stack_name.yml")
86
+ end
87
+
88
+ def parameter_region_template
89
+ File.join(StackMaster.base_dir, "stacktemplates", "parameter_region.yml")
90
+ end
91
+
92
+ def underscored_stack_name
93
+ @stack_name.gsub('-', '_')
94
+ end
95
+
96
+ def render(renderer)
97
+ binding = InitBinding.new(region: @region, stack_name: @stack_name).get_binding
98
+ renderer.result(binding)
99
+ end
100
+
101
+ class InitBinding
102
+ def initialize(region:, stack_name:)
103
+ @region = region
104
+ @stack_name = stack_name
105
+ end
106
+
107
+ attr_reader :region, :stack_name
108
+ end
109
+ end
110
+ end
111
+ end