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,38 @@
1
+ module StackMaster
2
+ module StackEvents
3
+ class Fetcher
4
+ def self.fetch(*args)
5
+ new(*args).fetch
6
+ end
7
+
8
+ def initialize(stack_name, region, from: nil)
9
+ @stack_name = stack_name
10
+ @region = region
11
+ @from = from
12
+ end
13
+
14
+ def fetch
15
+ events = fetch_events
16
+ if @from
17
+ filter_old_events(events)
18
+ else
19
+ events
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def cf
26
+ @cf ||= StackMaster.cloud_formation_driver
27
+ end
28
+
29
+ def filter_old_events(events)
30
+ events.select { |event| event.timestamp > @from }
31
+ end
32
+
33
+ def fetch_events
34
+ PagedResponseAccumulator.call(cf, :describe_stack_events, { stack_name: @stack_name }, :stack_events).stack_events
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module StackMaster
2
+ module StackEvents
3
+ class Presenter
4
+ def self.print_event(io, event)
5
+ new(io).print_event(event)
6
+ end
7
+
8
+ def initialize(io)
9
+ @io = io
10
+ end
11
+
12
+ def print_event(event)
13
+ @io.puts "#{event.timestamp.localtime} #{event.logical_resource_id} #{event.resource_type} #{event.resource_status} #{event.resource_status_reason}".colorize(event_colour(event))
14
+ end
15
+
16
+ def event_colour(event)
17
+ if StackStates.failure_state?(event.resource_status)
18
+ :red
19
+ elsif StackStates.success_state?(event.resource_status)
20
+ :green
21
+ else
22
+ :yellow
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ module StackMaster
2
+ module StackEvents
3
+ class Streamer
4
+ StackFailed = Class.new(StandardError)
5
+
6
+ def self.stream(*args, &block)
7
+ new(*args, &block).stream
8
+ end
9
+
10
+ def initialize(stack_name, region, from: Time.now, break_on_finish_state: true, sleep_between_fetches: 1, io: nil, &block)
11
+ @stack_name = stack_name
12
+ @region = region
13
+ @block = block
14
+ @seen_events = Set.new
15
+ @from = from
16
+ @break_on_finish_state = break_on_finish_state
17
+ @sleep_between_fetches = sleep_between_fetches
18
+ @io = io
19
+ end
20
+
21
+ def stream
22
+ catch(:halt) do
23
+ loop do
24
+ events = Fetcher.fetch(@stack_name, @region, from: @from)
25
+ unseen_events(events).each do |event|
26
+ @block.call(event) if @block
27
+ Presenter.print_event(@io, event) if @io
28
+ if @break_on_finish_state && finish_state?(event)
29
+ exit_with_error(event) if failure_state?(event)
30
+ throw :halt
31
+ end
32
+ end
33
+ sleep @sleep_between_fetches
34
+ end
35
+ end
36
+ rescue Interrupt
37
+ end
38
+
39
+ private
40
+
41
+ def unseen_events(events)
42
+ [].tap do |unseen_events|
43
+ events.each do |event|
44
+ next if @seen_events.include?(event.event_id)
45
+ @seen_events << event.event_id
46
+ unseen_events << event
47
+ end
48
+ end
49
+ end
50
+
51
+ def finish_state?(event)
52
+ StackStates.finish_state?(event.resource_status) &&
53
+ event.resource_type == 'AWS::CloudFormation::Stack' &&
54
+ event.logical_resource_id == @stack_name
55
+ end
56
+
57
+ def failure_state?(event)
58
+ StackStates.failure_state?(event.resource_status) &&
59
+ event.resource_type == 'AWS::CloudFormation::Stack' &&
60
+ event.logical_resource_id == @stack_name
61
+ end
62
+
63
+ def exit_with_error(event)
64
+ raise StackFailed, "#{event.logical_resource_id} did not succeed (last state was #{event.resource_status})"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,34 @@
1
+ module StackMaster
2
+ module StackStates
3
+ SUCCESS_STATES = %w[
4
+ CREATE_COMPLETE
5
+ UPDATE_COMPLETE
6
+ DELETE_COMPLETE
7
+ ].freeze
8
+ FAILURE_STATES = %w[
9
+ CREATE_FAILED
10
+ DELETE_FAILED
11
+ UPDATE_ROLLBACK_FAILED
12
+ ROLLBACK_FAILED
13
+ ROLLBACK_COMPLETE
14
+ ROLLBACK_FAILED
15
+ UPDATE_ROLLBACK_COMPLETE
16
+ UPDATE_ROLLBACK_FAILED
17
+ ].freeze
18
+ FINISH_STATES = (SUCCESS_STATES + FAILURE_STATES).freeze
19
+
20
+ extend self
21
+
22
+ def finish_state?(state)
23
+ FINISH_STATES.include?(state)
24
+ end
25
+
26
+ def failure_state?(state)
27
+ FAILURE_STATES.include?(state)
28
+ end
29
+
30
+ def success_state?(state)
31
+ SUCCESS_STATES.include?(state)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,61 @@
1
+ module StackMaster
2
+ class StackStatus
3
+ def initialize(config, stack_definition)
4
+ @config = config
5
+ @stack_definition = stack_definition
6
+ end
7
+
8
+ def changed_message
9
+ if changed?
10
+ 'Yes'
11
+ elsif no_echo_params?
12
+ 'No *'
13
+ else
14
+ 'No'
15
+ end
16
+ end
17
+
18
+ def changed?
19
+ stack.nil? || body_changed? || parameters_changed?
20
+ end
21
+
22
+ def status
23
+ stack ? stack.stack_status : nil
24
+ end
25
+
26
+ def body_changed?
27
+ stack.nil? || differ.body_different?
28
+ end
29
+
30
+ def parameters_changed?
31
+ stack.nil? || differ.params_different?
32
+ end
33
+
34
+ def no_echo_params?
35
+ !differ.noecho_keys.empty?
36
+ end
37
+
38
+ private
39
+
40
+ def stack
41
+ return @stack if defined?(@stack)
42
+ StackMaster.cloud_formation_driver.set_region(stack_definition.region)
43
+ @stack = find_stack
44
+ end
45
+
46
+ def find_stack
47
+ Stack.find(stack_definition.region, stack_definition.stack_name)
48
+ rescue Aws::CloudFormation::Errors::ValidationError
49
+ end
50
+
51
+ def differ
52
+ @differ ||= StackMaster::StackDiffer.new(proposed_stack, stack)
53
+ end
54
+
55
+ def proposed_stack
56
+ @proposed_stack ||= Stack.generate(stack_definition, @config)
57
+ end
58
+
59
+ attr_reader :stack_definition
60
+ end
61
+ end
@@ -0,0 +1,30 @@
1
+ module StackMaster
2
+ class TemplateCompiler
3
+ TemplateCompilationFailed = Class.new(RuntimeError)
4
+
5
+ def self.compile(config, template_file_path, compile_time_parameters, compiler_options = {})
6
+ compiler = template_compiler_for_file(template_file_path, config)
7
+ compiler.require_dependencies
8
+ compiler.compile(template_file_path, compile_time_parameters, compiler_options)
9
+ rescue
10
+ raise TemplateCompilationFailed.new("Failed to compile #{template_file_path}.")
11
+ end
12
+
13
+ def self.register(name, klass)
14
+ @compilers ||= {}
15
+ @compilers[name] = klass
16
+ end
17
+
18
+ # private
19
+ def self.template_compiler_for_file(template_file_path, config)
20
+ compiler_name = config.template_compilers.fetch(file_ext(template_file_path))
21
+ @compilers.fetch(compiler_name)
22
+ end
23
+ private_class_method :template_compiler_for_file
24
+
25
+ def self.file_ext(template_file_path)
26
+ File.extname(template_file_path).gsub('.', '').to_sym
27
+ end
28
+ private_class_method :file_ext
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module StackMaster::TemplateCompilers
2
+ class Cfndsl
3
+ def self.require_dependencies
4
+ require 'cfndsl'
5
+ end
6
+
7
+ def self.compile(template_file_path, _compile_time_parameters, _compiler_options = {})
8
+ ::CfnDsl.eval_file_with_extras(template_file_path).to_json
9
+ end
10
+
11
+ StackMaster::TemplateCompiler.register(:cfndsl, self)
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module StackMaster::TemplateCompilers
2
+ class Json
3
+ MAX_TEMPLATE_SIZE = 51200
4
+ private_constant :MAX_TEMPLATE_SIZE
5
+
6
+ def self.require_dependencies
7
+ require 'json'
8
+ end
9
+
10
+ def self.compile(template_file_path, _compile_time_parameters, _compiler_options = {})
11
+ template_body = File.read(template_file_path)
12
+ if template_body.size > MAX_TEMPLATE_SIZE
13
+ # Parse the json and rewrite compressed
14
+ JSON.dump(JSON.parse(template_body))
15
+ else
16
+ template_body
17
+ end
18
+ end
19
+
20
+ StackMaster::TemplateCompiler.register(:json, self)
21
+ end
22
+ end
@@ -0,0 +1,71 @@
1
+ require 'stack_master/sparkle_formation/compile_time/parameters_validator'
2
+ require 'stack_master/sparkle_formation/compile_time/definitions_validator'
3
+ require 'stack_master/sparkle_formation/compile_time/state_builder'
4
+
5
+ module StackMaster::TemplateCompilers
6
+ class SparkleFormation
7
+
8
+ CompileTime = StackMaster::SparkleFormation::CompileTime
9
+
10
+ def self.require_dependencies
11
+ require 'sparkle_formation'
12
+ require 'stack_master/sparkle_formation/template_file'
13
+ end
14
+
15
+ def self.compile(template_file_path, compile_time_parameters, compiler_options = {})
16
+ sparkle_template = compile_sparkle_template(template_file_path, compiler_options)
17
+ definitions = sparkle_template.parameters
18
+ validate_definitions(definitions)
19
+ validate_parameters(definitions, compile_time_parameters)
20
+
21
+ sparkle_template.compile_time_parameter_setter do
22
+ sparkle_template.compile_state = create_state(definitions, compile_time_parameters)
23
+ end
24
+
25
+ JSON.pretty_generate(sparkle_template)
26
+ end
27
+
28
+ private
29
+
30
+ def self.compile_sparkle_template(template_file_path, compiler_options)
31
+ sparkle_path = compiler_options['sparkle_path'] ?
32
+ File.expand_path(compiler_options['sparkle_path']) : File.dirname(template_file_path)
33
+
34
+ collection = ::SparkleFormation::SparkleCollection.new
35
+ root_pack = ::SparkleFormation::Sparkle.new(
36
+ :root => sparkle_path,
37
+ )
38
+ collection.set_root(root_pack)
39
+ if compiler_options['sparkle_packs']
40
+ compiler_options['sparkle_packs'].each do |pack_name|
41
+ require pack_name
42
+ pack = ::SparkleFormation::SparklePack.new(:name => pack_name)
43
+ collection.add_sparkle(pack)
44
+ end
45
+ end
46
+
47
+ sparkle_template = compile_template_with_sparkle_path(template_file_path, sparkle_path)
48
+ sparkle_template.sparkle.apply(collection)
49
+ sparkle_template
50
+ end
51
+
52
+ def self.compile_template_with_sparkle_path(template_path, sparkle_path)
53
+ ::SparkleFormation.sparkle_path = sparkle_path
54
+ ::SparkleFormation.compile(template_path, :sparkle)
55
+ end
56
+
57
+ def self.validate_definitions(definitions)
58
+ CompileTime::DefinitionsValidator.new(definitions).validate
59
+ end
60
+
61
+ def self.validate_parameters(definitions, compile_time_parameters)
62
+ CompileTime::ParametersValidator.new(definitions, compile_time_parameters).validate
63
+ end
64
+
65
+ def self.create_state(definitions, compile_time_parameters)
66
+ CompileTime::StateBuilder.new(definitions, compile_time_parameters).build
67
+ end
68
+
69
+ StackMaster::TemplateCompiler.register(:sparkle_formation, self)
70
+ end
71
+ end
@@ -0,0 +1,14 @@
1
+ module StackMaster::TemplateCompilers
2
+ class Yaml
3
+ def self.require_dependencies
4
+ require 'yaml'
5
+ require 'json'
6
+ end
7
+
8
+ def self.compile(template_file_path, _compile_time_parameters, _compiler_options = {})
9
+ File.read(template_file_path)
10
+ end
11
+
12
+ StackMaster::TemplateCompiler.register(:yaml, self)
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ module StackMaster
2
+ module TemplateUtils
3
+ MAX_TEMPLATE_SIZE = 51200
4
+ MAX_S3_TEMPLATE_SIZE = 460800
5
+
6
+ extend self
7
+
8
+ def identify_template_format(template_body)
9
+ return :json if template_body =~ /^{/x # ignore leading whitespaces
10
+ :yaml
11
+ end
12
+
13
+ def template_hash(template_body=nil)
14
+ return unless template_body
15
+ template_format = identify_template_format(template_body)
16
+ case template_format
17
+ when :json
18
+ JSON.parse(template_body)
19
+ when :yaml
20
+ YAML.load(template_body)
21
+ end
22
+ end
23
+
24
+ def maybe_compressed_template_body(template_body)
25
+ # Do not compress the template if it's not JSON because parsing YAML as a hash ignores
26
+ # CloudFormation-specific tags such as !Ref
27
+ return template_body if template_body.size <= MAX_TEMPLATE_SIZE || identify_template_format(template_body) != :json
28
+ JSON.dump(template_hash(template_body))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,193 @@
1
+ require 'securerandom'
2
+
3
+ module StackMaster
4
+ module TestDriver
5
+ class Stack
6
+ attr_reader :stack_id,
7
+ :stack_name,
8
+ :description,
9
+ :parameters,
10
+ :creation_time,
11
+ :last_update_time,
12
+ :stack_status,
13
+ :stack_status_reason,
14
+ :disable_rollback,
15
+ :role_arn,
16
+ :notification_arns,
17
+ :timeout_in_minutes,
18
+ :capabilities,
19
+ :outputs,
20
+ :tags
21
+
22
+ include Utils::Initializable
23
+
24
+ def parameters
25
+ @parameters.map do |hash|
26
+ OpenStruct.new(parameter_key: hash[:parameter_key],
27
+ parameter_value: hash[:parameter_value])
28
+ end
29
+ end
30
+ end
31
+
32
+ class StackEvent
33
+ attr_reader :stack_id,
34
+ :event_id,
35
+ :stack_name,
36
+ :logical_resource_id,
37
+ :physical_resource_id,
38
+ :resource_type,
39
+ :timestamp,
40
+ :resource_status,
41
+ :resource_status_reason,
42
+ :resource_properties
43
+
44
+ include Utils::Initializable
45
+
46
+ def timestamp
47
+ Time.parse(@timestamp) if @timestamp
48
+ end
49
+ end
50
+
51
+ class StackResource
52
+ attr_reader :stack_name,
53
+ :stack_id,
54
+ :logical_resource_id,
55
+ :physical_resource_id,
56
+ :resource_type,
57
+ :timestamp,
58
+ :resource_status,
59
+ :resource_status_reason,
60
+ :description
61
+
62
+ include Utils::Initializable
63
+ end
64
+
65
+ class CloudFormation
66
+ def initialize
67
+ reset
68
+ end
69
+
70
+ def region
71
+ @region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region
72
+ end
73
+
74
+ def set_region(region)
75
+ @region = region
76
+ end
77
+
78
+ def reset
79
+ @stacks = {}
80
+ @templates = {}
81
+ @stack_events = {}
82
+ @stack_resources = {}
83
+ @stack_policies = {}
84
+ @change_sets = {}
85
+ end
86
+
87
+ def create_change_set(options)
88
+ id = SecureRandom.uuid
89
+ options.merge!(change_set_id: id)
90
+ @change_sets[id] = options
91
+ @change_sets[options.fetch(:change_set_name)] = options
92
+ stack_name = options.fetch(:stack_name)
93
+ add_stack(stack_name: stack_name, stack_status: 'REVIEW_IN_PROGRESS') unless @stacks[stack_name]
94
+ OpenStruct.new(id: id)
95
+ end
96
+
97
+ def describe_change_set(options)
98
+ change_set_id = options.fetch(:change_set_name)
99
+ change_set = @change_sets.fetch(change_set_id)
100
+ change_details = [
101
+ OpenStruct.new(evaluation: 'Static', change_source: 'ResourceReference', target: OpenStruct.new(attribute: 'Properties', requires_recreation: 'Always', name: 'blah'))
102
+ ]
103
+ change = OpenStruct.new(action: 'Modify', replacement: 'True', scope: ['Properties'], details: change_details)
104
+ changes = [
105
+ OpenStruct.new(type: 'AWS::Resource', resource_change: change)
106
+ ]
107
+ OpenStruct.new(change_set.merge(changes: changes, status: 'CREATE_COMPLETE'))
108
+ end
109
+
110
+ def execute_change_set(options)
111
+ change_set_id = options.fetch(:change_set_name)
112
+ change_set = @change_sets.fetch(change_set_id)
113
+ @stacks[change_set.fetch(:stack_name)].attributes = change_set
114
+ end
115
+
116
+ def delete_change_set(options)
117
+ change_set_id = options.fetch(:change_set_name)
118
+ @change_sets.delete(change_set_id)
119
+ end
120
+
121
+ def describe_stacks(options = {})
122
+ stack_name = options[:stack_name]
123
+ stacks = if stack_name
124
+ if @stacks[stack_name]
125
+ [@stacks[stack_name]]
126
+ else
127
+ raise Aws::CloudFormation::Errors::ValidationError.new('', 'Stack does not exist')
128
+ end
129
+ else
130
+ @stacks.values
131
+ end
132
+ OpenStruct.new(stacks: stacks, next_token: nil)
133
+ end
134
+
135
+ def describe_stack_resources(options = {})
136
+ @stacks.fetch(options.fetch(:stack_name)) { raise Aws::CloudFormation::Errors::ValidationError.new('', 'Stack does not exist') }
137
+ OpenStruct.new(stack_resources: @stack_resources[options.fetch(:stack_name)])
138
+ end
139
+
140
+ def get_template(options)
141
+ template_body = @templates[options[:stack_name]] || nil
142
+ OpenStruct.new(template_body: template_body)
143
+ end
144
+
145
+ def get_stack_policy(options)
146
+ OpenStruct.new(stack_policy_body: @stack_policies[options.fetch(:stack_name)])
147
+ end
148
+
149
+ def set_stack_policy(options)
150
+ @stack_policies[options.fetch(:stack_name)] = options[:stack_policy_body]
151
+ end
152
+
153
+ def describe_stack_events(options)
154
+ events = @stack_events[options.fetch(:stack_name)] || []
155
+ OpenStruct.new(stack_events: events, next_token: nil)
156
+ end
157
+
158
+ def create_stack(options)
159
+ stack_name = options.fetch(:stack_name)
160
+ add_stack(options)
161
+ @stack_policies[stack_name] = options[:stack_policy_body]
162
+ end
163
+
164
+ def delete_stack(options)
165
+ stack_name = options.fetch(:stack_name)
166
+ @stacks.delete(stack_name)
167
+ end
168
+
169
+ def validate_template(options)
170
+ true
171
+ end
172
+
173
+ def add_stack(stack)
174
+ @stacks[stack.fetch(:stack_name)] = Stack.new(stack)
175
+ end
176
+
177
+ def add_stack_resource(options)
178
+ @stack_resources[options.fetch(:stack_name)] ||= []
179
+ @stack_resources[options.fetch(:stack_name)] << StackResource.new(options)
180
+ end
181
+
182
+ def set_template(stack_name, template)
183
+ @templates[stack_name] = template
184
+ end
185
+
186
+ def add_stack_event(event)
187
+ stack_name = event.fetch(:stack_name)
188
+ @stack_events[stack_name] ||= []
189
+ @stack_events[stack_name] << StackEvent.new(event)
190
+ end
191
+ end
192
+ end
193
+ end