stack_master 1.6.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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,20 @@
1
+ require 'table_print'
2
+
3
+ module StackMaster
4
+ module Commands
5
+ class ListStacks
6
+ include Command
7
+ include Commander::UI
8
+ include StackMaster::Commands::TerminalHelper
9
+
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def perform
15
+ tp.set :max_width, self.window_size
16
+ tp @config.stacks, :region, :stack_name
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ require 'table_print'
2
+
3
+ module StackMaster
4
+ module Commands
5
+ class Outputs
6
+ include Command
7
+ include Commander::UI
8
+ include StackMaster::Commands::TerminalHelper
9
+
10
+ def initialize(config, stack_definition, options = {})
11
+ @config = config
12
+ @stack_definition = stack_definition
13
+ end
14
+
15
+ def perform
16
+ if stack
17
+ tp.set :max_width, self.window_size
18
+ tp stack.outputs, :output_key, :output_value, :description
19
+ else
20
+ StackMaster.stdout.puts "Stack doesn't exist"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def stack
27
+ @stack ||= Stack.find(@stack_definition.region, @stack_definition.stack_name)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Resources
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
+ if stack_resources
14
+ tp stack_resources, :logical_resource_id, :resource_type, :timestamp, :resource_status, :resource_status_reason, :description
15
+ else
16
+ StackMaster.stdout.puts "Stack doesn't exist"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def stack_resources
23
+ @stack_resources = cf.describe_stack_resources(stack_name: @stack_definition.stack_name).stack_resources
24
+ rescue Aws::CloudFormation::Errors::ValidationError
25
+ nil
26
+ end
27
+
28
+ def cf
29
+ @cf ||= StackMaster.cloud_formation_driver
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ require 'table_print'
2
+ require 'ruby-progressbar'
3
+
4
+ module StackMaster
5
+ module Commands
6
+ class Status
7
+ include Command
8
+ include StackMaster::Commands::TerminalHelper
9
+
10
+ def initialize(config, show_progress = true)
11
+ @config = config
12
+ @show_progress = show_progress
13
+ end
14
+
15
+ def perform
16
+ progress if @show_progress
17
+ status = @config.stacks.map do |stack_definition|
18
+ stack_status = StackStatus.new(@config, stack_definition)
19
+ progress.increment if @show_progress
20
+ {
21
+ region: stack_definition.region,
22
+ stack_name: stack_definition.stack_name,
23
+ stack_status: stack_status.status,
24
+ different: stack_status.changed_message,
25
+ }
26
+ end
27
+ tp.set :max_width, self.window_size
28
+ tp.set :io, StackMaster.stdout
29
+ tp status
30
+ StackMaster.stdout.puts " * No echo parameters can't be diffed"
31
+ end
32
+
33
+ private
34
+
35
+ def progress
36
+ @progress ||= ProgressBar.create(title: "Fetching stack information",
37
+ total: @config.stacks.size,
38
+ output: StackMaster.stdout)
39
+ end
40
+
41
+ def sort_params(hash)
42
+ hash.sort.to_h
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ require 'os'
2
+
3
+ module StackMaster
4
+ module Commands
5
+ module TerminalHelper
6
+ def window_size
7
+ size = ENV.fetch("COLUMNS") { OS.windows? ? windows_window_size : unix_window_size }
8
+
9
+ if size.nil? || size == ""
10
+ 80
11
+ else
12
+ size.to_i
13
+ end
14
+ end
15
+
16
+ def unix_window_size
17
+ `tput cols`.chomp
18
+ end
19
+
20
+ def windows_window_size
21
+ columns_regex = %r{^\s+Columns:\s+([0-9]+)$}
22
+ output = `mode con`
23
+ columns_line = output.split("\n").select { |line| line.match(columns_regex) }.last
24
+ columns_line.match(columns_regex)[1]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module StackMaster
2
+ module Commands
3
+ class Validate
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
+ failed unless Validator.valid?(@stack_definition, @config)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,133 @@
1
+ require 'deep_merge/rails_compat'
2
+ require 'active_support/core_ext/object/deep_dup'
3
+
4
+ module StackMaster
5
+ class Config
6
+ def self.load!(config_file = 'stack_master.yml')
7
+ resolved_config_file = search_up_and_chdir(config_file)
8
+ config = YAML.load(File.read(resolved_config_file))
9
+ base_dir = File.dirname(File.expand_path(resolved_config_file))
10
+ new(config, base_dir)
11
+ end
12
+
13
+ attr_accessor :stacks,
14
+ :base_dir,
15
+ :template_dir,
16
+ :stack_defaults,
17
+ :region_defaults,
18
+ :region_aliases,
19
+ :template_compilers,
20
+
21
+ def self.search_up_and_chdir(config_file)
22
+ return config_file unless File.dirname(config_file) == "."
23
+
24
+ dir = Dir.pwd
25
+ parent_dir = File.expand_path("..", Dir.pwd)
26
+ while parent_dir != dir && !File.exists?(File.join(dir, config_file))
27
+ dir = parent_dir
28
+ parent_dir = File.expand_path("..", dir)
29
+ end
30
+
31
+ File.join(dir, config_file)
32
+ end
33
+
34
+ def initialize(config, base_dir)
35
+ @config = config
36
+ @base_dir = base_dir
37
+ @template_dir = config.fetch('template_dir', nil)
38
+ @stack_defaults = config.fetch('stack_defaults', {})
39
+ @region_aliases = Utils.underscore_keys_to_hyphen(config.fetch('region_aliases', {}))
40
+ @region_to_aliases = @region_aliases.inject({}) do |hash, (key, value)|
41
+ hash[value] ||= []
42
+ hash[value] << key
43
+ hash
44
+ end
45
+ @region_defaults = normalise_region_defaults(config.fetch('region_defaults', {}))
46
+ @stacks = []
47
+ load_template_compilers(config)
48
+ load_config
49
+ end
50
+
51
+ def filter(region = nil, stack_name = nil)
52
+ @stacks.select do |s|
53
+ (region.blank? || s.region == region || s.region == region.gsub('_', '-')) &&
54
+ (stack_name.blank? || s.stack_name == stack_name || s.stack_name == stack_name.gsub('_', '-'))
55
+ end
56
+ end
57
+
58
+ def find_stack(region, stack_name)
59
+ filter(region, stack_name).first
60
+ end
61
+
62
+ def unalias_region(region)
63
+ @region_aliases.fetch(region) { region }
64
+ end
65
+
66
+ private
67
+ def load_template_compilers(config)
68
+ @template_compilers = {}
69
+ populate_template_compilers(config.fetch('template_compilers', {}))
70
+ merge_defaults_to_user_defined_compilers
71
+ end
72
+
73
+ def merge_defaults_to_user_defined_compilers
74
+ @template_compilers = default_template_compilers.merge(@template_compilers)
75
+ end
76
+
77
+ def populate_template_compilers user_defined_compilers
78
+ user_defined_compilers.each do |key, val|
79
+ @template_compilers[key.to_sym] = val.to_sym
80
+ end
81
+ end
82
+
83
+ def default_template_compilers
84
+ {
85
+ rb: :sparkle_formation,
86
+ json: :json,
87
+ yml: :yaml,
88
+ yaml: :yaml,
89
+ }
90
+ end
91
+
92
+ def load_config
93
+ unaliased_stacks = resolve_region_aliases(@config.fetch('stacks'))
94
+ load_stacks(unaliased_stacks)
95
+ end
96
+
97
+ def resolve_region_aliases(stacks)
98
+ stacks.inject({}) do |hash, (region, attributes)|
99
+ hash[unalias_region(region)] = attributes
100
+ hash
101
+ end
102
+ end
103
+
104
+ def load_stacks(stacks)
105
+ stacks.each do |region, stacks_for_region|
106
+ region = Utils.underscore_to_hyphen(region)
107
+ stacks_for_region.each do |stack_name, attributes|
108
+ stack_name = Utils.underscore_to_hyphen(stack_name)
109
+ stack_attributes = build_stack_defaults(region).deeper_merge!(attributes).merge(
110
+ 'region' => region,
111
+ 'stack_name' => stack_name,
112
+ 'base_dir' => @base_dir,
113
+ 'template_dir' => @template_dir,
114
+ 'additional_parameter_lookup_dirs' => @region_to_aliases[region])
115
+ @stacks << StackDefinition.new(stack_attributes)
116
+ end
117
+ end
118
+ end
119
+
120
+ def build_stack_defaults(region)
121
+ region_defaults = @region_defaults.fetch(region, {}).deep_dup
122
+ @stack_defaults.deep_dup.deeper_merge(region_defaults)
123
+ end
124
+
125
+ def normalise_region_defaults(region_defaults)
126
+ region_defaults.inject({}) do |normalised_aliases, (region_or_alias, value)|
127
+ region = unalias_region(region_or_alias)
128
+ normalised_aliases[Utils.underscore_to_hyphen(region)] = value
129
+ normalised_aliases
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,4 @@
1
+ module StackMaster
2
+ class CtrlC < Exception
3
+ end
4
+ end
@@ -0,0 +1,29 @@
1
+ module StackMaster
2
+ class PagedResponseAccumulator
3
+ def self.call(*args)
4
+ new(*args).call
5
+ end
6
+
7
+ def initialize(cf, method, arguments, accumulator_method)
8
+ @cf = cf
9
+ @method = method
10
+ @arguments = arguments
11
+ @accumulator_method = accumulator_method
12
+ end
13
+
14
+ def call
15
+ book = []
16
+ next_token = nil
17
+ first_response = nil
18
+ begin
19
+ response = @cf.public_send(@method, @arguments.merge(next_token: next_token))
20
+ first_response = response if first_response.nil?
21
+ next_token = response.next_token
22
+ book += response.public_send(@accumulator_method)
23
+ end while !next_token.nil?
24
+ first_response.send("#{@accumulator_method}=", book.reverse)
25
+ first_response.send(:next_token=, book.reverse)
26
+ first_response
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/core_ext/object/deep_dup'
2
+
3
+ module StackMaster
4
+ class ParameterLoader
5
+
6
+ COMPILE_TIME_PARAMETERS_KEY = 'compile_time_parameters'
7
+
8
+ def self.load(parameter_files)
9
+ StackMaster.debug 'Searching for parameter files...'
10
+ parameter_files.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, file_name|
11
+ parameters = load_parameters(file_name)
12
+ template_parameters = create_template_parameters(parameters)
13
+ compile_time_parameters = create_compile_time_parameters(parameters)
14
+
15
+ merge_and_camelize(hash[:template_parameters], template_parameters)
16
+ merge_and_camelize(hash[:compile_time_parameters], compile_time_parameters)
17
+ hash
18
+ end
19
+
20
+ end
21
+
22
+ private
23
+
24
+ def self.load_parameters(file_name)
25
+ file_exists = File.exists?(file_name)
26
+ StackMaster.debug file_exists ? " #{file_name} found" : " #{file_name} not found"
27
+ file_exists ? load_file(file_name) : {}
28
+ end
29
+
30
+ def self.load_file(file_name)
31
+ YAML.load(File.read(file_name)) || {}
32
+ end
33
+
34
+ def self.create_template_parameters(parameters)
35
+ parameters.deep_dup.tap do |parameters_clone|
36
+ parameters_clone.delete(COMPILE_TIME_PARAMETERS_KEY) || parameters_clone.delete(COMPILE_TIME_PARAMETERS_KEY.camelize)
37
+ end
38
+ end
39
+
40
+ def self.create_compile_time_parameters(parameters)
41
+ (parameters[COMPILE_TIME_PARAMETERS_KEY] || parameters[COMPILE_TIME_PARAMETERS_KEY.camelize] || {}).deep_dup
42
+ end
43
+
44
+ def self.merge_and_camelize(hash, parameters)
45
+ parameters.each { |key, value| hash[key.camelize] = value }
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,98 @@
1
+ module StackMaster
2
+ class ParameterResolver
3
+ ResolverNotFound = Class.new(StandardError)
4
+ InvalidParameter = Class.new(StandardError)
5
+
6
+ def self.resolve(config, stack_definition, parameters)
7
+ new(config, stack_definition, parameters).resolve
8
+ end
9
+
10
+ def initialize(config, stack_definition, parameters)
11
+ @config = config
12
+ @stack_definition = stack_definition
13
+ @parameters = parameters
14
+ @resolvers = {}
15
+ end
16
+
17
+ def resolve
18
+ @parameters.reduce({}) do |parameters, (key, value)|
19
+ begin
20
+ parameters[key] = resolve_parameter_value(key, value)
21
+ rescue InvalidParameter
22
+ raise InvalidParameter, "Unable to resolve parameter #{key.inspect} value causing error: #{$!.message}"
23
+ end
24
+ parameters
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def require_parameter_resolver(file_name)
31
+ require "stack_master/parameter_resolvers/#{file_name}"
32
+ rescue LoadError
33
+ if file_name == file_name.singularize
34
+ raise ResolverNotFound.new(file_name)
35
+ else
36
+ require_parameter_resolver(file_name.singularize)
37
+ end
38
+ end
39
+
40
+ def load_parameter_resolver(class_name)
41
+ # Check if the class name already exists
42
+ resolver_class_const(class_name)
43
+ rescue NameError
44
+ # If it doesn't, try to load it
45
+ require_parameter_resolver(class_name.underscore)
46
+ end
47
+
48
+ def resolve_parameter_value(key, parameter_value)
49
+ return parameter_value.to_s if Numeric === parameter_value || parameter_value == true || parameter_value == false
50
+ return resolve_array_parameter_values(key, parameter_value).join(',') if Array === parameter_value
51
+ return parameter_value unless Hash === parameter_value
52
+ validate_parameter_value!(key, parameter_value)
53
+
54
+ resolver_name = parameter_value.keys.first.to_s
55
+ load_parameter_resolver(resolver_name)
56
+
57
+ value = parameter_value.values.first
58
+ resolver_class_name = resolver_name.camelize
59
+ call_resolver(resolver_class_name, value)
60
+ rescue Aws::CloudFormation::Errors::ValidationError
61
+ raise InvalidParameter, $!.message
62
+ end
63
+
64
+ def resolve_array_parameter_values(key, parameter_values)
65
+ parameter_values.reduce([]) do |values, parameter_value|
66
+ values << resolve_parameter_value(key, parameter_value)
67
+ end
68
+ end
69
+
70
+ def call_resolver(class_name, value)
71
+ resolver_class(class_name).resolve(value)
72
+ end
73
+
74
+ def resolver_class_name(class_name)
75
+ "StackMaster::ParameterResolvers::#{class_name.camelize}"
76
+ end
77
+
78
+ def resolver_class_const(class_name)
79
+ Kernel.const_get(resolver_class_name(class_name))
80
+ end
81
+
82
+ def resolver_class(class_name)
83
+ @resolvers.fetch(class_name) do
84
+ begin
85
+ @resolvers[class_name] = resolver_class_const(class_name).new(@config, @stack_definition)
86
+ rescue NameError
87
+ raise ResolverNotFound, "Could not find parameter resolver called #{class_name}, please double check your configuration"
88
+ end
89
+ end
90
+ end
91
+
92
+ def validate_parameter_value!(key, parameter_value)
93
+ if parameter_value.keys.size != 1
94
+ raise InvalidParameter, "#{key} hash contained more than one key: #{parameter_value.inspect}"
95
+ end
96
+ end
97
+ end
98
+ end