sfn 2.1.6 → 2.1.8
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 +7 -0
- data/lib/sfn/command.rb +1 -0
- data/lib/sfn/command/create.rb +1 -1
- data/lib/sfn/command/events.rb +26 -22
- data/lib/sfn/command/graph.rb +228 -0
- data/lib/sfn/command/inspect.rb +8 -6
- data/lib/sfn/command/update.rb +9 -20
- data/lib/sfn/command_module/base.rb +2 -1
- data/lib/sfn/command_module/template.rb +34 -10
- data/lib/sfn/config.rb +1 -0
- data/lib/sfn/config/events.rb +5 -0
- data/lib/sfn/config/graph.rb +24 -0
- data/lib/sfn/monkey_patch/stack.rb +1 -1
- data/lib/sfn/planner/aws.rb +33 -13
- data/lib/sfn/provider.rb +31 -16
- data/lib/sfn/version.rb +1 -1
- data/sfn.gemspec +3 -1
- metadata +42 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 610903a7a6be9347a5c0d51bff46e3eba9239e28
|
4
|
+
data.tar.gz: 0aacd95b68b649c70416a02c77c8bf06d60072cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cce4831036082b7f85c80e7d29b9d71aff2ae54b645830030126732642edfac1c10fd3e3a9e36f8bc889666cfd3bdc98f1c1113f948f3d6bc61310834cea2b9
|
7
|
+
data.tar.gz: bb744d8e3639da9440321c325f1f70ce593fa0e748795e0f4184e7dd9ec492345b85956755c83fcafac2227bfdb54b7a45e399715e767b521ca58d7533fb5185
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# v2.1.8
|
2
|
+
* [fix] Fix some planner errors caused by unexpected types (#146)
|
3
|
+
* [fix] Use common stack scrubbing implementation for create and update (#148)
|
4
|
+
* [fix] Properly expand nested stacks when displaying events (#148)
|
5
|
+
* [enhancement] Update internal stack data caching to reduce request numbers and size (#148)
|
6
|
+
* [feature] Add initial graph command implementation (currenty AWS only) (#152)
|
7
|
+
|
1
8
|
# v2.1.6
|
2
9
|
* [fix] Prevent configuration defaults overwriting user defined values (#144)
|
3
10
|
|
data/lib/sfn/command.rb
CHANGED
@@ -13,6 +13,7 @@ module Sfn
|
|
13
13
|
autoload :Diff, 'sfn/command/diff'
|
14
14
|
autoload :Events, 'sfn/command/events'
|
15
15
|
autoload :Export, 'sfn/command/export'
|
16
|
+
autoload :Graph, 'sfn/command/graph'
|
16
17
|
autoload :Import, 'sfn/command/import'
|
17
18
|
autoload :Init, 'sfn/command/init'
|
18
19
|
autoload :Inspect, 'sfn/command/inspect'
|
data/lib/sfn/command/create.rb
CHANGED
@@ -59,7 +59,7 @@ module Sfn
|
|
59
59
|
)
|
60
60
|
|
61
61
|
apply_stacks!(stack)
|
62
|
-
stack.template = Sfn::Utils::StackParameterScrubber.scrub!(stack.template)
|
62
|
+
stack.template = Sfn::Utils::StackParameterScrubber.scrub!(scrub_template(stack.template))
|
63
63
|
|
64
64
|
if(config[:print_only])
|
65
65
|
ui.puts _format_json(translate_template(stack.template))
|
data/lib/sfn/command/events.rb
CHANGED
@@ -15,10 +15,8 @@ module Sfn
|
|
15
15
|
name_required!
|
16
16
|
name = name_args.first
|
17
17
|
ui.info "Events for Stack: #{ui.color(name, :bold)}\n"
|
18
|
-
@
|
19
|
-
@stack = provider.
|
20
|
-
@stacks << stack
|
21
|
-
discover_stacks(stack)
|
18
|
+
@seen_events = []
|
19
|
+
@stack = provider.stack(name)
|
22
20
|
if(stack)
|
23
21
|
api_action!(:api_stack => stack) do
|
24
22
|
table = ui.table(self) do
|
@@ -41,13 +39,13 @@ module Sfn
|
|
41
39
|
end
|
42
40
|
end.display
|
43
41
|
if(config[:poll])
|
44
|
-
while(stack.in_progress?)
|
42
|
+
while(stack.reload.in_progress?)
|
45
43
|
to_wait = config.fetch(:poll_wait_time, 10).to_f
|
46
44
|
while(to_wait > 0)
|
47
45
|
sleep(0.1)
|
48
46
|
to_wait -= 0.1
|
49
47
|
end
|
50
|
-
stack.reload
|
48
|
+
stack.resources.reload
|
51
49
|
table.display
|
52
50
|
end
|
53
51
|
end
|
@@ -64,30 +62,36 @@ module Sfn
|
|
64
62
|
# @param last_id [String] only return events after this ID
|
65
63
|
# @return [Array<Hash>]
|
66
64
|
def get_events(*args)
|
67
|
-
discover_stacks(stack)
|
68
|
-
|
69
|
-
|
70
|
-
e.attributes.merge(:stack_name =>
|
65
|
+
stack_events = discover_stacks(stack).map do |i_stack|
|
66
|
+
i_events = i_stack.events.reload.all
|
67
|
+
i_events.map do |e|
|
68
|
+
e.attributes.merge(:stack_name => i_stack.name).to_smash
|
71
69
|
end
|
72
|
-
end.flatten.compact.find_all{|e| e[:time] }
|
73
|
-
stack_events.
|
74
|
-
|
70
|
+
end.flatten.compact.find_all{|e| e[:time] }.reverse
|
71
|
+
stack_events.delete_if{|evt| @seen_events.include?(evt)}
|
72
|
+
@seen_events.concat(stack_events)
|
73
|
+
unless(@initial_complete)
|
74
|
+
stack_events = stack_events.sort_by{|e| e[:time] }
|
75
|
+
unless(config[:all_events])
|
76
|
+
start_index = stack_events.rindex do |item|
|
77
|
+
item[:stack_name] == stack.name &&
|
78
|
+
item[:resource_state].to_s.end_with?('in_progress') &&
|
79
|
+
item[:resource_status_reason].to_s.downcase.include?('user init')
|
80
|
+
end
|
81
|
+
if(start_index)
|
82
|
+
stack_events.slice!(0, start_index)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@initial_complete = true
|
75
86
|
end
|
87
|
+
stack_events
|
76
88
|
end
|
77
89
|
|
78
90
|
# Discover stacks defined within the resources of given stack
|
79
91
|
#
|
80
92
|
# @param stack [Miasma::Models::Orchestration::Stack]
|
81
93
|
def discover_stacks(stack)
|
82
|
-
stack.
|
83
|
-
if(resource.type == 'AWS::CloudFormation::Stack')
|
84
|
-
nested_stack = provider.connection.stacks.get(resource.id)
|
85
|
-
if(nested_stack)
|
86
|
-
@stacks.push(nested_stack).uniq!
|
87
|
-
discover_stacks(nested_stack)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
94
|
+
@stacks = [stack] + stack.nested_stacks.reverse
|
91
95
|
end
|
92
96
|
|
93
97
|
# @return [Array<String>] default attributes for events
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'sfn'
|
2
|
+
require 'graph'
|
3
|
+
|
4
|
+
module Sfn
|
5
|
+
class Command
|
6
|
+
# Graph command
|
7
|
+
class Graph < Command
|
8
|
+
|
9
|
+
include Sfn::CommandModule::Base
|
10
|
+
include Sfn::CommandModule::Template
|
11
|
+
include Sfn::CommandModule::Stack
|
12
|
+
|
13
|
+
# Generate graph
|
14
|
+
def execute!
|
15
|
+
config[:print_only] = true
|
16
|
+
file = load_template_file
|
17
|
+
file.delete('sfn_nested_stack')
|
18
|
+
file = Sfn::Utils::StackParameterScrubber.scrub!(file)
|
19
|
+
file = translate_template(file)
|
20
|
+
@outputs = Smash.new
|
21
|
+
file = file.to_smash
|
22
|
+
ui.info 'Template resource graph generation'
|
23
|
+
if(config[:file])
|
24
|
+
ui.puts " -> path: #{config[:file]}"
|
25
|
+
end
|
26
|
+
run_action 'Pre-processing template for graphing' do
|
27
|
+
output_discovery(file, @outputs, nil, nil)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
graph = nil
|
31
|
+
run_action 'Generating resource graph' do
|
32
|
+
graph = generate_graph(file.to_smash)
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
run_action 'Writing graph result' do
|
36
|
+
FileUtils.mkdir_p(File.dirname(config[:output_file]))
|
37
|
+
if(config[:output_type] == 'dot')
|
38
|
+
File.open("#{config[:output_file]}.dot", 'w') do |file|
|
39
|
+
file.puts graph.to_s
|
40
|
+
end
|
41
|
+
else
|
42
|
+
graph.save config[:output_file], config[:output_type]
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_graph(template, args={})
|
49
|
+
graph = ::Graph.new
|
50
|
+
@root_graph = graph unless @root_graph
|
51
|
+
graph.graph_attribs << ::Graph::Attribute.new('overlap = false')
|
52
|
+
graph.graph_attribs << ::Graph::Attribute.new('splines = true')
|
53
|
+
graph.graph_attribs << ::Graph::Attribute.new('pack = true')
|
54
|
+
graph.graph_attribs << ::Graph::Attribute.new('start = "random"')
|
55
|
+
if(args[:name])
|
56
|
+
graph.name = "cluster_#{args[:name]}"
|
57
|
+
labelnode_key = "cluster_#{args[:name]}"
|
58
|
+
graph.plaintext << graph.node(labelnode_key)
|
59
|
+
graph.node(labelnode_key).label args[:name]
|
60
|
+
else
|
61
|
+
graph.name = 'root'
|
62
|
+
end
|
63
|
+
edge_detection(template, graph, args[:name].to_s.sub('cluster_', ''), args.fetch(:resource_names, []))
|
64
|
+
graph
|
65
|
+
end
|
66
|
+
|
67
|
+
def output_discovery(template, outputs, resource_name, parent_template, name='')
|
68
|
+
if(template['Resources'])
|
69
|
+
template['Resources'].each_pair do |r_name, r_info|
|
70
|
+
if(r_info['Type'] == 'AWS::CloudFormation::Stack')
|
71
|
+
output_discovery(r_info['Properties']['Stack'], outputs, r_name, template, r_name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
if(parent_template)
|
76
|
+
substack_parameters = Smash[
|
77
|
+
parent_template.fetch('Resources', resource_name, 'Properties', 'Parameters', {}).map do |key, value|
|
78
|
+
result = [key, value]
|
79
|
+
if(value.is_a?(Hash))
|
80
|
+
v_key = value.keys.first
|
81
|
+
v_value = value.values.first
|
82
|
+
if(v_key == 'Fn::GetAtt' && parent_template.fetch('Resources', {}).keys.include?(v_value.first) && v_value.last.start_with?('Outputs.'))
|
83
|
+
output_key = v_value.first << '__' << v_value.last.split('.', 2).last
|
84
|
+
if(outputs.key?(output_key))
|
85
|
+
new_value = outputs[output_key]
|
86
|
+
result = [key, new_value]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
]
|
93
|
+
processor = GraphProcessor.new({},
|
94
|
+
:parameters => substack_parameters
|
95
|
+
)
|
96
|
+
template['Resources'] = processor.dereference_processor(
|
97
|
+
template['Resources'], ['Ref']
|
98
|
+
)
|
99
|
+
template['Outputs'] = processor.dereference_processor(
|
100
|
+
template['Outputs'], ['Ref']
|
101
|
+
)
|
102
|
+
rename_processor = GraphProcessor.new({},
|
103
|
+
:parameters => Smash[
|
104
|
+
template.fetch('Resources', {}).keys.map do |r_key|
|
105
|
+
[r_key, {'Ref' => [name, r_key].join}]
|
106
|
+
end
|
107
|
+
]
|
108
|
+
)
|
109
|
+
derefed_outs = rename_processor.dereference_processor(
|
110
|
+
template.fetch('Outputs', {})
|
111
|
+
) || {}
|
112
|
+
|
113
|
+
derefed_outs.each do |o_name, o_data|
|
114
|
+
o_key = [name, o_name].join('__')
|
115
|
+
outputs[o_key] = o_data['Value']
|
116
|
+
end
|
117
|
+
end
|
118
|
+
outputs.dup.each do |key, value|
|
119
|
+
if(value.is_a?(Hash))
|
120
|
+
v_key = value.keys.first
|
121
|
+
v_value = value.values.first
|
122
|
+
if(v_key == 'Fn::GetAtt' && v_value.last.start_with?('Outputs.'))
|
123
|
+
output_key = v_value.first << '__' << v_value.last.split('.', 2).last
|
124
|
+
if(outputs.key?(output_key))
|
125
|
+
outputs[key] = outputs[output_key]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def edge_detection(template, graph, name = '', resource_names = [])
|
133
|
+
resources = template.fetch('Resources', {})
|
134
|
+
node_prefix = name
|
135
|
+
resources.each do |resource_name, resource_data|
|
136
|
+
node_name = [node_prefix, resource_name].join
|
137
|
+
if(resource_data['Type'] == 'AWS::CloudFormation::Stack')
|
138
|
+
graph.subgraph << generate_graph(
|
139
|
+
resource_data['Properties'].delete('Stack'),
|
140
|
+
:name => resource_name,
|
141
|
+
:type => resource_data['Type'],
|
142
|
+
:resource_names => resource_names
|
143
|
+
)
|
144
|
+
next
|
145
|
+
else
|
146
|
+
graph.node(node_name).attributes << graph.fillcolor(colorize(node_prefix).inspect)
|
147
|
+
graph.box3d << graph.node(node_name)
|
148
|
+
end
|
149
|
+
graph.filled << graph.node(node_name)
|
150
|
+
graph.node(node_name).label "#{resource_name}\n<#{resource_data['Type']}>\n#{name}"
|
151
|
+
resource_dependencies(resource_data, resource_names + resources.keys).each do |dep_name|
|
152
|
+
if(resources.keys.include?(dep_name))
|
153
|
+
dep_name = [node_prefix, dep_name].join
|
154
|
+
end
|
155
|
+
@root_graph.edge(dep_name, node_name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
resource_names.concat resources.keys.map{|r_name| [node_prefix, r_name].join}
|
159
|
+
end
|
160
|
+
|
161
|
+
def resource_dependencies(data, names)
|
162
|
+
case data
|
163
|
+
when Hash
|
164
|
+
data.map do |key, value|
|
165
|
+
if(key == 'Ref' && names.include?(value))
|
166
|
+
value
|
167
|
+
elsif(key == 'Fn::GetAtt' && names.include?(res = [value].flatten.compact.first))
|
168
|
+
res
|
169
|
+
else
|
170
|
+
resource_dependencies(key, names) +
|
171
|
+
resource_dependencies(value, names)
|
172
|
+
end
|
173
|
+
end.flatten.compact.uniq
|
174
|
+
when Array
|
175
|
+
data.map do |item|
|
176
|
+
resource_dependencies(item, names)
|
177
|
+
end.flatten.compact.uniq
|
178
|
+
else
|
179
|
+
[]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def colorize(string)
|
184
|
+
hash = string.chars.inject(0) do |memo, chr|
|
185
|
+
chr.ord + ((memo << 5) - memo)
|
186
|
+
end
|
187
|
+
color = '#'
|
188
|
+
6.times do |count|
|
189
|
+
color << ('00' + ((hash >> count * 8) & 0xFF).to_s(16)).slice(-2)
|
190
|
+
end
|
191
|
+
color
|
192
|
+
end
|
193
|
+
|
194
|
+
class GraphProcessor < SparkleFormation::Translation
|
195
|
+
MAP = {}
|
196
|
+
REF_MAPPING = {}
|
197
|
+
FN_MAPPING = {}
|
198
|
+
|
199
|
+
attr_accessor :name
|
200
|
+
|
201
|
+
def initialize(template, args={})
|
202
|
+
super
|
203
|
+
@name = args[:name]
|
204
|
+
end
|
205
|
+
|
206
|
+
def apply_function(hash, funcs=[])
|
207
|
+
k, v = hash.first
|
208
|
+
if(hash.size == 1)
|
209
|
+
case k
|
210
|
+
when 'Ref'
|
211
|
+
parameters.key?(v) ? parameters[v] : hash
|
212
|
+
when 'Fn::Join'
|
213
|
+
v.last
|
214
|
+
when 'Fn::Select'
|
215
|
+
v.last[v.first.to_i]
|
216
|
+
else
|
217
|
+
hash
|
218
|
+
end
|
219
|
+
else
|
220
|
+
hash
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
data/lib/sfn/command/inspect.rb
CHANGED
@@ -12,7 +12,7 @@ module Sfn
|
|
12
12
|
def execute!
|
13
13
|
name_required!
|
14
14
|
stack_name = name_args.last
|
15
|
-
stack = provider.
|
15
|
+
stack = provider.stack(stack_name)
|
16
16
|
ui.info "Stack inspection #{ui.color(stack_name, :bold)}:"
|
17
17
|
outputs = api_action!(:api_stack => stack) do
|
18
18
|
[:attribute, :nodes, :load_balancers, :instance_failure].map do |key|
|
@@ -95,7 +95,7 @@ module Sfn
|
|
95
95
|
def display_attribute(stack)
|
96
96
|
[config[:attribute]].flatten.compact.each do |stack_attribute|
|
97
97
|
attr = stack_attribute.split('.').inject(stack) do |memo, key|
|
98
|
-
args = key.scan(/\(([
|
98
|
+
args = key.scan(/\(([^\)]*)\)/).flatten.first.to_s
|
99
99
|
if(args)
|
100
100
|
args = args.split(',').map{|a| a.to_i.to_s == a ? a.to_i : a}
|
101
101
|
key = key.split('(').first
|
@@ -129,7 +129,7 @@ module Sfn
|
|
129
129
|
[
|
130
130
|
asg.name,
|
131
131
|
Smash[
|
132
|
-
asg.servers.map(&:expand).map{|s|
|
132
|
+
asg.servers.map(&:expand).compact.map{|s|
|
133
133
|
[s.id, Smash.new(
|
134
134
|
:name => s.name,
|
135
135
|
:addresses => s.addresses.map(&:address)
|
@@ -144,11 +144,13 @@ module Sfn
|
|
144
144
|
resource.within?(:compute, :servers)
|
145
145
|
end.map do |srv|
|
146
146
|
srv = srv.instance
|
147
|
-
|
147
|
+
if(srv)
|
148
|
+
[srv.id, Smash.new(
|
148
149
|
:name => srv.name,
|
149
150
|
:addresses => srv.addresses.map(&:address)
|
150
|
-
|
151
|
-
|
151
|
+
)]
|
152
|
+
end
|
153
|
+
end.compact
|
152
154
|
]
|
153
155
|
unless(asg_nodes.empty?)
|
154
156
|
ui.info ' AutoScale Group Instances:'
|
data/lib/sfn/command/update.rb
CHANGED
@@ -16,7 +16,7 @@ module Sfn
|
|
16
16
|
|
17
17
|
stack_info = "#{ui.color('Name:', :bold)} #{name}"
|
18
18
|
begin
|
19
|
-
stack = provider.
|
19
|
+
stack = provider.stacks.get(name)
|
20
20
|
rescue Miasma::Error::ApiError::RequestError
|
21
21
|
stack = nil
|
22
22
|
end
|
@@ -132,7 +132,6 @@ module Sfn
|
|
132
132
|
poll_stack(stack.name)
|
133
133
|
if(stack.reload.state == :update_complete)
|
134
134
|
ui.info "Stack update complete: #{ui.color('SUCCESS', :green)}"
|
135
|
-
stack.resources.reload
|
136
135
|
namespace.const_get(:Describe).new({:outputs => true}, [name]).execute!
|
137
136
|
else
|
138
137
|
ui.fatal "Update of stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
|
@@ -256,18 +255,22 @@ module Sfn
|
|
256
255
|
unless(val[:diffs].empty?)
|
257
256
|
p_name = nil
|
258
257
|
val[:diffs].each do |diff|
|
259
|
-
if(diff[:updated]
|
258
|
+
if(!diff[:updated].nil? || !diff[:original].nil?)
|
260
259
|
p_name = diff.fetch(:property_name, :path)
|
261
260
|
ui.print ' ' * 8
|
262
261
|
ui.print "#{p_name}: "
|
263
262
|
ui.print ' ' * (max_p - p_name.size)
|
264
|
-
ui.print ui.color("-#{diff[:original]}", :red)
|
265
|
-
ui.print ' ' * (max_o - diff[:original].size)
|
263
|
+
ui.print ui.color("-#{diff[:original]}", :red) unless diff[:original].nil?
|
264
|
+
ui.print ' ' * (max_o - diff[:original].to_s.size)
|
266
265
|
ui.print ' '
|
267
266
|
if(diff[:updated] == Sfn::Planner::RUNTIME_MODIFIED)
|
268
267
|
ui.puts ui.color("+#{diff[:original]} <Dependency Modified>", :green)
|
269
268
|
else
|
270
|
-
|
269
|
+
if(diff[:updated].nil?)
|
270
|
+
ui.puts
|
271
|
+
else
|
272
|
+
ui.puts ui.color("+#{diff[:updated]}", :green)
|
273
|
+
end
|
271
274
|
end
|
272
275
|
end
|
273
276
|
end
|
@@ -277,20 +280,6 @@ module Sfn
|
|
277
280
|
end
|
278
281
|
end
|
279
282
|
|
280
|
-
# Scrub sparkle/sfn customizations from the stack resource data
|
281
|
-
#
|
282
|
-
# @param template [Hash]
|
283
|
-
# @return [Hash]
|
284
|
-
def scrub_template(template)
|
285
|
-
template = Sfn::Utils::StackParameterScrubber.scrub!(template)
|
286
|
-
(template['Resources'] || {}).each do |r_name, r_content|
|
287
|
-
if(valid_stack_types.include?(r_content['Type']))
|
288
|
-
(r_content['Properties'] || {}).delete('Stack')
|
289
|
-
end
|
290
|
-
end
|
291
|
-
template
|
292
|
-
end
|
293
|
-
|
294
283
|
end
|
295
284
|
end
|
296
285
|
end
|
@@ -214,16 +214,13 @@ module Sfn
|
|
214
214
|
def process_nested_stack_shallow(sf, c_stack=nil)
|
215
215
|
sf.apply_nesting(:shallow) do |stack_name, stack, resource|
|
216
216
|
run_callbacks_for(:template, :stack_name => stack_name, :sparkle_stack => stack)
|
217
|
-
stack_definition = stack.compile.dump!
|
218
217
|
bucket = provider.connection.api_for(:storage).buckets.get(
|
219
218
|
config[:nesting_bucket]
|
220
219
|
)
|
221
220
|
if(config[:print_only])
|
222
221
|
template_url = "http://example.com/bucket/#{name_args.first}_#{stack_name}.json"
|
223
222
|
else
|
224
|
-
|
225
|
-
resource.properties.delete!(:stack)
|
226
|
-
end
|
223
|
+
stack_definition = dump_stack_for_storage(stack)
|
227
224
|
unless(bucket)
|
228
225
|
raise "Failed to locate configured bucket for stack template storage (#{bucket})!"
|
229
226
|
end
|
@@ -277,12 +274,7 @@ module Sfn
|
|
277
274
|
:current_parameters => current_parameters
|
278
275
|
)
|
279
276
|
)
|
280
|
-
|
281
|
-
stack_definition = stack.compile.dump!
|
282
|
-
|
283
|
-
if(config[:plan])
|
284
|
-
resource.properties.stack hold_stack
|
285
|
-
end
|
277
|
+
stack_definition = dump_stack_for_storage(stack)
|
286
278
|
bucket = provider.connection.api_for(:storage).buckets.get(
|
287
279
|
config[:nesting_bucket]
|
288
280
|
)
|
@@ -308,6 +300,38 @@ module Sfn
|
|
308
300
|
end
|
309
301
|
end
|
310
302
|
|
303
|
+
# Remove internally used `Stack` property from Stack resources and
|
304
|
+
# and generate compiled Hash
|
305
|
+
#
|
306
|
+
# @param stack [SparkleFormation]
|
307
|
+
# @return [Hash]
|
308
|
+
def dump_stack_for_storage(stack)
|
309
|
+
nested_stacks = stack.nested_stacks.map do |nested_resource|
|
310
|
+
[nested_resource.resource_name!, stack.compile.resources.set!(nested_resource.resource_name!).properties.delete!(:stack)]
|
311
|
+
end
|
312
|
+
stack_definition = stack.compile.dump!
|
313
|
+
if(config[:plan])
|
314
|
+
nested_stacks.each do |nested_name, nested_data|
|
315
|
+
stack.compile.resources.set!(nested_name).properties.stack nested_data
|
316
|
+
end
|
317
|
+
end
|
318
|
+
stack_definition
|
319
|
+
end
|
320
|
+
|
321
|
+
# Scrub sparkle/sfn customizations from the stack resource data
|
322
|
+
#
|
323
|
+
# @param template [Hash]
|
324
|
+
# @return [Hash]
|
325
|
+
def scrub_template(template)
|
326
|
+
template = Sfn::Utils::StackParameterScrubber.scrub!(template)
|
327
|
+
(template['Resources'] || {}).each do |r_name, r_content|
|
328
|
+
if(valid_stack_types.include?(r_content['Type']))
|
329
|
+
result = (r_content['Properties'] || {}).delete('Stack')
|
330
|
+
end
|
331
|
+
end
|
332
|
+
template
|
333
|
+
end
|
334
|
+
|
311
335
|
# Update the nested stack information for specific provider
|
312
336
|
#
|
313
337
|
# @param provider [Symbol]
|
data/lib/sfn/config.rb
CHANGED
@@ -48,6 +48,7 @@ module Sfn
|
|
48
48
|
autoload :Diff, 'sfn/config/diff'
|
49
49
|
autoload :Events, 'sfn/config/events'
|
50
50
|
autoload :Export, 'sfn/config/export'
|
51
|
+
autoload :Graph, 'sfn/config/graph'
|
51
52
|
autoload :Import, 'sfn/config/import'
|
52
53
|
autoload :Init, 'sfn/config/init'
|
53
54
|
autoload :Inspect, 'sfn/config/inspect'
|
data/lib/sfn/config/events.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'sfn'
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
class Config
|
5
|
+
# Generate graph
|
6
|
+
class Graph < Validate
|
7
|
+
|
8
|
+
attribute(
|
9
|
+
:output_file, String,
|
10
|
+
:description => 'Directory to write graph files',
|
11
|
+
:short_flag => 'O',
|
12
|
+
:default => File.join(Dir.pwd, 'sfn-graph')
|
13
|
+
)
|
14
|
+
|
15
|
+
attribute(
|
16
|
+
:output_type, String,
|
17
|
+
:description => 'File output type (Requires graphviz package for non-dot types)',
|
18
|
+
:short_flag => 'e',
|
19
|
+
:default => 'dot'
|
20
|
+
)
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -194,7 +194,7 @@ module Sfn
|
|
194
194
|
# @param recurse [TrueClass, FalseClass] recurse to fetch _all_ stacks
|
195
195
|
# @return [Array<Miasma::Models::Orchestration::Stack>]
|
196
196
|
def nested_stacks(recurse=true)
|
197
|
-
resources.all.map do |resource|
|
197
|
+
resources.reload.all.map do |resource|
|
198
198
|
if(api.data.fetch(:stack_types, []).include?(resource.type))
|
199
199
|
# Custom remote load support
|
200
200
|
if(resource.type == 'Custom::JackalStack')
|
data/lib/sfn/planner/aws.rb
CHANGED
@@ -130,7 +130,7 @@ module Sfn
|
|
130
130
|
# @return [Hash] report
|
131
131
|
def generate_plan(template, parameters)
|
132
132
|
parameters = Smash[parameters.map{|k,v| [k, v.to_s]}]
|
133
|
-
Smash.new(
|
133
|
+
result = Smash.new(
|
134
134
|
:stacks => Smash.new(
|
135
135
|
origin_stack.name => plan_stack(
|
136
136
|
origin_stack,
|
@@ -145,10 +145,27 @@ module Sfn
|
|
145
145
|
:unavailable => Smash.new,
|
146
146
|
:unknown => Smash.new
|
147
147
|
)
|
148
|
+
# scrub_stack_properties(template)
|
149
|
+
result
|
148
150
|
end
|
149
151
|
|
150
152
|
protected
|
151
153
|
|
154
|
+
# Remote custom Stack property from Stack resources within template
|
155
|
+
#
|
156
|
+
# @param template [Hash]
|
157
|
+
# @return [TrueClass]
|
158
|
+
def scrub_stack_properties(template)
|
159
|
+
if(template['Resources'])
|
160
|
+
template['Resources'].each do |name, info|
|
161
|
+
if(is_stack?(info['Type']) && info['Properties'].is_a?(Hash))
|
162
|
+
info['Properties'].delete('Stack')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
152
169
|
# Set global parameters available for all template translations.
|
153
170
|
# These are pseudo-parameters that are provided by the
|
154
171
|
# orchestration api runtime.
|
@@ -257,16 +274,17 @@ module Sfn
|
|
257
274
|
o_nested_stacks = origin_template.fetch('Resources', {}).find_all do |s_name, s_val|
|
258
275
|
is_stack?(s_val['Type'])
|
259
276
|
end.map(&:first)
|
260
|
-
n_nested_stacks = new_template_hash
|
277
|
+
n_nested_stacks = (new_template_hash['Resources'] || {}).find_all do |s_name, s_val|
|
261
278
|
is_stack?(s_val['Type'])
|
262
279
|
end.map(&:first)
|
263
280
|
[o_nested_stacks + n_nested_stacks].flatten.compact.uniq.each do |n_name|
|
264
281
|
o_stack = stack.nested_stacks(false).detect{|s| s.data[:logical_id] == n_name}
|
265
|
-
n_exists = is_stack?(new_template_hash
|
266
|
-
n_template = new_template_hash
|
267
|
-
n_parameters = new_template_hash
|
268
|
-
n_type = new_template_hash
|
269
|
-
origin_template
|
282
|
+
n_exists = is_stack?(new_template_hash.get('Resources', n_name, 'Type'))
|
283
|
+
n_template = new_template_hash.get('Resources', n_name, 'Properties', 'Stack')
|
284
|
+
n_parameters = new_template_hash.fetch('Resources', n_name, 'Properties', 'Parameters', Smash.new)
|
285
|
+
n_type = new_template_hash.fetch('Resources', n_name, 'Type',
|
286
|
+
origin_template.get('Resources', n_name, 'Type')
|
287
|
+
)
|
270
288
|
resource = Smash.new(
|
271
289
|
:name => n_name,
|
272
290
|
:type => n_type,
|
@@ -290,9 +308,7 @@ module Sfn
|
|
290
308
|
end
|
291
309
|
end
|
292
310
|
|
293
|
-
|
294
|
-
new_template_hash['Resources'][ns_name]['Properties'].delete('Stack')
|
295
|
-
end
|
311
|
+
scrub_stack_properties(new_template_hash)
|
296
312
|
|
297
313
|
update_template = dereference_template(
|
298
314
|
t_key, new_template_hash, new_parameters,
|
@@ -354,10 +370,14 @@ module Sfn
|
|
354
370
|
else
|
355
371
|
if(p_path.include?('Properties'))
|
356
372
|
resource_name = p_path[1]
|
357
|
-
|
373
|
+
if(p_path.size < 4 && p_path.last == 'Properties')
|
374
|
+
property_name = diff.flatten.compact.last.keys.first
|
375
|
+
else
|
376
|
+
property_name = p_path[3].to_s.sub(/\[\d+\]$/, '')
|
377
|
+
end
|
358
378
|
type = templates[:origin]['Resources'][resource_name]['Type']
|
359
|
-
info = SfnAws.registry.fetch(type, {})
|
360
|
-
effect = info
|
379
|
+
info = SfnAws.registry.fetch(type, {}).to_smash
|
380
|
+
effect = info.fetch(:full_properties, property_name, :update_causes, :unknown).to_sym
|
361
381
|
resource = Smash.new(
|
362
382
|
:name => resource_name,
|
363
383
|
:type => type,
|
data/lib/sfn/provider.rb
CHANGED
@@ -88,20 +88,26 @@ module Sfn
|
|
88
88
|
end
|
89
89
|
|
90
90
|
# @return [Miasma::Orchestration::Stacks]
|
91
|
-
def stacks
|
92
|
-
connection.stacks.from_json(cached_stacks)
|
91
|
+
def stacks(stack_id=nil)
|
92
|
+
connection.stacks.from_json(cached_stacks(stack_id))
|
93
93
|
end
|
94
94
|
|
95
95
|
# @return [String] json representation of cached stacks
|
96
|
-
def cached_stacks
|
97
|
-
|
96
|
+
def cached_stacks(stack_id=nil)
|
97
|
+
unless(@initial_fetch_complete || stack_id)
|
98
|
+
recache = true
|
99
|
+
if(stack_id && @initial_fetch_complete)
|
100
|
+
recache = !!stacks.get(stack_id)
|
101
|
+
end
|
102
|
+
fetch_stacks(stack_id) if recache
|
103
|
+
end
|
98
104
|
value = cache[:stacks].value
|
99
105
|
value ? MultiJson.dump(MultiJson.load(value).values) : '[]'
|
100
106
|
end
|
101
107
|
|
102
108
|
# @return [Miasma::Orchestration::Stack, NilClass]
|
103
109
|
def stack(stack_id)
|
104
|
-
stacks.get(stack_id)
|
110
|
+
stacks(stack_id).get(stack_id)
|
105
111
|
end
|
106
112
|
|
107
113
|
# Store stack attribute changes
|
@@ -162,23 +168,32 @@ module Sfn
|
|
162
168
|
# Request stack information and store in cache
|
163
169
|
#
|
164
170
|
# @return [TrueClass]
|
165
|
-
def fetch_stacks
|
171
|
+
def fetch_stacks(stack_id=nil)
|
166
172
|
cache.locked_action(:stacks_lock) do
|
167
173
|
logger.info "Lock aquired for stack update. Requesting stacks from upstream. (#{Thread.current})"
|
168
|
-
|
169
|
-
connection.stacks.
|
170
|
-
|
171
|
-
|
172
|
-
|
174
|
+
if(stack_id)
|
175
|
+
single_stack = connection.stacks.get(stack_id)
|
176
|
+
stacks = single_stack ? {single_stack.id => single_stack} : {}
|
177
|
+
else
|
178
|
+
stacks = Hash[
|
179
|
+
connection.stacks.reload.all.map do |stack|
|
180
|
+
[stack.id, stack.attributes]
|
181
|
+
end
|
182
|
+
]
|
183
|
+
end
|
173
184
|
if(cache[:stacks].value)
|
174
185
|
existing_stacks = MultiJson.load(cache[:stacks].value)
|
175
186
|
# Force common types
|
176
187
|
stacks = MultiJson.load(MultiJson.dump(stacks))
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
stacks.
|
188
|
+
if(stack_id)
|
189
|
+
stacks = existing_stacks.to_smash.deep_merge(stacks)
|
190
|
+
else
|
191
|
+
# Remove stacks that have been deleted
|
192
|
+
stale_ids = existing_stacks.keys - stacks.keys
|
193
|
+
stacks = existing_stacks.to_smash.deep_merge(stacks)
|
194
|
+
stale_ids.each do |stale_id|
|
195
|
+
stacks.delete(stale_id)
|
196
|
+
end
|
182
197
|
end
|
183
198
|
end
|
184
199
|
cache[:stacks].value = stacks.to_json
|
data/lib/sfn/version.rb
CHANGED
data/sfn.gemspec
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.license = 'Apache-2.0'
|
12
12
|
s.require_path = 'lib'
|
13
13
|
s.add_runtime_dependency 'bogo-cli', '>= 0.2.4', '< 0.4'
|
14
|
+
s.add_runtime_dependency 'bogo-ui', '>= 0.1.13', '< 0.4'
|
14
15
|
s.add_runtime_dependency 'miasma', '>= 0.3.0', '< 0.4'
|
15
16
|
s.add_runtime_dependency 'miasma-aws', '>= 0.3.0', '< 0.4'
|
16
17
|
s.add_runtime_dependency 'miasma-azure', '>= 0.1.0', '< 0.3'
|
@@ -19,7 +20,8 @@ Gem::Specification.new do |s|
|
|
19
20
|
s.add_runtime_dependency 'net-ssh'
|
20
21
|
s.add_runtime_dependency 'sparkle_formation', '>= 2.1.2', '< 3'
|
21
22
|
s.add_runtime_dependency 'hashdiff', '~> 0.2.2'
|
22
|
-
s.
|
23
|
+
s.add_runtime_dependency 'graph', '~> 2.8.1'
|
24
|
+
s.add_development_dependency 'rake', '~> 10'
|
23
25
|
s.add_development_dependency 'minitest'
|
24
26
|
s.add_development_dependency 'mocha'
|
25
27
|
s.executables << 'sfn'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sfn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Roberts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bogo-cli
|
@@ -30,6 +30,26 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '0.4'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bogo-ui
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.1.13
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.4'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.1.13
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.4'
|
33
53
|
- !ruby/object:Gem::Dependency
|
34
54
|
name: miasma
|
35
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,20 +198,34 @@ dependencies:
|
|
178
198
|
- - "~>"
|
179
199
|
- !ruby/object:Gem::Version
|
180
200
|
version: 0.2.2
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: graph
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - "~>"
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: 2.8.1
|
208
|
+
type: :runtime
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - "~>"
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: 2.8.1
|
181
215
|
- !ruby/object:Gem::Dependency
|
182
216
|
name: rake
|
183
217
|
requirement: !ruby/object:Gem::Requirement
|
184
218
|
requirements:
|
185
|
-
- - "
|
219
|
+
- - "~>"
|
186
220
|
- !ruby/object:Gem::Version
|
187
|
-
version: '
|
221
|
+
version: '10'
|
188
222
|
type: :development
|
189
223
|
prerelease: false
|
190
224
|
version_requirements: !ruby/object:Gem::Requirement
|
191
225
|
requirements:
|
192
|
-
- - "
|
226
|
+
- - "~>"
|
193
227
|
- !ruby/object:Gem::Version
|
194
|
-
version: '
|
228
|
+
version: '10'
|
195
229
|
- !ruby/object:Gem::Dependency
|
196
230
|
name: minitest
|
197
231
|
requirement: !ruby/object:Gem::Requirement
|
@@ -271,6 +305,7 @@ files:
|
|
271
305
|
- lib/sfn/command/diff.rb
|
272
306
|
- lib/sfn/command/events.rb
|
273
307
|
- lib/sfn/command/export.rb
|
308
|
+
- lib/sfn/command/graph.rb
|
274
309
|
- lib/sfn/command/import.rb
|
275
310
|
- lib/sfn/command/init.rb
|
276
311
|
- lib/sfn/command/inspect.rb
|
@@ -292,6 +327,7 @@ files:
|
|
292
327
|
- lib/sfn/config/diff.rb
|
293
328
|
- lib/sfn/config/events.rb
|
294
329
|
- lib/sfn/config/export.rb
|
330
|
+
- lib/sfn/config/graph.rb
|
295
331
|
- lib/sfn/config/import.rb
|
296
332
|
- lib/sfn/config/init.rb
|
297
333
|
- lib/sfn/config/inspect.rb
|