sfn 2.2.0 → 3.0.0
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 +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
|
|