biosphere 0.2.14 → 0.2.17
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/bin/biosphere +16 -11
- data/lib/biosphere/cli/terraformplanning.rb +239 -3
- data/lib/biosphere/kube.rb +6 -2
- data/lib/biosphere/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 668761124d495501e57b6581a66d3a461e20e1b6
|
4
|
+
data.tar.gz: 77b077d01ee5d1b57cfa2de40fe62bfe8bb282c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4fd8a19b4e2d1a045a077605d15f270170ec54f1e4dd20075704418a9dc54e7f215dc15dd1267d9876ac70f2d3e2832951abd6ebcc8b1cf853f7c4e84de62dd0
|
7
|
+
data.tar.gz: ce5820b8bb1762817df4582f24b3aeea759c9d2d90654f951331d57910d5ddd8fe2df1794db5c77881adb419f5ec123fba551a12d4a6ecad9c1d1b860f76b49b
|
data/bin/biosphere
CHANGED
@@ -273,7 +273,7 @@ elsif ARGV[0] == "statereset" && options.src
|
|
273
273
|
state.save()
|
274
274
|
s3.save("#{options.build_dir}/state.node") unless localmode
|
275
275
|
suite.deployments.each do |name, deployment|
|
276
|
-
s3.delete_object()
|
276
|
+
s3.delete_object("#{name}.tfstate")
|
277
277
|
end
|
278
278
|
end
|
279
279
|
|
@@ -302,21 +302,30 @@ elsif ARGV[0] == "commit" && options.src
|
|
302
302
|
end
|
303
303
|
|
304
304
|
s3.set_lock()
|
305
|
-
|
305
|
+
state_file = "#{options.build_dir}/#{deployment}.tfstate"
|
306
|
+
s3.retrieve(state_file)
|
306
307
|
begin
|
307
|
-
tf_plan_str = %x( terraform plan -state=#{
|
308
|
+
tf_plan_str = %x( terraform plan -state=#{state_file} #{options.build_dir}/#{deployment} )
|
308
309
|
rescue Errno::ENOENT => e
|
309
310
|
STDERR.puts "Could not find terraform. Install with with \"brew install terraform\"".colorize(:red)
|
310
311
|
s3.release_lock()
|
311
312
|
end
|
313
|
+
|
314
|
+
tf_graph_str = %x( terraform graph #{options.build_dir}/#{deployment} )
|
315
|
+
|
312
316
|
tfplanning = Biosphere::CLI::TerraformPlanning.new()
|
313
|
-
plan = tfplanning.generate_plan(suite.deployments[deployment], tf_plan_str)
|
317
|
+
plan = tfplanning.generate_plan(suite.deployments[deployment], tf_plan_str, tf_graph_str)
|
314
318
|
if !plan
|
315
319
|
STDERR.puts "Error parsing tf plan output"
|
316
320
|
s3.release_lock()
|
317
321
|
exit
|
318
322
|
end
|
319
323
|
|
324
|
+
targets = plan.get_resources.collect { |x| "-target=#{x}" }.join(" ")
|
325
|
+
puts "Targets: #{targets}"
|
326
|
+
|
327
|
+
tf_plan_str = %x( terraform plan #{targets} -state=#{state_file} -out #{options.build_dir}/plan #{options.build_dir}/#{deployment} )
|
328
|
+
|
320
329
|
# Print the raw terraform output
|
321
330
|
puts "== TERRAFORM PLAN START ==".colorize(:green)
|
322
331
|
puts "\n" + tf_plan_str
|
@@ -326,8 +335,6 @@ elsif ARGV[0] == "commit" && options.src
|
|
326
335
|
puts "Target group listing:"
|
327
336
|
plan.print
|
328
337
|
|
329
|
-
targets = plan.get_resources.collect { |x| "-target=#{x}" }.join(" ")
|
330
|
-
puts "Targets: #{targets}"
|
331
338
|
answer = ""
|
332
339
|
if !options.force
|
333
340
|
while answer.empty? || (answer != "y" && answer != "n")
|
@@ -342,11 +349,8 @@ elsif ARGV[0] == "commit" && options.src
|
|
342
349
|
puts "\nOk, will not proceed with commit"
|
343
350
|
elsif answer == "y"
|
344
351
|
puts "\nApplying the changes (this may take several minutes)"
|
345
|
-
state_file = "#{options.build_dir}/#{deployment}.tfstate"
|
346
|
-
#tf_apply = %x( terraform apply -state=#{state_file} #{options.build_dir})
|
347
|
-
#puts "\n" + tf_apply
|
348
352
|
begin
|
349
|
-
PTY.spawn("terraform apply
|
353
|
+
PTY.spawn("terraform apply -state-out=#{state_file} #{options.build_dir}/plan") do |stdout, stdin, pid|
|
350
354
|
begin
|
351
355
|
stdout.each { |line| puts line }
|
352
356
|
rescue Errno::EIO
|
@@ -375,8 +379,9 @@ elsif ARGV[0] == "commit" && options.src
|
|
375
379
|
suite.deployments[deployment].load_outputs(state_file)
|
376
380
|
state.node[:biosphere][:last_commit_time] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
377
381
|
state.save()
|
378
|
-
s3.save(
|
382
|
+
s3.save(state_file)
|
379
383
|
s3.save("#{options.build_dir}/state.node")
|
384
|
+
#File.delete("#{options.build_dir}/plan")
|
380
385
|
end
|
381
386
|
|
382
387
|
s3.release_lock()
|
@@ -4,6 +4,233 @@ require 'colorize'
|
|
4
4
|
|
5
5
|
class Biosphere
|
6
6
|
|
7
|
+
class TerraformGraph
|
8
|
+
|
9
|
+
attr_accessor :graph
|
10
|
+
|
11
|
+
class Edge
|
12
|
+
attr_accessor :src, :dst, :length
|
13
|
+
|
14
|
+
def initialize(src, dst, length = 1)
|
15
|
+
@src = src
|
16
|
+
@dst = dst
|
17
|
+
@length = length
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Graph < Array
|
23
|
+
attr_reader :edges, :map
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@map = {}
|
27
|
+
@edges = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect(src_name, dst_name, length = 1)
|
31
|
+
unless @map.include?(src_name)
|
32
|
+
raise ArgumentError, "No such vertex: #{src_name}"
|
33
|
+
end
|
34
|
+
unless @map.include?(dst_name)
|
35
|
+
raise ArgumentError, "No such vertex: #{dst_name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
src = @map[src_name]
|
39
|
+
dst = @map[dst_name]
|
40
|
+
|
41
|
+
@edges.push Edge.new(src, dst, length)
|
42
|
+
end
|
43
|
+
|
44
|
+
def push_name(name)
|
45
|
+
if !@map[name]
|
46
|
+
index = @map.length + 1
|
47
|
+
@map[name] = index
|
48
|
+
self.push(index)
|
49
|
+
return index
|
50
|
+
else
|
51
|
+
return @map[name]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def connect_mutually(vertex1_name, vertex2_name, length = 1)
|
56
|
+
self.connect vertex1_name, vertex2_name, length
|
57
|
+
self.connect vertex2_name, vertex1_name, length
|
58
|
+
end
|
59
|
+
|
60
|
+
def neighbors(vertex_name)
|
61
|
+
vertex = @map[vertex_name]
|
62
|
+
neighbors = []
|
63
|
+
@edges.each do |edge|
|
64
|
+
neighbors.push edge.dst if edge.src == vertex
|
65
|
+
end
|
66
|
+
return neighbors.uniq.collect { |x| @map.key(x) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def neighbors_by_index(vertex)
|
70
|
+
neighbors = []
|
71
|
+
@edges.each do |edge|
|
72
|
+
neighbors.push edge.dst if edge.src == vertex
|
73
|
+
end
|
74
|
+
return neighbors.uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
def length_between(src_name, dst_name)
|
78
|
+
src = @map[src_name]
|
79
|
+
dst = @map[dst_name]
|
80
|
+
|
81
|
+
@edges.each do |edge|
|
82
|
+
return edge.length if edge.src == src and edge.dst == dst
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def length_between_index(src, dst)
|
88
|
+
@edges.each do |edge|
|
89
|
+
return edge.length if edge.src == src and edge.dst == dst
|
90
|
+
end
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def dijkstra(src_name, dst_name = nil)
|
95
|
+
src = @map[src_name]
|
96
|
+
dst = @map[dst_name]
|
97
|
+
|
98
|
+
distances = {}
|
99
|
+
previouses = {}
|
100
|
+
self.each do |vertex|
|
101
|
+
distances[vertex] = nil # Infinity
|
102
|
+
previouses[vertex] = nil
|
103
|
+
end
|
104
|
+
distances[src] = 0
|
105
|
+
vertices = self.clone
|
106
|
+
until vertices.empty?
|
107
|
+
nearest_vertex = vertices.inject do |a, b|
|
108
|
+
next b unless distances[a]
|
109
|
+
next a unless distances[b]
|
110
|
+
next a if distances[a] < distances[b]
|
111
|
+
b
|
112
|
+
end
|
113
|
+
break unless distances[nearest_vertex] # Infinity
|
114
|
+
if dst and nearest_vertex == dst
|
115
|
+
path = path_to_names(get_path(previouses, src, dst))
|
116
|
+
return { path: path, distance: distances[dst] }
|
117
|
+
end
|
118
|
+
neighbors = vertices.neighbors_by_index(nearest_vertex)
|
119
|
+
neighbors.each do |vertex|
|
120
|
+
alt = distances[nearest_vertex] + vertices.length_between_index(nearest_vertex, vertex)
|
121
|
+
if distances[vertex].nil? or alt < distances[vertex]
|
122
|
+
distances[vertex] = alt
|
123
|
+
previouses[vertex] = nearest_vertex
|
124
|
+
# decrease-key v in Q # ???
|
125
|
+
end
|
126
|
+
end
|
127
|
+
vertices.delete nearest_vertex
|
128
|
+
end
|
129
|
+
if dst
|
130
|
+
return nil
|
131
|
+
else
|
132
|
+
paths = {}
|
133
|
+
distances.each { |k, v| paths[k] = path_to_names(get_path(previouses, src, k)) }
|
134
|
+
return { paths: paths, distances: distances }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def path_to_names(path)
|
140
|
+
p = []
|
141
|
+
path.each do |index|
|
142
|
+
p << @map.key(index)
|
143
|
+
end
|
144
|
+
|
145
|
+
return p
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_path(previouses, src, dest)
|
149
|
+
path = get_path_recursively(previouses, src, dest)
|
150
|
+
path.is_a?(Array) ? path.reverse : path
|
151
|
+
end
|
152
|
+
|
153
|
+
# Unroll through previouses array until we get to source
|
154
|
+
def get_path_recursively(previouses, src, dest)
|
155
|
+
return src if src == dest
|
156
|
+
raise ArgumentError, "No path from #{src} to #{dest}" if previouses[dest].nil?
|
157
|
+
[dest, get_path_recursively(previouses, src, previouses[dest])].flatten
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize()
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse_line(line)
|
166
|
+
if (m = line.match(/"\[(.+?)\] (?<name>\S+?)(\((.+?)\))?" \[label/))
|
167
|
+
return {
|
168
|
+
:type => :node,
|
169
|
+
:name => m[:name]
|
170
|
+
}
|
171
|
+
elsif (m = line.match(/"\[(.+?)\] (?<from>\S+?)(\s\((.+?)\)){0,1}" -> "\[(.+?)\] (?<to>\S+?)(\s\((.+?)\)){0,1}"/))
|
172
|
+
return {
|
173
|
+
:type => :edge,
|
174
|
+
:from => m[:from],
|
175
|
+
:to => m[:to]
|
176
|
+
}
|
177
|
+
else
|
178
|
+
return nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def load(data)
|
183
|
+
|
184
|
+
@graph = Graph.new
|
185
|
+
|
186
|
+
lines = data.split("\n")
|
187
|
+
lines.each do |line|
|
188
|
+
|
189
|
+
m = parse_line(line)
|
190
|
+
if m
|
191
|
+
if m[:type] == :node
|
192
|
+
@graph.push_name m[:name]
|
193
|
+
elsif m[:type] == :edge
|
194
|
+
@graph.push_name m[:from]
|
195
|
+
@graph.push_name m[:to]
|
196
|
+
@graph.connect(m[:from], m[:to], 1)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_blacklist_by_dependency(item)
|
203
|
+
path = @graph.dijkstra("root", item)
|
204
|
+
return path[:path]
|
205
|
+
end
|
206
|
+
|
207
|
+
def filter_tf_plan(plan)
|
208
|
+
|
209
|
+
blacklist = []
|
210
|
+
plan.items.each do |item|
|
211
|
+
if item[:action] == :not_picked
|
212
|
+
begin
|
213
|
+
blacklist << get_blacklist_by_dependency(item[:resource_name])
|
214
|
+
rescue ArgumentError => e
|
215
|
+
puts "Error: #{e}. item: #{item}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
blacklist.each do |blacklist_items|
|
221
|
+
blacklist_items.each do |blacklist_path_item|
|
222
|
+
plan.items.each do |item|
|
223
|
+
if item[:resource_name] == blacklist_path_item && item[:action] != :not_picked
|
224
|
+
item[:action] = :not_picked
|
225
|
+
item[:reason] = "not selected as dependent on #{blacklist_items[blacklist_items.index(item[:resource_name])+1..-1].join(" -> ")}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
return plan
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
7
234
|
class CLI
|
8
235
|
|
9
236
|
class TerraformPlanning
|
@@ -12,6 +239,7 @@ class Biosphere
|
|
12
239
|
attr_accessor :items
|
13
240
|
def initialize()
|
14
241
|
@items = []
|
242
|
+
@graph = nil
|
15
243
|
end
|
16
244
|
|
17
245
|
def length
|
@@ -63,11 +291,20 @@ class Biosphere
|
|
63
291
|
end
|
64
292
|
end
|
65
293
|
|
66
|
-
def generate_plan(deployment, tf_output_str)
|
294
|
+
def generate_plan(deployment, tf_output_str, tf_graph_str = nil)
|
295
|
+
|
296
|
+
if tf_graph_str
|
297
|
+
@graph = Biosphere::TerraformGraph.new
|
298
|
+
@graph.load(tf_graph_str)
|
299
|
+
end
|
300
|
+
|
67
301
|
data = parse_terraform_plan_output(tf_output_str)
|
68
302
|
|
69
303
|
plan = build_terraform_targetting_plan(deployment, data)
|
70
304
|
|
305
|
+
if @graph
|
306
|
+
plan = @graph.filter_tf_plan(plan)
|
307
|
+
end
|
71
308
|
return plan
|
72
309
|
end
|
73
310
|
|
@@ -192,7 +429,6 @@ class Biosphere
|
|
192
429
|
# Relaunches are more complex: we need to bucket resources based on group, so that we can later pick just one change from each group
|
193
430
|
changes[:relaunches].each do |change|
|
194
431
|
group = resource_to_target_group_map[change]
|
195
|
-
puts "group #{group} for change #{change}"
|
196
432
|
if group
|
197
433
|
group_changes_map[group] = (group_changes_map[group] ||= []) << change
|
198
434
|
elsif resources_not_in_any_target_group[change]
|
@@ -214,7 +450,7 @@ class Biosphere
|
|
214
450
|
# Handle safe groups: just one changing resource in the group
|
215
451
|
safe_groups = group_changes_map.select { |name, resources| resources.length <= 1 }
|
216
452
|
safe_groups.each do |group_name, resources|
|
217
|
-
resources.each do |resource_name|
|
453
|
+
resources.each do |resource_name|
|
218
454
|
plan.items << {
|
219
455
|
:resource_name => resource_name,
|
220
456
|
:target_group => group_name,
|
data/lib/biosphere/kube.rb
CHANGED
@@ -84,14 +84,18 @@ class Biosphere
|
|
84
84
|
puts "Error doing api call: #{e}".colorize(:red)
|
85
85
|
puts "This might be because you did not specify namespace in your resource: #{resource[:metadata]}".colorize(:yellow)
|
86
86
|
else
|
87
|
-
puts "Error calling API: #{e}"
|
87
|
+
puts "Error calling API (on RestClient::MethodNotAllowed): #{e}"
|
88
88
|
end
|
89
89
|
puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
|
90
|
+
puts "Dumpin resource request:"
|
91
|
+
pp resource.to_h.to_json
|
90
92
|
raise e
|
91
93
|
|
92
94
|
rescue RestClient::Exception => e
|
93
|
-
puts "Error calling API: #{e}"
|
95
|
+
puts "Error calling API (on RestClient::Exception rescue): #{e}"
|
94
96
|
puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
|
97
|
+
puts "Dumpin resource request:"
|
98
|
+
pp resource.to_h.to_json
|
95
99
|
raise e
|
96
100
|
end
|
97
101
|
return {
|
data/lib/biosphere/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: biosphere
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juho Mäkinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-06-
|
11
|
+
date: 2017-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|