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,41 @@
1
+ require_relative 'empty_validator'
2
+ require_relative 'string_validator'
3
+ require_relative 'number_validator'
4
+ require_relative 'allowed_values_validator'
5
+ require_relative 'allowed_pattern_validator'
6
+ require_relative 'max_length_validator'
7
+ require_relative 'min_length_validator'
8
+ require_relative 'max_size_validator'
9
+ require_relative 'min_size_validator'
10
+
11
+ module StackMaster
12
+ module SparkleFormation
13
+ module CompileTime
14
+ class ValueValidatorFactory
15
+
16
+ VALIDATORS_TYPES = [
17
+ EmptyValidator,
18
+ StringValidator,
19
+ NumberValidator,
20
+ AllowedValuesValidator,
21
+ AllowedPatternValidator,
22
+ MaxLengthValidator,
23
+ MinLengthValidator,
24
+ MaxSizeValidator,
25
+ MinSizeValidator
26
+ ]
27
+
28
+ def initialize(name, definition, parameter)
29
+ @name = name
30
+ @definition = definition
31
+ @parameter = parameter
32
+ end
33
+
34
+ def build
35
+ VALIDATORS_TYPES.map { |validator| validator.new(@name, @definition, @parameter)}
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,115 @@
1
+ require 'sparkle_formation'
2
+ require 'erubis'
3
+
4
+ module StackMaster
5
+ module SparkleFormation
6
+ TemplateFileNotFound = ::Class.new(StandardError)
7
+
8
+ class SfEruby < Erubis::Eruby
9
+ include Erubis::ArrayEnhancer
10
+
11
+ def add_expr(src, code, indicator)
12
+ case indicator
13
+ when '='
14
+ src << " #{@bufvar} << (" << code << ');'
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+
21
+ class TemplateContext < AttributeStruct
22
+ include ::SparkleFormation::SparkleAttribute
23
+ include ::SparkleFormation::SparkleAttribute::Aws
24
+ include ::SparkleFormation::Utils::TypeCheckers
25
+
26
+ def self.build(vars, prefix)
27
+ ::Class.new(self).tap do |klass|
28
+ vars.each do |key, value|
29
+ klass.send(:define_method, key) do
30
+ value
31
+ end
32
+ end
33
+
34
+ end.new(vars, prefix)
35
+ end
36
+
37
+ def initialize(vars, prefix)
38
+ self._camel_keys = true
39
+ @vars = vars
40
+ @prefix = prefix
41
+ end
42
+
43
+ def has_var?(var_key)
44
+ @vars.include?(var_key)
45
+ end
46
+
47
+ def render(file_name, vars = {})
48
+ Template.render(@prefix, file_name, vars)
49
+ end
50
+ end
51
+
52
+ # Splits up long strings with multiple lines in them to multiple strings
53
+ # in the CF array. Makes the compiled template and diffs more readable.
54
+ class CloudFormationLineFormatter
55
+ def self.format(template)
56
+ new(template).format
57
+ end
58
+
59
+ def initialize(template)
60
+ @template = template
61
+ end
62
+
63
+ def format
64
+ @template.flat_map do |lines|
65
+ lines = lines.to_s if Symbol === lines
66
+ if String === lines
67
+ newlines = []
68
+ lines.count("\n").times do
69
+ newlines << "\n"
70
+ end
71
+ newlines = lines.split("\n").map do |line|
72
+ "#{line}#{newlines.pop}"
73
+ end
74
+ if lines.starts_with?("\n")
75
+ newlines.insert(0, "\n")
76
+ end
77
+ newlines
78
+ else
79
+ lines
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ module Template
86
+ def self.render(prefix, file_name, vars)
87
+ file_path = File.join(::SparkleFormation.sparkle_path, prefix, file_name)
88
+ template = File.read(file_path)
89
+ template_context = TemplateContext.build(vars, prefix)
90
+ compiled_template = SfEruby.new(template).evaluate(template_context)
91
+ CloudFormationLineFormatter.format(compiled_template)
92
+ rescue Errno::ENOENT => e
93
+ Kernel.raise TemplateFileNotFound, "Could not find template file at path: #{file_path}"
94
+ end
95
+ end
96
+
97
+ module JoinedFile
98
+ def _joined_file(file_name, vars = {})
99
+ join!(Template.render('joined_file', file_name, vars))
100
+ end
101
+ alias_method :joined_file!, :_joined_file
102
+ end
103
+
104
+ module UserDataFile
105
+ def _user_data_file(file_name, vars = {})
106
+ base64!(join!(Template.render('user_data', file_name, vars)))
107
+ end
108
+ alias_method :user_data_file!, :_user_data_file
109
+ end
110
+ end
111
+ end
112
+
113
+ SparkleFormation::SparkleAttribute::Aws.send(:include, StackMaster::SparkleFormation::UserDataFile)
114
+ SparkleFormation::SparkleAttribute::Aws.send(:include, StackMaster::SparkleFormation::JoinedFile)
115
+
@@ -0,0 +1,105 @@
1
+ module StackMaster
2
+ class Stack
3
+ attr_reader :stack_name,
4
+ :region,
5
+ :stack_id,
6
+ :stack_status,
7
+ :parameters,
8
+ :template_body,
9
+ :template_format,
10
+ :role_arn,
11
+ :notification_arns,
12
+ :outputs,
13
+ :stack_policy_body,
14
+ :tags,
15
+ :files
16
+
17
+ include Utils::Initializable
18
+
19
+ def template_default_parameters
20
+ TemplateUtils.template_hash(template).fetch('Parameters', {}).inject({}) do |result, (parameter_name, description)|
21
+ result[parameter_name] = description['Default']
22
+ result
23
+ end
24
+ end
25
+
26
+ def parameters_with_defaults
27
+ template_default_parameters.merge(parameters)
28
+ end
29
+
30
+ def missing_parameters?
31
+ parameters_with_defaults.any? do |key, value|
32
+ value == nil
33
+ end
34
+ end
35
+
36
+ def self.find(region, stack_name)
37
+ cf = StackMaster.cloud_formation_driver
38
+ cf_stack = cf.describe_stacks(stack_name: stack_name).stacks.first
39
+ return unless cf_stack
40
+ parameters = cf_stack.parameters.inject({}) do |params_hash, param_struct|
41
+ params_hash[param_struct.parameter_key] = param_struct.parameter_value
42
+ params_hash
43
+ end
44
+ template_body ||= cf.get_template(stack_name: stack_name, template_stage: 'Original').template_body
45
+ template_format = TemplateUtils.identify_template_format(template_body)
46
+ stack_policy_body ||= cf.get_stack_policy(stack_name: stack_name).stack_policy_body
47
+ outputs = cf_stack.outputs
48
+
49
+ new(region: region,
50
+ stack_name: stack_name,
51
+ stack_id: cf_stack.stack_id,
52
+ parameters: parameters,
53
+ template_body: template_body,
54
+ template_format: template_format,
55
+ outputs: outputs,
56
+ role_arn: cf_stack.role_arn,
57
+ notification_arns: cf_stack.notification_arns,
58
+ stack_policy_body: stack_policy_body,
59
+ stack_status: cf_stack.stack_status)
60
+ rescue Aws::CloudFormation::Errors::ValidationError
61
+ nil
62
+ end
63
+
64
+ def self.generate(stack_definition, config)
65
+ parameter_hash = ParameterLoader.load(stack_definition.parameter_files)
66
+ template_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:template_parameters])
67
+ compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
68
+ template_body = TemplateCompiler.compile(config, stack_definition.template_file_path, compile_time_parameters, stack_definition.compiler_options)
69
+ template_format = TemplateUtils.identify_template_format(template_body)
70
+ stack_policy_body = if stack_definition.stack_policy_file_path
71
+ File.read(stack_definition.stack_policy_file_path)
72
+ end
73
+ new(region: stack_definition.region,
74
+ stack_name: stack_definition.stack_name,
75
+ tags: stack_definition.tags,
76
+ parameters: template_parameters,
77
+ template_body: template_body,
78
+ template_format: template_format,
79
+ role_arn: stack_definition.role_arn,
80
+ notification_arns: stack_definition.notification_arns,
81
+ stack_policy_body: stack_policy_body)
82
+ end
83
+
84
+ def max_template_size(use_s3)
85
+ return TemplateUtils::MAX_S3_TEMPLATE_SIZE if use_s3
86
+ TemplateUtils::MAX_TEMPLATE_SIZE
87
+ end
88
+
89
+ def too_big?(use_s3 = false)
90
+ template.size > max_template_size(use_s3)
91
+ end
92
+
93
+ def aws_parameters
94
+ Utils.hash_to_aws_parameters(parameters)
95
+ end
96
+
97
+ def aws_tags
98
+ Utils.hash_to_aws_tags(tags)
99
+ end
100
+
101
+ def template
102
+ @template ||= TemplateUtils.maybe_compressed_template_body(template_body)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,103 @@
1
+ module StackMaster
2
+ class StackDefinition
3
+ attr_accessor :region,
4
+ :stack_name,
5
+ :template,
6
+ :tags,
7
+ :role_arn,
8
+ :notification_arns,
9
+ :base_dir,
10
+ :template_dir,
11
+ :secret_file,
12
+ :stack_policy_file,
13
+ :additional_parameter_lookup_dirs,
14
+ :s3,
15
+ :files,
16
+ :compiler_options
17
+
18
+ include Utils::Initializable
19
+
20
+ def initialize(attributes = {})
21
+ @additional_parameter_lookup_dirs = []
22
+ @compiler_options = {}
23
+ @notification_arns = []
24
+ @s3 = {}
25
+ @files = []
26
+ super
27
+ @template_dir ||= File.join(@base_dir, 'templates')
28
+ end
29
+
30
+ def ==(other)
31
+ self.class === other &&
32
+ @region == other.region &&
33
+ @stack_name == other.stack_name &&
34
+ @template == other.template &&
35
+ @tags == other.tags &&
36
+ @role_arn == other.role_arn &&
37
+ @notification_arns == other.notification_arns &&
38
+ @base_dir == other.base_dir &&
39
+ @secret_file == other.secret_file &&
40
+ @stack_policy_file == other.stack_policy_file &&
41
+ @additional_parameter_lookup_dirs == other.additional_parameter_lookup_dirs &&
42
+ @s3 == other.s3 &&
43
+ @compiler_options == other.compiler_options
44
+ end
45
+
46
+ def template_file_path
47
+ File.expand_path(File.join(template_dir, template))
48
+ end
49
+
50
+ def files_dir
51
+ File.join(base_dir, 'files')
52
+ end
53
+
54
+ def s3_files
55
+ files.inject({}) do |hash, file|
56
+ path = File.join(files_dir, file)
57
+ hash[file] = {
58
+ path: path,
59
+ body: File.read(path)
60
+ }
61
+ hash
62
+ end
63
+ end
64
+
65
+ def s3_template_file_name
66
+ return template if ['.json', '.yaml', '.yml'].include?(File.extname(template))
67
+ Utils.change_extension(template, 'json')
68
+ end
69
+
70
+ def parameter_files
71
+ [ default_parameter_file_path, region_parameter_file_path, additional_parameter_lookup_file_paths ].flatten.compact
72
+ end
73
+
74
+ def stack_policy_file_path
75
+ File.join(base_dir, 'policies', stack_policy_file) if stack_policy_file
76
+ end
77
+
78
+ def s3_configured?
79
+ !s3.nil?
80
+ end
81
+
82
+ private
83
+
84
+ def additional_parameter_lookup_file_paths
85
+ return unless additional_parameter_lookup_dirs
86
+ additional_parameter_lookup_dirs.map do |a|
87
+ Dir.glob(File.join(base_dir, 'parameters', a, "#{underscored_stack_name}.y*ml"))
88
+ end
89
+ end
90
+
91
+ def region_parameter_file_path
92
+ Dir.glob(File.join(base_dir, 'parameters', "#{region}", "#{underscored_stack_name}.y*ml"))
93
+ end
94
+
95
+ def default_parameter_file_path
96
+ Dir.glob(File.join(base_dir, 'parameters', "#{underscored_stack_name}.y*ml"))
97
+ end
98
+
99
+ def underscored_stack_name
100
+ stack_name.gsub('-', '_')
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,111 @@
1
+ require "diffy"
2
+
3
+ module StackMaster
4
+ class StackDiffer
5
+ def initialize(proposed_stack, current_stack)
6
+ @proposed_stack = proposed_stack
7
+ @current_stack = current_stack
8
+ end
9
+
10
+ def proposed_template
11
+ return @proposed_stack.template_body unless @proposed_stack.template_format == :json
12
+ JSON.pretty_generate(JSON.parse(@proposed_stack.template_body))
13
+ end
14
+
15
+ def current_template
16
+ return '' unless @current_stack
17
+ return @current_stack.template_body unless @current_stack.template_format == :json
18
+ JSON.pretty_generate(TemplateUtils.template_hash(@current_stack.template_body))
19
+ end
20
+
21
+ def current_parameters
22
+ if @current_stack
23
+ YAML.dump(sort_params(@current_stack.parameters_with_defaults))
24
+ else
25
+ ''
26
+ end
27
+ end
28
+
29
+ def proposed_parameters
30
+ # **** out any secret parameters in the current stack.
31
+ params = @proposed_stack.parameters_with_defaults
32
+ if @current_stack
33
+ noecho_keys.each do |key|
34
+ params[key] = "****"
35
+ end
36
+ end
37
+ YAML.dump(sort_params(params))
38
+ end
39
+
40
+ def body_different?
41
+ body_diff != ''
42
+ end
43
+
44
+ def body_diff
45
+ @body_diff ||= Diffy::Diff.new(current_template, proposed_template, context: 7, include_diff_info: true).to_s
46
+ end
47
+
48
+ def params_different?
49
+ params_diff != ''
50
+ end
51
+
52
+ def params_diff
53
+ @param_diff ||= Diffy::Diff.new(current_parameters, proposed_parameters, {}).to_s
54
+ end
55
+
56
+ def output_diff
57
+ display_diff('Stack', body_diff)
58
+ display_diff('Parameters', params_diff)
59
+ unless noecho_keys.empty?
60
+ StackMaster.stdout.puts " * can not tell if NoEcho parameters are different."
61
+ end
62
+ StackMaster.stdout.puts "No stack found" if @current_stack.nil?
63
+ end
64
+
65
+ def noecho_keys
66
+ if @current_stack
67
+ @current_stack.parameters_with_defaults.select do |key, value|
68
+ value == "****"
69
+ end.keys
70
+ else
71
+ []
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def display_diff(thing, diff)
78
+ StackMaster.stdout.print "#{thing} diff: "
79
+ if diff == ''
80
+ StackMaster.stdout.puts "No changes"
81
+ else
82
+ StackMaster.stdout.puts
83
+ diff.each_line do |line|
84
+ if line.start_with?('+')
85
+ StackMaster.stdout.print colorize(line, :green)
86
+ elsif line.start_with?('-')
87
+ StackMaster.stdout.print colorize(line, :red)
88
+ else
89
+ StackMaster.stdout.print line
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def sort_params(hash)
96
+ hash.sort.to_h
97
+ end
98
+
99
+ def colorize(text, color)
100
+ if colorize?
101
+ text.colorize(color)
102
+ else
103
+ text
104
+ end
105
+ end
106
+
107
+ def colorize?
108
+ ENV.fetch('COLORIZE') { 'true' } == 'true'
109
+ end
110
+ end
111
+ end