sfn 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/bin/command-config-generator +44 -0
- data/bin/sfn +1 -5
- data/docs/README.md +2 -1
- data/docs/callbacks.md +18 -0
- data/docs/command-config.md +1276 -0
- data/docs/configuration.md +0 -105
- data/docs/overview.md +1 -0
- data/lib/sfn.rb +1 -0
- data/lib/sfn/api_provider.rb +9 -0
- data/lib/sfn/api_provider/google.rb +84 -0
- data/lib/sfn/command.rb +22 -0
- data/lib/sfn/command/create.rb +33 -45
- data/lib/sfn/command/describe.rb +1 -1
- data/lib/sfn/command/destroy.rb +7 -1
- data/lib/sfn/command/diff.rb +4 -5
- data/lib/sfn/command/graph.rb +1 -3
- data/lib/sfn/command/print.rb +3 -4
- data/lib/sfn/command/update.rb +89 -87
- data/lib/sfn/command/validate.rb +24 -11
- data/lib/sfn/command_module/base.rb +1 -12
- data/lib/sfn/command_module/stack.rb +45 -56
- data/lib/sfn/command_module/template.rb +74 -33
- data/lib/sfn/config/print.rb +5 -0
- data/lib/sfn/config/update.rb +4 -0
- data/lib/sfn/config/validate.rb +4 -0
- data/lib/sfn/monkey_patch/stack.rb +153 -83
- data/lib/sfn/monkey_patch/stack/azure.rb +23 -0
- data/lib/sfn/monkey_patch/stack/google.rb +129 -0
- data/lib/sfn/planner/aws.rb +111 -72
- data/lib/sfn/utils/json.rb +3 -0
- data/lib/sfn/utils/stack_parameter_scrubber.rb +20 -23
- data/lib/sfn/utils/stack_parameter_validator.rb +162 -157
- data/lib/sfn/version.rb +1 -1
- data/sfn.gemspec +3 -2
- metadata +34 -8
data/lib/sfn/command/update.rb
CHANGED
@@ -27,16 +27,18 @@ module Sfn
|
|
27
27
|
s_name = [name]
|
28
28
|
|
29
29
|
c_setter = lambda do |c_stack|
|
30
|
-
|
31
|
-
|
30
|
+
if(c_stack.outputs)
|
31
|
+
compile_params = c_stack.outputs.detect do |output|
|
32
|
+
output.key == 'CompileState'
|
33
|
+
end
|
32
34
|
end
|
33
35
|
if(compile_params)
|
34
36
|
compile_params = MultiJson.load(compile_params.value)
|
35
|
-
c_current = config[:compile_parameters].fetch(s_name.join('
|
36
|
-
config[:compile_parameters][s_name.join('
|
37
|
+
c_current = config[:compile_parameters].fetch(s_name.join('__'), Smash.new)
|
38
|
+
config[:compile_parameters][s_name.join('__')] = compile_params.merge(c_current)
|
37
39
|
end
|
38
40
|
c_stack.nested_stacks(false).each do |n_stack|
|
39
|
-
s_name.push(n_stack.name)
|
41
|
+
s_name.push(n_stack.data.fetch(:logical_id, n_stack.name))
|
40
42
|
c_setter.call(n_stack)
|
41
43
|
s_name.pop
|
42
44
|
end
|
@@ -46,114 +48,114 @@ module Sfn
|
|
46
48
|
c_setter.call(stack)
|
47
49
|
end
|
48
50
|
|
51
|
+
ui.debug "Compile parameters - #{config[:compile_parameters]}"
|
49
52
|
file = load_template_file(:stack => stack)
|
50
53
|
stack_info << " #{ui.color('Path:', :bold)} #{config[:file]}"
|
51
|
-
nested_stacks = file.delete('sfn_nested_stack')
|
52
54
|
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
ui.fatal "Failed to locate requested stack: #{ui.color(name, :red, :bold)}"
|
59
|
-
raise "Failed to locate stack: #{name}"
|
60
|
-
end
|
56
|
+
unless(stack)
|
57
|
+
ui.fatal "Failed to locate requested stack: #{ui.color(name, :red, :bold)}"
|
58
|
+
raise "Failed to locate stack: #{name}"
|
59
|
+
end
|
61
60
|
|
62
|
-
|
61
|
+
ui.info "#{ui.color('SparkleFormation:', :bold)} #{ui.color('update', :green)}"
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
63
|
+
unless(file)
|
64
|
+
if(config[:template])
|
65
|
+
file = config[:template]
|
66
|
+
stack_info << " #{ui.color('(template provided)', :green)}"
|
67
|
+
else
|
68
|
+
stack_info << " #{ui.color('(no template update)', :yellow)}"
|
71
69
|
end
|
72
|
-
|
70
|
+
end
|
71
|
+
ui.info " -> #{stack_info}"
|
73
72
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
if(file)
|
74
|
+
if(config[:print_only])
|
75
|
+
ui.puts format_json(parameter_scrub!(template_content(file)))
|
76
|
+
return
|
77
|
+
end
|
79
78
|
|
80
|
-
|
81
|
-
|
79
|
+
original_template = stack.template
|
80
|
+
original_parameters = stack.parameters
|
82
81
|
|
83
|
-
|
84
|
-
apply_stacks!(stack)
|
82
|
+
apply_stacks!(stack)
|
85
83
|
|
86
|
-
|
87
|
-
|
84
|
+
populate_parameters!(file, :current_parameters => stack.root_parameters)
|
85
|
+
update_template = stack.template
|
88
86
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
rescue => e
|
99
|
-
unless(e.message.include?('Confirmation declined'))
|
100
|
-
ui.error "Unexpected error when generating plan information: #{e.class} - #{e}"
|
101
|
-
ui.debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
102
|
-
ui.confirm 'Continue with stack update?' unless config[:plan_only]
|
103
|
-
else
|
104
|
-
raise
|
105
|
-
end
|
87
|
+
if(config[:plan])
|
88
|
+
begin
|
89
|
+
stack.template = original_template
|
90
|
+
stack.parameters = original_parameters
|
91
|
+
plan = build_planner(stack)
|
92
|
+
if(plan)
|
93
|
+
result = plan.generate_plan(file.dump, config_root_parameters)
|
94
|
+
display_plan_information(result)
|
106
95
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
96
|
+
rescue => e
|
97
|
+
unless(e.message.include?('Confirmation declined'))
|
98
|
+
ui.error "Unexpected error when generating plan information: #{e.class} - #{e}"
|
99
|
+
ui.debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
100
|
+
ui.confirm 'Continue with stack update?' unless config[:plan_only]
|
101
|
+
else
|
102
|
+
raise
|
110
103
|
end
|
111
104
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
apply_stacks!(stack)
|
117
|
-
original_parameters = stack.parameters
|
118
|
-
populate_parameters!(stack.template, :current_parameters => stack.parameters)
|
119
|
-
stack.parameters = config_root_parameters
|
105
|
+
if(config[:plan_only])
|
106
|
+
ui.info 'Plan only mode requested. Exiting.'
|
107
|
+
exit 0
|
108
|
+
end
|
120
109
|
end
|
110
|
+
stack.parameters = config_root_parameters
|
111
|
+
else
|
112
|
+
apply_stacks!(stack)
|
113
|
+
original_parameters = stack.parameters
|
114
|
+
populate_parameters!(file, :current_parameters => stack.root_parameters)
|
115
|
+
stack.parameters = config_root_parameters
|
116
|
+
end
|
121
117
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
118
|
+
if(config[:upload_root_template])
|
119
|
+
upload_result = store_template(name, file, Smash.new)
|
120
|
+
stack.template_url = upload_result[:url]
|
121
|
+
else
|
122
|
+
stack.template = parameter_scrub!(template_content(file, :scrub))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Set options defined within config into stack instance for update request
|
126
|
+
if(config[:merge_api_options])
|
127
|
+
config.fetch(:options, Smash.new).each_pair do |key, value|
|
128
|
+
if(stack.respond_to?("#{key}="))
|
129
|
+
stack.send("#{key}=", value)
|
128
130
|
end
|
129
131
|
end
|
132
|
+
end
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
else
|
140
|
-
ui.fatal "Update of stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
|
141
|
-
raise 'Stack did not reach a successful update completion state.'
|
142
|
-
end
|
134
|
+
begin
|
135
|
+
api_action!(:api_stack => stack) do
|
136
|
+
stack.save
|
137
|
+
if(config[:poll])
|
138
|
+
poll_stack(stack.name)
|
139
|
+
if(stack.reload.state == :update_complete)
|
140
|
+
ui.info "Stack update complete: #{ui.color('SUCCESS', :green)}"
|
141
|
+
namespace.const_get(:Describe).new({:outputs => true}, [name]).execute!
|
143
142
|
else
|
144
|
-
ui.
|
145
|
-
|
143
|
+
ui.fatal "Update of stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
|
144
|
+
raise 'Stack did not reach a successful update completion state.'
|
146
145
|
end
|
147
|
-
end
|
148
|
-
rescue Miasma::Error::ApiError::RequestError => e
|
149
|
-
if(e.message.downcase.include?('no updates'))
|
150
|
-
ui.warn "No updates detected for stack (#{stack.name})"
|
151
146
|
else
|
152
|
-
|
147
|
+
ui.warn 'Stack state polling has been disabled.'
|
148
|
+
ui.info "Stack update initialized for #{ui.color(name, :green)}"
|
153
149
|
end
|
154
150
|
end
|
155
|
-
|
151
|
+
rescue Miasma::Error::ApiError::RequestError => e
|
152
|
+
if(e.message.downcase.include?('no updates'))
|
153
|
+
ui.warn "No updates detected for stack (#{stack.name})"
|
154
|
+
else
|
155
|
+
raise
|
156
|
+
end
|
156
157
|
end
|
158
|
+
|
157
159
|
end
|
158
160
|
|
159
161
|
def build_planner(stack)
|
data/lib/sfn/command/validate.rb
CHANGED
@@ -14,21 +14,25 @@ module Sfn
|
|
14
14
|
print_only_original = config[:print_only]
|
15
15
|
config[:print_only] = true
|
16
16
|
file = load_template_file
|
17
|
-
file.delete('sfn_nested_stack')
|
18
17
|
ui.info "#{ui.color("Template Validation (#{provider.connection.provider}): ", :bold)} #{config[:file].sub(Dir.pwd, '').sub(%r{^/}, '')}"
|
19
|
-
file = Sfn::Utils::StackParameterScrubber.scrub!(file)
|
20
|
-
file = translate_template(file)
|
21
18
|
config[:print_only] = print_only_original
|
22
19
|
|
20
|
+
raw_template = _format_json(parameter_scrub!(template_content(file)))
|
21
|
+
|
23
22
|
if(config[:print_only])
|
24
|
-
ui.puts
|
23
|
+
ui.puts raw_template
|
25
24
|
else
|
26
|
-
validate_stack(file, sparkle_collection.get(:template, config[:file])[:name])
|
25
|
+
validate_stack(file.dump, sparkle_collection.get(:template, config[:file])[:name])
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
|
29
|
+
# Validate template with remote API and unpack nested templates if required
|
30
|
+
#
|
31
|
+
# @param template [Hash] template data structure
|
32
|
+
# @param name [String] name of template
|
33
|
+
# @return [TrueClass]
|
34
|
+
def validate_stack(template, name)
|
35
|
+
resources = template.fetch('Resources', {})
|
32
36
|
nested_stacks = resources.find_all do |r_name, r_value|
|
33
37
|
r_value.is_a?(Hash) &&
|
34
38
|
provider.connection.data[:stack_types].include?(r_value['Type'])
|
@@ -39,14 +43,23 @@ module Sfn
|
|
39
43
|
end
|
40
44
|
begin
|
41
45
|
ui.info "Validating: #{ui.color(name, :bold)}"
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
+
if(config[:upload_root_template])
|
47
|
+
upload_result = store_template('validation-stack', template, Smash.new)
|
48
|
+
stack = provider.connection.stacks.build(
|
49
|
+
:name => 'validation-stack',
|
50
|
+
:template_url => upload_result[:url]
|
51
|
+
)
|
52
|
+
else
|
53
|
+
stack = provider.connection.stacks.build(
|
54
|
+
:name => 'validation-stack',
|
55
|
+
:template => parameter_scrub!(template)
|
56
|
+
)
|
57
|
+
end
|
46
58
|
result = api_action!(:api_stack => stack) do
|
47
59
|
stack.validate
|
48
60
|
end
|
49
61
|
ui.info ui.color(' -> VALID', :bold, :green)
|
62
|
+
true
|
50
63
|
rescue => e
|
51
64
|
ui.info ui.color(' -> INVALID', :bold, :red)
|
52
65
|
ui.fatal e.message
|
@@ -110,18 +110,7 @@ module Sfn
|
|
110
110
|
def poll_stack(name)
|
111
111
|
provider.connection.stacks.reload
|
112
112
|
retry_attempts = 0
|
113
|
-
|
114
|
-
events = Sfn::Command::Events.new({:poll => true}, [name]).execute!
|
115
|
-
rescue => e
|
116
|
-
if(retry_attempts < config.fetch(:max_poll_retries, 5).to_i)
|
117
|
-
retry_attempts += 1
|
118
|
-
warn "Unexpected error encountered (#{e.class}: #{e}) Retrying [retry count: #{retry_attempts}]"
|
119
|
-
sleep(1)
|
120
|
-
retry
|
121
|
-
else
|
122
|
-
raise
|
123
|
-
end
|
124
|
-
end
|
113
|
+
events = Sfn::Command::Events.new({:poll => true}, [name]).execute!
|
125
114
|
end
|
126
115
|
|
127
116
|
# Wrapper for information retrieval. Provides consistent error
|
@@ -8,8 +8,6 @@ module Sfn
|
|
8
8
|
|
9
9
|
module InstanceMethods
|
10
10
|
|
11
|
-
# unpacked stack name joiner/identifier
|
12
|
-
UNPACK_NAME_JOINER = '-sfn-'
|
13
11
|
# maximum number of attempts to get valid parameter value
|
14
12
|
MAX_PARAMETER_ATTEMPTS = 5
|
15
13
|
# Template parameter locations
|
@@ -24,12 +22,14 @@ module Sfn
|
|
24
22
|
def apply_stacks!(stack)
|
25
23
|
remote_stacks = [config[:apply_stack]].flatten.compact
|
26
24
|
remote_stacks.each do |stack_name|
|
27
|
-
remote_stack = provider.
|
25
|
+
remote_stack = provider.stack(stack_name)
|
28
26
|
if(remote_stack)
|
29
27
|
apply_nested_stacks!(remote_stack, stack)
|
30
|
-
|
28
|
+
mappings = generate_custom_apply_mappings(remote_stack)
|
29
|
+
execute_apply_stack(remote_stack, stack, mappings)
|
31
30
|
else
|
32
|
-
|
31
|
+
ui.error "Failed to apply requested stack. Unable to locate. (#{stack_name})"
|
32
|
+
raise "Failed to locate stack: #{stack}"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
stack
|
@@ -42,67 +42,47 @@ module Sfn
|
|
42
42
|
# @return [Miasma::Models::Orchestration::Stack]
|
43
43
|
def apply_nested_stacks!(remote_stack, stack)
|
44
44
|
remote_stack.resources.all.each do |resource|
|
45
|
-
if(resource.type
|
45
|
+
if(valid_stack_types.include?(resource.type))
|
46
46
|
nested_stack = resource.expand
|
47
47
|
apply_nested_stacks!(nested_stack, stack)
|
48
|
-
|
48
|
+
mappings = generate_custom_apply_mappings(nested_stack)
|
49
|
+
execute_apply_stack(nested_stack, stack, mappings)
|
49
50
|
end
|
50
51
|
end
|
51
52
|
stack
|
52
53
|
end
|
53
54
|
|
54
|
-
#
|
55
|
+
# Build apply mappings valid for given provider stack
|
55
56
|
#
|
56
|
-
# @param
|
57
|
-
# @
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
57
|
+
# @param provider_stack [Miasma::Models::Orchestration::Stack] stack providing outputs
|
58
|
+
# @return [Hash] output to parameter mapping
|
59
|
+
def generate_custom_apply_mappings(provider_stack)
|
60
|
+
if(config[:apply_mapping])
|
61
|
+
valid_keys = config[:apply_mapping].keys.find_all do |a_key|
|
62
|
+
a_key = a_key.to_s
|
63
|
+
!a_key.include?('__') ||
|
64
|
+
a_key.split('__').first == provider_stack.name
|
65
|
+
end
|
66
|
+
to_remove = valid_keys.find_all do |key|
|
67
|
+
valid_keys.any?{|v_key| v_key.match(/__#{Regexp.escape(key)}$/)}
|
68
|
+
end
|
69
|
+
valid_keys -= to_remove
|
70
|
+
Hash[
|
71
|
+
valid_keys.map do |a_key|
|
72
|
+
cut_key = a_key.include?('__') ? a_key.slice(a_key.index('__') + 2, a_key.length) : a_key
|
73
|
+
[cut_key, config[:apply_mapping][a_key]]
|
74
|
+
end
|
75
|
+
]
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
73
|
-
#
|
74
|
-
# the previous stacks automatically
|
79
|
+
# Apply provider stack outputs to receiver stack parameters
|
75
80
|
#
|
76
|
-
# @param
|
77
|
-
# @param
|
78
|
-
# @param action [String] create or update
|
81
|
+
# @param provider_stack [Miasma::Models::Orchestration::Stack] stack providing outputs
|
82
|
+
# @param receiver_stack [Miasma::Models::Orchestration::Stack] stack receiving outputs for parameters
|
79
83
|
# @return [TrueClass]
|
80
|
-
def
|
81
|
-
|
82
|
-
stack_count = 0
|
83
|
-
file['Resources'].each do |stack_resource_name, stack_resource|
|
84
|
-
|
85
|
-
nested_stack_name = "#{name}#{UNPACK_NAME_JOINER}#{Kernel.sprintf('%0.3d', stack_count)}-#{stack_resource_name}"
|
86
|
-
nested_stack_template = stack_resource['Properties']['Stack']
|
87
|
-
|
88
|
-
namespace.const_get(action.to_s.capitalize).new(
|
89
|
-
Smash.new(
|
90
|
-
:print_only => config[:print_only],
|
91
|
-
:template => nested_stack_template,
|
92
|
-
:parameters => config.fetch(:parameters, Smash.new).to_smash,
|
93
|
-
:apply_stacks => config[:apply_stacks],
|
94
|
-
:options => config[:options]
|
95
|
-
),
|
96
|
-
[nested_stack_name]
|
97
|
-
).execute!
|
98
|
-
unless(config[:print_only])
|
99
|
-
config[:apply_stacks].push(nested_stack_name).uniq!
|
100
|
-
end
|
101
|
-
config[:template] = nil
|
102
|
-
provider.connection.stacks.reload
|
103
|
-
stack_count += 1
|
104
|
-
end
|
105
|
-
|
84
|
+
def execute_apply_stack(provider_stack, receiver_stack, mappings)
|
85
|
+
receiver_stack.apply_stack(provider_stack, :mapping => mappings)
|
106
86
|
true
|
107
87
|
end
|
108
88
|
|
@@ -148,7 +128,7 @@ module Sfn
|
|
148
128
|
valid = false
|
149
129
|
# When parameter is a hash type, it is being set via
|
150
130
|
# intrinsic function and we don't modify
|
151
|
-
if(current_parameters[k]
|
131
|
+
if(function_set_parameter?(current_parameters[k]))
|
152
132
|
if(current_stack)
|
153
133
|
enable_set = validate_stack_parameter(current_stack, k, ns_k, current_parameters[k])
|
154
134
|
else
|
@@ -157,7 +137,7 @@ module Sfn
|
|
157
137
|
if(enable_set)
|
158
138
|
# NOTE: direct set dumps the stack (nfi). Smash will
|
159
139
|
# auto dup it, and works, so yay i guess.
|
160
|
-
config[:parameters][ns_k] = Smash.new(current_parameters[k])
|
140
|
+
config[:parameters][ns_k] = current_parameters[k].is_a?(Hash) ? Smash.new(current_parameters[k]) : current_parameters[k].dup
|
161
141
|
valid = true
|
162
142
|
end
|
163
143
|
else
|
@@ -189,7 +169,7 @@ module Sfn
|
|
189
169
|
else
|
190
170
|
answer = default
|
191
171
|
end
|
192
|
-
validation =
|
172
|
+
validation = validate_parameter(answer, v)
|
193
173
|
if(validation == true)
|
194
174
|
config[:parameters][ns_k] = answer
|
195
175
|
valid = true
|
@@ -215,6 +195,14 @@ module Sfn
|
|
215
195
|
]
|
216
196
|
end
|
217
197
|
|
198
|
+
# Determine if parameter was set via intrinsic function
|
199
|
+
#
|
200
|
+
# @param val [Object]
|
201
|
+
# @return [TrueClass, FalseClass]
|
202
|
+
def function_set_parameter?(val)
|
203
|
+
val.is_a?(Hash)
|
204
|
+
end
|
205
|
+
|
218
206
|
# @return [Hash] parameters for root stack create/update
|
219
207
|
def config_root_parameters
|
220
208
|
Hash[
|
@@ -280,6 +268,7 @@ module Sfn
|
|
280
268
|
klass.class_eval do
|
281
269
|
extend Sfn::CommandModule::Stack::ClassMethods
|
282
270
|
include Sfn::CommandModule::Stack::InstanceMethods
|
271
|
+
include Utils::StackParameterValidator
|
283
272
|
end
|
284
273
|
end
|
285
274
|
|