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.
- checksums.yaml +7 -0
- data/README.md +548 -0
- data/bin/stack_master +17 -0
- data/lib/stack_master.rb +159 -0
- data/lib/stack_master/aws_driver/cloud_formation.rb +41 -0
- data/lib/stack_master/aws_driver/s3.rb +68 -0
- data/lib/stack_master/change_set.rb +109 -0
- data/lib/stack_master/cli.rb +208 -0
- data/lib/stack_master/command.rb +57 -0
- data/lib/stack_master/commands/apply.rb +221 -0
- data/lib/stack_master/commands/delete.rb +53 -0
- data/lib/stack_master/commands/diff.rb +31 -0
- data/lib/stack_master/commands/events.rb +39 -0
- data/lib/stack_master/commands/init.rb +111 -0
- data/lib/stack_master/commands/list_stacks.rb +20 -0
- data/lib/stack_master/commands/outputs.rb +31 -0
- data/lib/stack_master/commands/resources.rb +33 -0
- data/lib/stack_master/commands/status.rb +46 -0
- data/lib/stack_master/commands/terminal_helper.rb +28 -0
- data/lib/stack_master/commands/validate.rb +17 -0
- data/lib/stack_master/config.rb +133 -0
- data/lib/stack_master/ctrl_c.rb +4 -0
- data/lib/stack_master/paged_response_accumulator.rb +29 -0
- data/lib/stack_master/parameter_loader.rb +49 -0
- data/lib/stack_master/parameter_resolver.rb +98 -0
- data/lib/stack_master/parameter_resolvers/ami_finder.rb +36 -0
- data/lib/stack_master/parameter_resolvers/env.rb +18 -0
- data/lib/stack_master/parameter_resolvers/latest_ami.rb +19 -0
- data/lib/stack_master/parameter_resolvers/latest_ami_by_tags.rb +18 -0
- data/lib/stack_master/parameter_resolvers/parameter_store.rb +31 -0
- data/lib/stack_master/parameter_resolvers/secret.rb +52 -0
- data/lib/stack_master/parameter_resolvers/security_group.rb +22 -0
- data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +31 -0
- data/lib/stack_master/parameter_resolvers/stack_output.rb +76 -0
- data/lib/stack_master/prompter.rb +21 -0
- data/lib/stack_master/resolver_array.rb +35 -0
- data/lib/stack_master/security_group_finder.rb +28 -0
- data/lib/stack_master/sns_topic_finder.rb +26 -0
- data/lib/stack_master/sparkle_formation/compile_time/allowed_pattern_validator.rb +35 -0
- data/lib/stack_master/sparkle_formation/compile_time/allowed_values_validator.rb +37 -0
- data/lib/stack_master/sparkle_formation/compile_time/definitions_validator.rb +33 -0
- data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +32 -0
- data/lib/stack_master/sparkle_formation/compile_time/max_length_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/max_size_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/min_length_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/min_size_validator.rb +36 -0
- data/lib/stack_master/sparkle_formation/compile_time/number_validator.rb +35 -0
- data/lib/stack_master/sparkle_formation/compile_time/parameters_validator.rb +27 -0
- data/lib/stack_master/sparkle_formation/compile_time/state_builder.rb +32 -0
- data/lib/stack_master/sparkle_formation/compile_time/string_validator.rb +33 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_builder.rb +40 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_validator.rb +40 -0
- data/lib/stack_master/sparkle_formation/compile_time/value_validator_factory.rb +41 -0
- data/lib/stack_master/sparkle_formation/template_file.rb +115 -0
- data/lib/stack_master/stack.rb +105 -0
- data/lib/stack_master/stack_definition.rb +103 -0
- data/lib/stack_master/stack_differ.rb +111 -0
- data/lib/stack_master/stack_events/fetcher.rb +38 -0
- data/lib/stack_master/stack_events/presenter.rb +27 -0
- data/lib/stack_master/stack_events/streamer.rb +68 -0
- data/lib/stack_master/stack_states.rb +34 -0
- data/lib/stack_master/stack_status.rb +61 -0
- data/lib/stack_master/template_compiler.rb +30 -0
- data/lib/stack_master/template_compilers/cfndsl.rb +13 -0
- data/lib/stack_master/template_compilers/json.rb +22 -0
- data/lib/stack_master/template_compilers/sparkle_formation.rb +71 -0
- data/lib/stack_master/template_compilers/yaml.rb +14 -0
- data/lib/stack_master/template_utils.rb +31 -0
- data/lib/stack_master/test_driver/cloud_formation.rb +193 -0
- data/lib/stack_master/test_driver/s3.rb +34 -0
- data/lib/stack_master/testing.rb +9 -0
- data/lib/stack_master/utils.rb +50 -0
- data/lib/stack_master/validator.rb +33 -0
- data/lib/stack_master/version.rb +3 -0
- 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
|