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