sfn 3.0.26 → 3.0.28
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 +5 -0
- data/lib/sfn/api_provider/terraform.rb +0 -1
- data/lib/sfn/callback/stack_policy.rb +2 -2
- data/lib/sfn/command.rb +3 -3
- data/lib/sfn/command/events.rb +10 -4
- data/lib/sfn/command/graph.rb +12 -166
- data/lib/sfn/command/graph/aws.rb +174 -0
- data/lib/sfn/command/graph/provider.rb +13 -0
- data/lib/sfn/command/graph/terraform.rb +193 -0
- data/lib/sfn/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 403602f06deb39fe3f2751236c97dd6f06443431
|
4
|
+
data.tar.gz: 69f16806fb62a281b40d8f6588b4d5bf5772419d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b36166fbdb2424563aec23d2c068616264582b25192ab4e377ae7312aa210d1565d721270af1885bc14196e4f56a05afa47afa1b926a815de278aa8c90591415
|
7
|
+
data.tar.gz: 710cf024266fbe9725150f0930756e28d5a284b85db61ad033b048e106fd90bc3cd94c3c8e4b316f474b65e2148acc3e02afa9b06c2b3f676f557cb48a09c07d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# v3.0.28
|
2
|
+
* [fix] Fix stack save policy callback (#264)
|
3
|
+
* [fix] Prevent processing unset event arrays
|
4
|
+
* [enhancement] Support graphing terraform provider templates
|
5
|
+
|
1
6
|
# v3.0.26
|
2
7
|
* [fix] Properly support compile time parameter setting with/without stack name (#261)
|
3
8
|
* [fix] Default stack name when storing policy internally if no name provided
|
@@ -80,8 +80,8 @@ module Sfn
|
|
80
80
|
@policies[p_stack.name]
|
81
81
|
).to_smash
|
82
82
|
if(stack_policy)
|
83
|
-
stack_policy[:
|
84
|
-
policy_match = policy_item[:
|
83
|
+
stack_policy[:Statement].delete_if do |policy_item|
|
84
|
+
policy_match = policy_item[:Resource].to_s.match(
|
85
85
|
%r{LogicalResourceId/(?<logical_id>.+)$}
|
86
86
|
)
|
87
87
|
if(policy_match)
|
data/lib/sfn/command.rb
CHANGED
@@ -65,13 +65,13 @@ module Sfn
|
|
65
65
|
# @return [TrueClass, FalseClass]
|
66
66
|
def load_api_provider_extensions!
|
67
67
|
if(config.get(:credentials, :provider))
|
68
|
-
base_ext = Bogo::Utility.camel(config.get(:credentials, :provider))
|
68
|
+
base_ext = Bogo::Utility.camel(config.get(:credentials, :provider)).to_sym
|
69
69
|
targ_ext = self.class.name.split('::').last
|
70
|
-
if(ApiProvider.
|
70
|
+
if(ApiProvider.constants.include?(base_ext))
|
71
71
|
base_module = ApiProvider.const_get(base_ext)
|
72
72
|
ui.debug "Loading core provider extensions via `#{base_module}`"
|
73
73
|
extend base_module
|
74
|
-
if(base_module.
|
74
|
+
if(base_module.constants.include?(targ_ext))
|
75
75
|
targ_module = base_module.const_get(targ_ext)
|
76
76
|
ui.debug "Loading targeted provider extensions via `#{targ_module}`"
|
77
77
|
extend targ_module
|
data/lib/sfn/command/events.rb
CHANGED
@@ -63,6 +63,7 @@ module Sfn
|
|
63
63
|
# @return [Array<Hash>]
|
64
64
|
def get_events(*args)
|
65
65
|
stack_events = discover_stacks(stack).map do |i_stack|
|
66
|
+
i_events = []
|
66
67
|
begin
|
67
68
|
if(@initial_complete && i_stack.in_progress?)
|
68
69
|
i_events = i_stack.events.update!
|
@@ -73,12 +74,17 @@ module Sfn
|
|
73
74
|
if(e.class.to_s.start_with?('Errno'))
|
74
75
|
ui.warn "Connection error encountered: #{e.message} (retrying)"
|
75
76
|
ui.debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
76
|
-
|
77
|
-
|
77
|
+
else
|
78
|
+
ui.error "Unexpected error received fetching events: #{e.message}"
|
79
|
+
ui.debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
78
80
|
end
|
81
|
+
sleep(5)
|
82
|
+
retry
|
79
83
|
end
|
80
|
-
i_events
|
81
|
-
|
84
|
+
if(i_events)
|
85
|
+
i_events.map do |e|
|
86
|
+
e.attributes.merge(:stack_name => i_stack.name).to_smash
|
87
|
+
end
|
82
88
|
end
|
83
89
|
end.flatten.compact.find_all{|e| e[:time] }.reverse
|
84
90
|
stack_events.delete_if{|evt| @seen_events.include?(evt)}
|
data/lib/sfn/command/graph.rb
CHANGED
@@ -6,6 +6,8 @@ module Sfn
|
|
6
6
|
# Graph command
|
7
7
|
class Graph < Command
|
8
8
|
|
9
|
+
autoload :Provider, 'sfn/command/graph/provider'
|
10
|
+
|
9
11
|
include Sfn::CommandModule::Base
|
10
12
|
include Sfn::CommandModule::Template
|
11
13
|
include Sfn::CommandModule::Stack
|
@@ -21,13 +23,17 @@ module Sfn
|
|
21
23
|
config[:print_only] = true
|
22
24
|
validate_graph_style!
|
23
25
|
file = load_template_file
|
24
|
-
|
26
|
+
provider = Bogo::Utility.camel(file.provider).to_sym
|
27
|
+
if(Provider.constants.include?(provider))
|
28
|
+
graph_const = Provider.const_get(provider)
|
29
|
+
ui.debug "Loading provider graph implementation - #{graph_const}"
|
30
|
+
extend graph_const
|
25
31
|
@outputs = Smash.new
|
26
32
|
ui.info "Template resource graph generation - Style: #{ui.color(config[:graph_style], :bold)}"
|
27
33
|
if(config[:file])
|
28
34
|
ui.puts " -> path: #{config[:file]}"
|
29
35
|
end
|
30
|
-
template_dump = file.compile.
|
36
|
+
template_dump = file.compile.sparkle_dump!.to_smash
|
31
37
|
run_action 'Pre-processing template for graphing' do
|
32
38
|
output_discovery(template_dump, @outputs, nil, nil)
|
33
39
|
ui.debug 'Output remapping results from pre-processing:'
|
@@ -53,8 +59,11 @@ module Sfn
|
|
53
59
|
nil
|
54
60
|
end
|
55
61
|
else
|
62
|
+
valid_providers = Provider.constants.sort.map{ |provider|
|
63
|
+
Bogo::Utility.snake(provider)
|
64
|
+
}.join('`, `')
|
56
65
|
ui.error "Graphing for provider `#{file.provider}` not currently supported."
|
57
|
-
ui.error "Currently supported providers:
|
66
|
+
ui.error "Currently supported providers: `#{valid_providers}`."
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
@@ -77,137 +86,6 @@ module Sfn
|
|
77
86
|
graph
|
78
87
|
end
|
79
88
|
|
80
|
-
def output_discovery(template, outputs, resource_name, parent_template, name='')
|
81
|
-
if(template['Resources'])
|
82
|
-
template['Resources'].keys.each do |r_name|
|
83
|
-
r_info = template['Resources'][r_name]
|
84
|
-
if(r_info['Type'] == 'AWS::CloudFormation::Stack')
|
85
|
-
output_discovery(r_info['Properties']['Stack'], outputs, r_name, template, r_name)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
if(parent_template)
|
90
|
-
ui.debug "Pre-processing stack resource `#{resource_name}`"
|
91
|
-
substack_parameters = Smash[
|
92
|
-
parent_template.fetch('Resources', resource_name, 'Properties', 'Parameters', {}).map do |key, value|
|
93
|
-
result = [key, value]
|
94
|
-
if(value.is_a?(Hash))
|
95
|
-
v_key = value.keys.first
|
96
|
-
v_value = value.values.first
|
97
|
-
if(v_key == 'Fn::GetAtt' && parent_template.fetch('Resources', {}).keys.include?(v_value.first) && v_value.last.start_with?('Outputs.'))
|
98
|
-
output_key = v_value.first + '__' + v_value.last.split('.', 2).last
|
99
|
-
ui.debug "Output key for check: #{output_key}"
|
100
|
-
if(outputs.key?(output_key))
|
101
|
-
new_value = outputs[output_key]
|
102
|
-
result = [key, new_value]
|
103
|
-
ui.debug "Parameter for output swap `#{key}`: #{value} -> #{new_value}"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
result
|
108
|
-
end
|
109
|
-
]
|
110
|
-
|
111
|
-
ui.debug "Generated internal parameters for `#{resource_name}`: #{substack_parameters}"
|
112
|
-
|
113
|
-
processor = GraphProcessor.new({},
|
114
|
-
:parameters => substack_parameters
|
115
|
-
)
|
116
|
-
template['Resources'] = processor.dereference_processor(
|
117
|
-
template['Resources'], ['Ref']
|
118
|
-
)
|
119
|
-
template['Outputs'] = processor.dereference_processor(
|
120
|
-
template['Outputs'], ['Ref']
|
121
|
-
)
|
122
|
-
rename_processor = GraphProcessor.new({},
|
123
|
-
:parameters => Smash[
|
124
|
-
template.fetch('Resources', {}).keys.map do |r_key|
|
125
|
-
[r_key, {'Ref' => [name, r_key].join}]
|
126
|
-
end
|
127
|
-
]
|
128
|
-
)
|
129
|
-
derefed_outs = rename_processor.dereference_processor(
|
130
|
-
template.fetch('Outputs', {})
|
131
|
-
) || {}
|
132
|
-
|
133
|
-
derefed_outs.each do |o_name, o_data|
|
134
|
-
o_key = [name, o_name].join('__')
|
135
|
-
outputs[o_key] = o_data['Value']
|
136
|
-
end
|
137
|
-
end
|
138
|
-
outputs.dup.each do |key, value|
|
139
|
-
if(value.is_a?(Hash))
|
140
|
-
v_key = value.keys.first
|
141
|
-
v_value = value.values.first
|
142
|
-
if(v_key == 'Fn::GetAtt' && v_value.last.start_with?('Outputs.'))
|
143
|
-
output_key = v_value.first << '__' << v_value.last.split('.', 2).last
|
144
|
-
if(outputs.key?(output_key))
|
145
|
-
outputs[key] = outputs[output_key]
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def edge_detection(template, graph, name = '', resource_names = [])
|
153
|
-
resources = template.fetch('Resources', {})
|
154
|
-
node_prefix = name
|
155
|
-
resources.each do |resource_name, resource_data|
|
156
|
-
node_name = [node_prefix, resource_name].join
|
157
|
-
if(resource_data['Type'] == 'AWS::CloudFormation::Stack')
|
158
|
-
graph.subgraph << generate_graph(
|
159
|
-
resource_data['Properties'].delete('Stack'),
|
160
|
-
:name => resource_name,
|
161
|
-
:type => resource_data['Type'],
|
162
|
-
:resource_names => resource_names
|
163
|
-
)
|
164
|
-
next
|
165
|
-
else
|
166
|
-
graph.node(node_name).attributes << graph.fillcolor(colorize(node_prefix.empty? ? config[:file] : node_prefix).inspect)
|
167
|
-
graph.box3d << graph.node(node_name)
|
168
|
-
end
|
169
|
-
graph.filled << graph.node(node_name)
|
170
|
-
graph.node(node_name).label "#{resource_name}\n<#{resource_data['Type']}>\n#{name}"
|
171
|
-
resource_dependencies(resource_data, resource_names + resources.keys).each do |dep_name|
|
172
|
-
if(resources.keys.include?(dep_name))
|
173
|
-
dep_name = [node_prefix, dep_name].join
|
174
|
-
end
|
175
|
-
if(config[:graph_style] == 'creation')
|
176
|
-
@root_graph.edge(dep_name, node_name)
|
177
|
-
else
|
178
|
-
@root_graph.edge(node_name, dep_name)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
resource_names.concat resources.keys.map{|r_name| [node_prefix, r_name].join}
|
183
|
-
end
|
184
|
-
|
185
|
-
def resource_dependencies(data, names)
|
186
|
-
case data
|
187
|
-
when Hash
|
188
|
-
data.map do |key, value|
|
189
|
-
if(key == 'Ref' && names.include?(value))
|
190
|
-
value
|
191
|
-
elsif(key == 'DependsOn')
|
192
|
-
[value].flatten.compact.find_all do |dependson_name|
|
193
|
-
names.include?(dependson_name)
|
194
|
-
end
|
195
|
-
elsif(key == 'Fn::GetAtt' && names.include?(res = [value].flatten.compact.first))
|
196
|
-
res
|
197
|
-
else
|
198
|
-
resource_dependencies(key, names) +
|
199
|
-
resource_dependencies(value, names)
|
200
|
-
end
|
201
|
-
end.flatten.compact.uniq
|
202
|
-
when Array
|
203
|
-
data.map do |item|
|
204
|
-
resource_dependencies(item, names)
|
205
|
-
end.flatten.compact.uniq
|
206
|
-
else
|
207
|
-
[]
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
89
|
def colorize(string)
|
212
90
|
hash = string.chars.inject(0) do |memo, chr|
|
213
91
|
if(memo + chr.ord > 127)
|
@@ -240,38 +118,6 @@ module Sfn
|
|
240
118
|
end
|
241
119
|
end
|
242
120
|
|
243
|
-
class GraphProcessor < SparkleFormation::Translation
|
244
|
-
MAP = {}
|
245
|
-
REF_MAPPING = {}
|
246
|
-
FN_MAPPING = {}
|
247
|
-
|
248
|
-
attr_accessor :name
|
249
|
-
|
250
|
-
def initialize(template, args={})
|
251
|
-
super
|
252
|
-
@name = args[:name]
|
253
|
-
end
|
254
|
-
|
255
|
-
def apply_function(hash, funcs=[])
|
256
|
-
k, v = hash.first
|
257
|
-
if(hash.size == 1)
|
258
|
-
case k
|
259
|
-
when 'Ref'
|
260
|
-
parameters.key?(v) ? parameters[v] : hash
|
261
|
-
when 'Fn::Join'
|
262
|
-
v.last
|
263
|
-
when 'Fn::Select'
|
264
|
-
v.last[v.first.to_i]
|
265
|
-
else
|
266
|
-
hash
|
267
|
-
end
|
268
|
-
else
|
269
|
-
hash
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
end
|
274
|
-
|
275
121
|
end
|
276
122
|
end
|
277
123
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'sfn'
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
class Command
|
5
|
+
# Graph command
|
6
|
+
class Graph < Command
|
7
|
+
module Provider
|
8
|
+
module Aws
|
9
|
+
class AwsGraphProcessor < SparkleFormation::Translation
|
10
|
+
MAP = {}
|
11
|
+
REF_MAPPING = {}
|
12
|
+
FN_MAPPING = {}
|
13
|
+
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
def initialize(template, args={})
|
17
|
+
super
|
18
|
+
@name = args[:name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply_function(hash, funcs=[])
|
22
|
+
k, v = hash.first
|
23
|
+
if(hash.size == 1)
|
24
|
+
case k
|
25
|
+
when 'Ref'
|
26
|
+
parameters.key?(v) ? parameters[v] : hash
|
27
|
+
when 'Fn::Join'
|
28
|
+
v.last
|
29
|
+
when 'Fn::Select'
|
30
|
+
v.last[v.first.to_i]
|
31
|
+
else
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
else
|
35
|
+
hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_discovery(template, outputs, resource_name, parent_template, name='')
|
41
|
+
if(template['Resources'])
|
42
|
+
template['Resources'].keys.each do |r_name|
|
43
|
+
r_info = template['Resources'][r_name]
|
44
|
+
if(r_info['Type'] == 'AWS::CloudFormation::Stack')
|
45
|
+
output_discovery(r_info['Properties']['Stack'], outputs, r_name, template, r_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
if(parent_template)
|
50
|
+
ui.debug "Pre-processing stack resource `#{resource_name}`"
|
51
|
+
substack_parameters = Smash[
|
52
|
+
parent_template.fetch('Resources', resource_name, 'Properties', 'Parameters', {}).map do |key, value|
|
53
|
+
result = [key, value]
|
54
|
+
if(value.is_a?(Hash))
|
55
|
+
v_key = value.keys.first
|
56
|
+
v_value = value.values.first
|
57
|
+
if(v_key == 'Fn::GetAtt' && parent_template.fetch('Resources', {}).keys.include?(v_value.first) && v_value.last.start_with?('Outputs.'))
|
58
|
+
output_key = v_value.first + '__' + v_value.last.split('.', 2).last
|
59
|
+
ui.debug "Output key for check: #{output_key}"
|
60
|
+
if(outputs.key?(output_key))
|
61
|
+
new_value = outputs[output_key]
|
62
|
+
result = [key, new_value]
|
63
|
+
ui.debug "Parameter for output swap `#{key}`: #{value} -> #{new_value}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
result
|
68
|
+
end
|
69
|
+
]
|
70
|
+
|
71
|
+
ui.debug "Generated internal parameters for `#{resource_name}`: #{substack_parameters}"
|
72
|
+
|
73
|
+
processor = AwsGraphProcessor.new({},
|
74
|
+
:parameters => substack_parameters
|
75
|
+
)
|
76
|
+
template['Resources'] = processor.dereference_processor(
|
77
|
+
template['Resources'], ['Ref']
|
78
|
+
)
|
79
|
+
template['Outputs'] = processor.dereference_processor(
|
80
|
+
template['Outputs'], ['Ref']
|
81
|
+
)
|
82
|
+
rename_processor = AwsGraphProcessor.new({},
|
83
|
+
:parameters => Smash[
|
84
|
+
template.fetch('Resources', {}).keys.map do |r_key|
|
85
|
+
[r_key, {'Ref' => [name, r_key].join}]
|
86
|
+
end
|
87
|
+
]
|
88
|
+
)
|
89
|
+
derefed_outs = rename_processor.dereference_processor(
|
90
|
+
template.fetch('Outputs', {})
|
91
|
+
) || {}
|
92
|
+
|
93
|
+
derefed_outs.each do |o_name, o_data|
|
94
|
+
o_key = [name, o_name].join('__')
|
95
|
+
outputs[o_key] = o_data['Value']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
outputs.dup.each do |key, value|
|
99
|
+
if(value.is_a?(Hash))
|
100
|
+
v_key = value.keys.first
|
101
|
+
v_value = value.values.first
|
102
|
+
if(v_key == 'Fn::GetAtt' && v_value.last.start_with?('Outputs.'))
|
103
|
+
output_key = v_value.first << '__' << v_value.last.split('.', 2).last
|
104
|
+
if(outputs.key?(output_key))
|
105
|
+
outputs[key] = outputs[output_key]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def edge_detection(template, graph, name = '', resource_names = [])
|
113
|
+
resources = template.fetch('Resources', {})
|
114
|
+
node_prefix = name
|
115
|
+
resources.each do |resource_name, resource_data|
|
116
|
+
node_name = [node_prefix, resource_name].join
|
117
|
+
if(resource_data['Type'] == 'AWS::CloudFormation::Stack')
|
118
|
+
graph.subgraph << generate_graph(
|
119
|
+
resource_data['Properties'].delete('Stack'),
|
120
|
+
:name => resource_name,
|
121
|
+
:type => resource_data['Type'],
|
122
|
+
:resource_names => resource_names
|
123
|
+
)
|
124
|
+
next
|
125
|
+
else
|
126
|
+
graph.node(node_name).attributes << graph.fillcolor(colorize(node_prefix.empty? ? config[:file] : node_prefix).inspect)
|
127
|
+
graph.box3d << graph.node(node_name)
|
128
|
+
end
|
129
|
+
graph.filled << graph.node(node_name)
|
130
|
+
graph.node(node_name).label "#{resource_name}\n<#{resource_data['Type']}>\n#{name}"
|
131
|
+
resource_dependencies(resource_data, resource_names + resources.keys).each do |dep_name|
|
132
|
+
if(resources.keys.include?(dep_name))
|
133
|
+
dep_name = [node_prefix, dep_name].join
|
134
|
+
end
|
135
|
+
if(config[:graph_style] == 'creation')
|
136
|
+
@root_graph.edge(dep_name, node_name)
|
137
|
+
else
|
138
|
+
@root_graph.edge(node_name, dep_name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
resource_names.concat resources.keys.map{|r_name| [node_prefix, r_name].join}
|
143
|
+
end
|
144
|
+
|
145
|
+
def resource_dependencies(data, names)
|
146
|
+
case data
|
147
|
+
when Hash
|
148
|
+
data.map do |key, value|
|
149
|
+
if(key == 'Ref' && names.include?(value))
|
150
|
+
value
|
151
|
+
elsif(key == 'DependsOn')
|
152
|
+
[value].flatten.compact.find_all do |dependson_name|
|
153
|
+
names.include?(dependson_name)
|
154
|
+
end
|
155
|
+
elsif(key == 'Fn::GetAtt' && names.include?(res = [value].flatten.compact.first))
|
156
|
+
res
|
157
|
+
else
|
158
|
+
resource_dependencies(key, names) +
|
159
|
+
resource_dependencies(value, names)
|
160
|
+
end
|
161
|
+
end.flatten.compact.uniq
|
162
|
+
when Array
|
163
|
+
data.map do |item|
|
164
|
+
resource_dependencies(item, names)
|
165
|
+
end.flatten.compact.uniq
|
166
|
+
else
|
167
|
+
[]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'sfn'
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
class Command
|
5
|
+
# Graph command
|
6
|
+
class Graph < Command
|
7
|
+
module Provider
|
8
|
+
module Terraform
|
9
|
+
class TerraformGraphProcessor < SparkleFormation::Translation
|
10
|
+
MAP = {}
|
11
|
+
REF_MAPPING = {}
|
12
|
+
FN_MAPPING = {}
|
13
|
+
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
def initialize(template, args={})
|
17
|
+
super
|
18
|
+
@name = args[:name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def dereference_processor(obj, funcs=[])
|
22
|
+
case obj
|
23
|
+
when Array
|
24
|
+
obj = obj.map{|v| dereference_processor(v, funcs)}
|
25
|
+
when Hash
|
26
|
+
new_hash = {}
|
27
|
+
obj.each do |k, v|
|
28
|
+
new_hash[k] = dereference_processor(v, funcs)
|
29
|
+
end
|
30
|
+
obj = new_hash
|
31
|
+
when String
|
32
|
+
obj = apply_function(obj, funcs)
|
33
|
+
end
|
34
|
+
obj
|
35
|
+
end
|
36
|
+
|
37
|
+
def parameters
|
38
|
+
Hash[
|
39
|
+
@original.fetch('parameters', {}).map do |k, v|
|
40
|
+
[k, v.fetch('default', '')]
|
41
|
+
end
|
42
|
+
].merge(@parameters)
|
43
|
+
end
|
44
|
+
|
45
|
+
def resources
|
46
|
+
@original.fetch('resources', {})
|
47
|
+
end
|
48
|
+
|
49
|
+
def outputs
|
50
|
+
@original.fetch('outputs', {})
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_function(string, funcs=[])
|
54
|
+
# first check for vars and replace with params
|
55
|
+
string.scan(/(\$\{var\.(.+?)\})/).each do |match|
|
56
|
+
if(parameters[match.last])
|
57
|
+
string.sub!(match.first, parameters[match.last])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
string
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def output_discovery(template, outputs, resource_name, parent_template, name='')
|
65
|
+
if(template['resources'])
|
66
|
+
template['resources'].keys.each do |r_name|
|
67
|
+
r_info = template['resources'][r_name]
|
68
|
+
if(r_info['type'] == 'module')
|
69
|
+
output_discovery(r_info['properties']['stack'], outputs, r_name, template, r_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
if(parent_template)
|
74
|
+
ui.debug "Pre-processing stack resource `#{resource_name}`"
|
75
|
+
substack_parameters = Smash[
|
76
|
+
parent_template.fetch('resources', resource_name, 'properties', 'parameters', {}).map do |key, value|
|
77
|
+
result = [key, value]
|
78
|
+
if(value.to_s.start_with?('${module.'))
|
79
|
+
output_key = value.sub('${module.', '').sub('}', '').sub('.', '__')
|
80
|
+
ui.debug "Output key for check: #{output_key}"
|
81
|
+
if(outputs.key?(output_key))
|
82
|
+
new_value = outputs[output_key]
|
83
|
+
result = [key, new_value]
|
84
|
+
ui.debug "Parameter for output swap `#{key}`: #{value} -> #{new_value}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
result
|
88
|
+
end
|
89
|
+
]
|
90
|
+
|
91
|
+
ui.debug "Generated internal parameters for `#{resource_name}`: #{substack_parameters}"
|
92
|
+
|
93
|
+
processor = TerraformGraphProcessor.new({},
|
94
|
+
:parameters => substack_parameters
|
95
|
+
)
|
96
|
+
template['resources'] = processor.dereference_processor(
|
97
|
+
template['resources'], []
|
98
|
+
)
|
99
|
+
template['outputs'] = processor.dereference_processor(
|
100
|
+
template['outputs'], []
|
101
|
+
)
|
102
|
+
derefed_outs = template['outputs'] || {}
|
103
|
+
derefed_outs.each do |o_name, o_data|
|
104
|
+
o_key = [name, o_name].join('__')
|
105
|
+
val = o_data['value']
|
106
|
+
if(val.start_with?('${') && val.scan('.').count == 2)
|
107
|
+
val = val.split('.')
|
108
|
+
val[1] = "#{name}__#{val[1]}"
|
109
|
+
val = val.join('.')
|
110
|
+
end
|
111
|
+
outputs[o_key] = val
|
112
|
+
end
|
113
|
+
end
|
114
|
+
outputs.dup.each do |key, value|
|
115
|
+
if(value.to_s.start_with?('${module.'))
|
116
|
+
output_key = value.to_s.sub('${module.', '').sub('}', '').sub('.', '__')
|
117
|
+
if(outputs.key?(output_key))
|
118
|
+
outputs[key] = outputs[output_key]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def edge_detection(template, graph, name = '', resource_names = [])
|
125
|
+
resources = (template.fetch('resources', {}) || {})
|
126
|
+
node_prefix = name
|
127
|
+
resources.each do |resource_name, resource_data|
|
128
|
+
node_name = [node_prefix, resource_name].join('__')
|
129
|
+
if(resource_data['type'] == 'module')
|
130
|
+
graph.subgraph << generate_graph(
|
131
|
+
resource_data['properties'].delete('stack'),
|
132
|
+
:name => resource_name,
|
133
|
+
:type => resource_data['type'],
|
134
|
+
:resource_names => resource_names
|
135
|
+
)
|
136
|
+
next
|
137
|
+
else
|
138
|
+
graph.node(node_name).attributes << graph.fillcolor(colorize(node_prefix.empty? ? config[:file] : node_prefix).inspect)
|
139
|
+
graph.box3d << graph.node(node_name)
|
140
|
+
end
|
141
|
+
graph.filled << graph.node(node_name)
|
142
|
+
graph.node(node_name).label "#{resource_name}\n<#{resource_data['type']}>\n#{name}"
|
143
|
+
resource_dependencies(resource_data, resource_names + resources.keys).each do |dep_name|
|
144
|
+
if(resources.keys.include?(dep_name))
|
145
|
+
dep_name = [node_prefix, dep_name].join('__')
|
146
|
+
end
|
147
|
+
if(config[:graph_style] == 'creation')
|
148
|
+
@root_graph.edge(dep_name, node_name)
|
149
|
+
else
|
150
|
+
@root_graph.edge(node_name, dep_name)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
resource_names.concat resources.keys.map{|r_name| [node_prefix, r_name].join('__')}
|
155
|
+
end
|
156
|
+
|
157
|
+
def resource_dependencies(data, names)
|
158
|
+
case data
|
159
|
+
when String
|
160
|
+
result = []
|
161
|
+
if(data.start_with?('${') && data.scan('.').count >= 1)
|
162
|
+
data = data.tr('${}', '')
|
163
|
+
check_name = data.split('.')[1]
|
164
|
+
if names.include?(check_name)
|
165
|
+
result.push(check_name)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
result
|
169
|
+
when Hash
|
170
|
+
data.map do |key, value|
|
171
|
+
if(key == 'depends_on')
|
172
|
+
[value].flatten.compact.map do |dependson_name|
|
173
|
+
dep_name = dependson_name.split('.').last
|
174
|
+
dep_name if names.include?(dep_name)
|
175
|
+
end
|
176
|
+
else
|
177
|
+
resource_dependencies(key, names) +
|
178
|
+
resource_dependencies(value, names)
|
179
|
+
end
|
180
|
+
end.flatten.compact.uniq
|
181
|
+
when Array
|
182
|
+
data.map do |item|
|
183
|
+
resource_dependencies(item, names)
|
184
|
+
end.flatten.compact.uniq
|
185
|
+
else
|
186
|
+
[]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/sfn/version.rb
CHANGED
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: 3.0.
|
4
|
+
version: 3.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Roberts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bogo-cli
|
@@ -409,6 +409,9 @@ files:
|
|
409
409
|
- lib/sfn/command/events.rb
|
410
410
|
- lib/sfn/command/export.rb
|
411
411
|
- lib/sfn/command/graph.rb
|
412
|
+
- lib/sfn/command/graph/aws.rb
|
413
|
+
- lib/sfn/command/graph/provider.rb
|
414
|
+
- lib/sfn/command/graph/terraform.rb
|
412
415
|
- lib/sfn/command/import.rb
|
413
416
|
- lib/sfn/command/init.rb
|
414
417
|
- lib/sfn/command/inspect.rb
|
@@ -484,7 +487,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
484
487
|
version: '0'
|
485
488
|
requirements: []
|
486
489
|
rubyforge_project:
|
487
|
-
rubygems_version: 2.
|
490
|
+
rubygems_version: 2.6.11
|
488
491
|
signing_key:
|
489
492
|
specification_version: 4
|
490
493
|
summary: SparkleFormation CLI
|