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,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