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