biosphere 0.2.18 → 0.2.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/biosphere +5 -18
- data/lib/biosphere.rb +2 -1
- data/lib/biosphere/cli/renamedeployment.rb +82 -0
- data/lib/biosphere/cli/terraformutils.rb +34 -0
- data/lib/biosphere/kube.rb +200 -61
- data/lib/biosphere/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26bb5b9c83385f48f0fb3039200337e9a38d9c9f
|
4
|
+
data.tar.gz: be31681e9d3442d22e9b6a0f8099e291efff6649
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27e7d343a95f38e83d3f4c31515f196a9df41c7f767d8e6d51dfdac580eb332958d54e9cf70c4064b334d53b169765bfea8ccff988ca36d45b4d274a2cd6be64
|
7
|
+
data.tar.gz: 1c52390455f83cc190afdf9de14e1665416dcac88ea45951c37f98169bde96f6ead943bf740b9ad23a3331c33a699ebaf50e520a38f1d6d6b7088af260e6e821
|
data/bin/biosphere
CHANGED
@@ -89,7 +89,7 @@ if !STDOUT.isatty
|
|
89
89
|
end
|
90
90
|
|
91
91
|
update_info = Biosphere::CLI::UpdateManager::check_for_update()
|
92
|
-
if !update_info[:up_to_date]
|
92
|
+
if update_info && !update_info[:up_to_date]
|
93
93
|
STDERR.puts "Notice. There is a new #{update_info[:latest]} biosphere version available. Your current version is #{update_info[:current]}\nUse \"gem install biosphere\" to update".colorize(:yellow)
|
94
94
|
end
|
95
95
|
|
@@ -173,22 +173,7 @@ if options.src
|
|
173
173
|
end
|
174
174
|
|
175
175
|
unless state.node[:deployments][""].nil?
|
176
|
-
|
177
|
-
if !options.force
|
178
|
-
while answer.empty? || (answer != "y" && answer != "n")
|
179
|
-
puts "\e[31mDeployment with an empty name, do you want to clean these out now? [y/n]\e[0m"
|
180
|
-
answer = STDIN.gets.chomp
|
181
|
-
end
|
182
|
-
|
183
|
-
if answer == "n"
|
184
|
-
puts "Remember to clean these out later, this state won't work with Kubernetes"
|
185
|
-
elsif answer == "y"
|
186
|
-
state.node[:deployments].delete("")
|
187
|
-
puts "\e[32mRemoved deployment with empty name\e[0m"
|
188
|
-
end
|
189
|
-
else
|
190
|
-
puts "Forcing since --force was set"
|
191
|
-
end
|
176
|
+
puts "State contains deployments with empty names. Remember to clean these out with rename-deployment, this state won't work with Kubernetes".colorize(:red)
|
192
177
|
end
|
193
178
|
end
|
194
179
|
|
@@ -426,6 +411,9 @@ elsif ARGV[0] == "destroy" && options.src
|
|
426
411
|
|
427
412
|
s3.release_lock()
|
428
413
|
|
414
|
+
elsif ARGV[0] == "rename-deployment"
|
415
|
+
Biosphere::CLI::RenameDeployment::renamedeployment(suite, s3, options.build_dir, ARGV[1], ARGV[2], force: options.force)
|
416
|
+
|
429
417
|
elsif ARGV[0] == "lock"
|
430
418
|
if localmode
|
431
419
|
STDERR.puts "lock not supported in local mode (set in Settings :biosphere[:local] = true"
|
@@ -446,4 +434,3 @@ else
|
|
446
434
|
STDERR.puts "\nERROR: Unknown command #{ARGV[0]}. Maybe you wanted to do: \"biosphere action #{ARGV[0]}\"?"
|
447
435
|
exit -1
|
448
436
|
end
|
449
|
-
|
data/lib/biosphere.rb
CHANGED
@@ -13,6 +13,7 @@ require "biosphere/deployment"
|
|
13
13
|
require "biosphere/terraformproxy"
|
14
14
|
require "biosphere/suite"
|
15
15
|
require "biosphere/cli/terraformplanning"
|
16
|
+
require "biosphere/cli/terraformutils"
|
16
17
|
require "biosphere/cli/updatemanager"
|
18
|
+
require "biosphere/cli/renamedeployment"
|
17
19
|
require "biosphere/s3"
|
18
|
-
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'biosphere'
|
2
|
+
require 'pp'
|
3
|
+
require "awesome_print"
|
4
|
+
require 'colorize'
|
5
|
+
require 'biosphere/s3.rb'
|
6
|
+
require 'pty'
|
7
|
+
|
8
|
+
class Biosphere
|
9
|
+
class CLI
|
10
|
+
class RenameDeployment
|
11
|
+
def self.renamedeployment(suite, s3, build_dir, deployment, new_name, terraform: Biosphere::CLI::TerraformUtils.new(), localmode: false, force: false)
|
12
|
+
|
13
|
+
if !suite.kind_of?(::Biosphere::Suite)
|
14
|
+
raise ArgumentError, "RenameDeployment needs a proper suite as the first argument"
|
15
|
+
end
|
16
|
+
|
17
|
+
if !s3.kind_of?(S3)
|
18
|
+
raise ArgumentError, "RenameDeployment requires an s3 client as the second argument"
|
19
|
+
end
|
20
|
+
|
21
|
+
localmode = suite.biosphere_settings[:local] || localmode
|
22
|
+
|
23
|
+
if !deployment
|
24
|
+
puts "Please specify deployment name as the second parameter."
|
25
|
+
puts "Available deployments:"
|
26
|
+
suite.deployments.each do |name, deployment|
|
27
|
+
puts "\t#{name}"
|
28
|
+
end
|
29
|
+
exit(-1)
|
30
|
+
end
|
31
|
+
|
32
|
+
if !new_name
|
33
|
+
puts "Please specify the new name as the third parameter."
|
34
|
+
exit(-1)
|
35
|
+
end
|
36
|
+
|
37
|
+
if !suite.deployments[deployment]
|
38
|
+
puts "Deployment #{deployment} doesn't exist in the current suite".colorize(:red)
|
39
|
+
exit(-1)
|
40
|
+
elsif suite.deployments[new_name]
|
41
|
+
puts "The current suite already contains a deployment called #{new_name}".colorize(:red)
|
42
|
+
exit(-1)
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "Renaming #{deployment} to #{new_name}"
|
46
|
+
|
47
|
+
suite.deployments[deployment].all_resources.each do |r|
|
48
|
+
r[:new_name] = r[:name].sub(deployment,new_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
state_file = "#{build_dir}/#{deployment}.tfstate"
|
52
|
+
suite.deployments[deployment].all_resources.each do |r|
|
53
|
+
terraform.move(state_file, r[:type], r[:name], r[:new_name])
|
54
|
+
r[:name] = r[:new_name]
|
55
|
+
r.delete(:new_name)
|
56
|
+
end
|
57
|
+
suite.state.node[:deployments][new_name] = suite.state.node[:deployments].delete(deployment)
|
58
|
+
|
59
|
+
puts "State renaming done!".colorize(:green)
|
60
|
+
puts "Remember to change the deployment name in the deployment specifications too!".colorize(:yellow)
|
61
|
+
|
62
|
+
count = 0
|
63
|
+
suite.write_json_to(build_dir) do |file_name, destination, str, suite_deployment|
|
64
|
+
puts "Wrote #{str.length} bytes from #{file_name} to #{destination} (#{suite_deployment.export["resource"].length} resources)"
|
65
|
+
count = count + 1
|
66
|
+
end
|
67
|
+
|
68
|
+
puts "Wrote #{count} files under #{build_dir}"
|
69
|
+
|
70
|
+
unless suite.state.node[:biosphere].nil?
|
71
|
+
suite.state.node[:biosphere][:last_build_time] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
72
|
+
end
|
73
|
+
|
74
|
+
FileUtils.mv("#{build_dir}/#{deployment}.tfstate", "#{build_dir}/#{new_name}.tfstate")
|
75
|
+
suite.state.save()
|
76
|
+
s3.save("#{build_dir}/state.node") unless localmode
|
77
|
+
s3.delete_object("#{build_dir}/#{deployment}.tfstate") unless localmode
|
78
|
+
s3.save("#{build_dir}/#{new_name}.tfstate") unless localmode
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'biosphere'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
class Biosphere
|
5
|
+
class CLI
|
6
|
+
class TerraformUtils
|
7
|
+
def initialize()
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def move(tfstate_file, resource_type, old_name, new_name)
|
12
|
+
if tfstate_file.nil? || resource_type.nil? || old_name.nil? || new_name.nil?
|
13
|
+
puts "Can't run terraform mv without a tfstate_file, resource_type, old_resource_name and a new_resource_name"
|
14
|
+
puts "tfstate_file: #{tfstate_file}"
|
15
|
+
puts "resource_type: #{resource_type}"
|
16
|
+
puts "old_name: #{old_name}"
|
17
|
+
puts "new_name: #{new_name}"
|
18
|
+
exit(-1)
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
PTY.spawn("terraform state mv -state=#{tfstate_file} #{resource_type}.#{old_name} #{resource_type}.#{new_name}") do |stdout, stdin, pid|
|
23
|
+
begin
|
24
|
+
stdout.each { |line| puts line }
|
25
|
+
rescue Errno::EIO
|
26
|
+
end
|
27
|
+
end
|
28
|
+
rescue PTY::ChildExited
|
29
|
+
puts "The child process exited!"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/biosphere/kube.rb
CHANGED
@@ -3,6 +3,8 @@ require 'kubeclient'
|
|
3
3
|
require 'erb'
|
4
4
|
require 'hashdiff'
|
5
5
|
require 'ostruct'
|
6
|
+
require 'jsonpath'
|
7
|
+
require 'deep_dup'
|
6
8
|
|
7
9
|
class String
|
8
10
|
# Converts "CamelCase"" into "camel_case"
|
@@ -26,6 +28,83 @@ end
|
|
26
28
|
class Biosphere
|
27
29
|
module Kube
|
28
30
|
|
31
|
+
#
|
32
|
+
# Encapsulates a single resource inside kube apiserver (eg. a Deployment or a DaemonSet).
|
33
|
+
# A KubeResource comes from a manifest which has one or more resources (yaml allows separating
|
34
|
+
# multiple documents with --- inside a single file).
|
35
|
+
#
|
36
|
+
#
|
37
|
+
class KubeResource
|
38
|
+
attr_accessor :resource, :document, :source_file, :preserve_current_values
|
39
|
+
def initialize(document, source_file)
|
40
|
+
@document = document
|
41
|
+
@source_file = source_file
|
42
|
+
@preserve_current_values = []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Merges the resource with the current resource version in the api server, which
|
46
|
+
# has important properties such as:
|
47
|
+
# - metadata.selfLink
|
48
|
+
# - metadata.uid
|
49
|
+
# - metadata.resourceVersion
|
50
|
+
#
|
51
|
+
# A document can't be updated (PUT verb) unless these are carried over
|
52
|
+
#
|
53
|
+
def merge_for_put(current)
|
54
|
+
new_version = DeepDup.deep_dup(@document)
|
55
|
+
|
56
|
+
if current["metadata"]
|
57
|
+
new_version["metadata"]["selfLink"] = current["metadata"]["selfLink"] if current["metadata"]["selfLink"]
|
58
|
+
new_version["metadata"]["uid"] = current["metadata"]["uid"] if current["metadata"]["uid"]
|
59
|
+
new_version["metadata"]["resourceVersion"] = current["metadata"]["resourceVersion"] if current["metadata"]["resourceVersion"]
|
60
|
+
end
|
61
|
+
|
62
|
+
if current["spec"]
|
63
|
+
new_version["spec"] = {} if !new_version["spec"]
|
64
|
+
|
65
|
+
# handle spec.clusterIP
|
66
|
+
if new_version["spec"]["clusterIP"] && new_version["spec"]["clusterIP"] != current["spec"]["clusterIP"]
|
67
|
+
raise ArgumentError, "#{@source_file}: Tried to modify spec.clusterIP from #{current["spec"]["clusterIP"]} to #{new_version["spec"]["clusterIP"]} but the field is immutable"
|
68
|
+
end
|
69
|
+
new_version["spec"]["clusterIP"] = current["spec"]["clusterIP"] if current["spec"]["clusterIP"]
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
@preserve_current_values.each do |jsonpath_query|
|
74
|
+
jp = JsonPath.new(jsonpath_query)
|
75
|
+
current_value = jp.on(current)
|
76
|
+
if current_value.length > 1
|
77
|
+
raise ArgumentError, "#{@source_file}: A JSONPath query \"#{jsonpath_query}\" matched more than one element: #{current_value}. This is not allowed because it should be used to preserve the current value in the Kubernets API server for the property."
|
78
|
+
end
|
79
|
+
|
80
|
+
new_value = jp.on(new_version)
|
81
|
+
if new_value.length > 1
|
82
|
+
raise ArgumentError, "#{@source_file}: A JSONPath query \"#{jsonpath_query}\" matched more than one element: #{new_value}. This is not allowed because it should be used to preserve the current value in the Kubernets API server for the property."
|
83
|
+
end
|
84
|
+
|
85
|
+
if current_value.first != new_value.first
|
86
|
+
new_version = JsonPath.for(new_version).gsub(jsonpath_query) { |proposed_value| current_value.first }.to_hash
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
return new_version
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class KubeResourceERBBinding < OpenStruct
|
95
|
+
attr_accessor :preserve_current_values
|
96
|
+
|
97
|
+
def initialize(object)
|
98
|
+
@preserve_current_values = []
|
99
|
+
super(object)
|
100
|
+
end
|
101
|
+
|
102
|
+
def preserve_current_value(jsonpathquery)
|
103
|
+
@preserve_current_values << jsonpathquery
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
29
108
|
class Client
|
30
109
|
def initialize(hostname, ssl_options)
|
31
110
|
@clients = []
|
@@ -48,7 +127,7 @@ class Biosphere
|
|
48
127
|
|
49
128
|
def get_resource_name(resource)
|
50
129
|
resource_name = nil
|
51
|
-
kind = resource[
|
130
|
+
kind = resource["kind"].underscore_case
|
52
131
|
@clients.each do |c|
|
53
132
|
if c.instance_variable_get("@entities")[kind]
|
54
133
|
return c.instance_variable_get("@entities")[kind].resource_name
|
@@ -58,9 +137,9 @@ class Biosphere
|
|
58
137
|
end
|
59
138
|
|
60
139
|
def get_client(resource)
|
61
|
-
kind = resource[
|
140
|
+
kind = resource["kind"].underscore_case
|
62
141
|
@clients.each do |c|
|
63
|
-
if c.instance_variable_get("@api_group") + c.instance_variable_get("@api_version") == resource[
|
142
|
+
if c.instance_variable_get("@api_group") + c.instance_variable_get("@api_version") == resource["apiVersion"]
|
64
143
|
return c
|
65
144
|
end
|
66
145
|
end
|
@@ -68,7 +147,7 @@ class Biosphere
|
|
68
147
|
end
|
69
148
|
|
70
149
|
def post(resource)
|
71
|
-
name = resource[
|
150
|
+
name = resource["metadata"]["name"]
|
72
151
|
client = get_client(resource)
|
73
152
|
resource_name = get_resource_name(resource)
|
74
153
|
|
@@ -76,9 +155,10 @@ class Biosphere
|
|
76
155
|
raise ArgumentError, "Unknown resource #{resource[:kind]} of #{name} for kubernetes. Maybe this is in a new extension api?"
|
77
156
|
end
|
78
157
|
|
79
|
-
ns_prefix = client.build_namespace_prefix(resource[
|
158
|
+
ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
|
159
|
+
body = JSON.pretty_generate(resource.to_h)
|
80
160
|
begin
|
81
|
-
ret = client.rest_client[ns_prefix + resource_name].post(
|
161
|
+
ret = client.rest_client[ns_prefix + resource_name].post(body, { 'Content-Type' => 'application/json' }.merge(client.instance_variable_get("@headers")))
|
82
162
|
rescue RestClient::MethodNotAllowed => e
|
83
163
|
if !resource[:metadata][:namespace]
|
84
164
|
puts "Error doing api call: #{e}".colorize(:red)
|
@@ -88,7 +168,11 @@ class Biosphere
|
|
88
168
|
end
|
89
169
|
puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
|
90
170
|
puts "Dumpin resource request:"
|
91
|
-
pp
|
171
|
+
pp body
|
172
|
+
raise e
|
173
|
+
|
174
|
+
rescue RestClient::BadRequest => e
|
175
|
+
handle_bad_request(client, e, body, ns_prefix, resource_name)
|
92
176
|
raise e
|
93
177
|
|
94
178
|
rescue RestClient::Exception => e
|
@@ -101,51 +185,59 @@ class Biosphere
|
|
101
185
|
return {
|
102
186
|
action: :post,
|
103
187
|
resource: ns_prefix + resource_name + "/#{name}",
|
104
|
-
body: JSON.parse(ret.body
|
188
|
+
body: JSON.parse(ret.body)
|
105
189
|
}
|
106
190
|
end
|
107
191
|
|
108
192
|
def get(resource)
|
109
|
-
name = resource[
|
193
|
+
name = resource["metadata"]["name"]
|
110
194
|
client = get_client(resource)
|
111
195
|
resource_name = get_resource_name(resource)
|
112
196
|
|
113
197
|
if !client
|
114
|
-
raise ArgumentError, "Unknown resource #{resource[
|
198
|
+
raise ArgumentError, "Unknown resource #{resource["kind"]} of #{name} for kubernetes. Maybe this is in a new extension api?"
|
115
199
|
end
|
116
200
|
|
117
|
-
ns_prefix = client.build_namespace_prefix(resource[
|
201
|
+
ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
|
118
202
|
key = ns_prefix + resource_name + "/#{name}"
|
119
203
|
ret = client.rest_client[key].get(client.instance_variable_get("@headers"))
|
120
204
|
return {
|
121
205
|
action: :get,
|
122
206
|
resource: key,
|
123
|
-
body: JSON.parse(ret.body
|
207
|
+
body: JSON.parse(ret.body)
|
124
208
|
}
|
125
209
|
end
|
126
210
|
|
127
211
|
def put(resource)
|
128
|
-
name = resource[
|
212
|
+
name = resource["metadata"]["name"]
|
129
213
|
client = get_client(resource)
|
130
214
|
resource_name = get_resource_name(resource)
|
131
215
|
|
132
216
|
if !client
|
133
|
-
raise ArgumentError, "Unknown resource #{resource[
|
217
|
+
raise ArgumentError, "Unknown resource #{resource["kind"]} of #{name} for kubernetes. Maybe this is in a new extension api?"
|
134
218
|
end
|
135
219
|
|
136
|
-
ns_prefix = client.build_namespace_prefix(resource[
|
220
|
+
ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
|
137
221
|
key = ns_prefix + resource_name + "/#{name}"
|
138
222
|
ret = client.rest_client[key].put(resource.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(client.instance_variable_get("@headers")))
|
139
223
|
return {
|
140
224
|
action: :put,
|
141
225
|
resource: key,
|
142
|
-
body: JSON.parse(ret.body
|
226
|
+
body: JSON.parse(ret.body)
|
143
227
|
}
|
144
228
|
|
145
229
|
end
|
146
230
|
|
147
|
-
|
148
|
-
|
231
|
+
# Applies the KubeResource into the api server
|
232
|
+
#
|
233
|
+
# The update process has the following sequence:
|
234
|
+
#
|
235
|
+
# 1) Try to fetch the resource to check if the resource is already there
|
236
|
+
# 2.1) If a new resource: issue a POST
|
237
|
+
# 2.2) If resource exists: merge existing resource with the KubeResource and issue a PUT (update)
|
238
|
+
def apply_resource(kuberesource)
|
239
|
+
resource = kuberesource.document
|
240
|
+
name = resource["metadata"]["name"]
|
149
241
|
responses = []
|
150
242
|
not_found = false
|
151
243
|
begin
|
@@ -175,21 +267,65 @@ class Biosphere
|
|
175
267
|
# Get the current full resource from apiserver
|
176
268
|
current_resource = response[:body]
|
177
269
|
|
178
|
-
update_resource =
|
270
|
+
update_resource = kuberesource.merge_for_put(current_resource)
|
179
271
|
|
180
272
|
begin
|
181
273
|
responses << put(update_resource)
|
274
|
+
rescue RestClient::BadRequest => e
|
275
|
+
handle_bad_request(client, e, body, ns_prefix, resource_name)
|
276
|
+
raise e
|
182
277
|
rescue RestClient::Exception => e
|
183
278
|
puts "Error updating resource: #{e} #{e.class}"
|
184
279
|
pp JSON.parse(e.response)
|
185
|
-
rescue RestClient::Exception => e
|
186
|
-
puts "Misc exception: #{e}, #{e.class}, #{e.response}"
|
187
280
|
end
|
188
281
|
|
189
282
|
return responses
|
190
283
|
end
|
191
284
|
end
|
192
285
|
|
286
|
+
def print_error_location(lines, linenumber)
|
287
|
+
start_line = [0, linenumber - 3].max
|
288
|
+
end_line = [lines.length - 1, linenumber + 3].min
|
289
|
+
lines[start_line..end_line].each_with_index do |line, num|
|
290
|
+
num += start_line
|
291
|
+
if num == linenumber
|
292
|
+
STDERR.printf("%04d> %s\n".red, num, line)
|
293
|
+
else
|
294
|
+
STDERR.printf("%04d| %s\n", num, line)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def handle_bad_request(client, e, body, ns_prefix, resource_name)
|
300
|
+
puts "Error calling API (on RestClient::BadRequest rescue): #{e}"
|
301
|
+
puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
|
302
|
+
|
303
|
+
begin
|
304
|
+
msg = JSON.parse(e.http_body)
|
305
|
+
if msg["message"]
|
306
|
+
m = msg["message"].match(/\[pos ([0-9]+?)\]:\s?(.+)/)
|
307
|
+
if m
|
308
|
+
error_pos = m[1].to_i
|
309
|
+
if error_pos < body.length
|
310
|
+
# Find the line number where the error is
|
311
|
+
line_number = 0
|
312
|
+
for pos in 0..body.length - 1
|
313
|
+
if body[pos] == "\n"
|
314
|
+
line_number += 1
|
315
|
+
end
|
316
|
+
if pos >= m[1].to_i
|
317
|
+
break
|
318
|
+
end
|
319
|
+
end
|
320
|
+
print_error_location(body.split("\n"), line_number)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
rescue
|
325
|
+
puts "Error message from body #{e.http_body}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
193
329
|
end # end of class Client
|
194
330
|
|
195
331
|
def kube_test(str)
|
@@ -211,23 +347,45 @@ class Biosphere
|
|
211
347
|
def self.load_resources(file, context={})
|
212
348
|
resources = []
|
213
349
|
puts "Loading file #{File.absolute_path(file)}"
|
350
|
+
|
351
|
+
# Let's wrap the context into an OpenStruct based class so that context["foo"]
|
352
|
+
# can be accessed as contextstruct.foo
|
353
|
+
#
|
354
|
+
# This is required for two reasons:
|
355
|
+
# 1) The ERB template evaluation requires a binding object so that
|
356
|
+
# we can pass variables to the template (from the context object)
|
357
|
+
# 2) We want to push some metadata from the ERB template to back here
|
358
|
+
# such as the preserve_current_value
|
359
|
+
contextstruct = KubeResourceERBBinding.new(context)
|
360
|
+
|
361
|
+
# Read the file in. A single .yaml can contain multiple documents
|
214
362
|
data = IO.read(file)
|
363
|
+
|
364
|
+
# First phase: Evaluate ERB templating. We'll use the context to pass
|
365
|
+
# all the template variables into the erb.
|
366
|
+
# Handle all ERB error reporting with the rescue clauses,
|
367
|
+
# so that user gets as good error message as possible.
|
215
368
|
begin
|
216
|
-
str = ERB.new(data).result(
|
369
|
+
str = ERB.new(data).result(contextstruct.instance_eval { binding })
|
217
370
|
rescue NoMethodError => e
|
218
371
|
puts "Error evaluating erb templating for #{file}. Error: #{e}"
|
219
372
|
m = /\(erb\):([0-9]+):/.match(e.backtrace.first)
|
220
373
|
if m
|
221
374
|
puts "Error at line #{m[1]}. This is before ERB templating. Remember to run biosphere build if you changed settings."
|
222
375
|
linenumber = m[1].to_i
|
223
|
-
if linenumber > 0 # Linenumbers seems to be off with 1 as the array is starting at zero
|
224
|
-
linenumber = linenumber - 1
|
225
|
-
end
|
226
376
|
lines = data.split("\n")
|
227
|
-
start_line = [0, linenumber -
|
228
|
-
end_line = [lines.length - 1, linenumber +
|
377
|
+
start_line = [0, linenumber - 4].max
|
378
|
+
end_line = [lines.length - 1, linenumber + 2].min
|
379
|
+
|
380
|
+
# Lines is the full .yaml file, one line in each index
|
381
|
+
# the [start_line..end_line] slices the subset of the lines
|
382
|
+
# which we want to display
|
229
383
|
lines[start_line..end_line].each_with_index do |line, num|
|
230
|
-
|
384
|
+
|
385
|
+
# num is the current line number from the subset of the lines
|
386
|
+
# We need to add start_line + 1 to it so that it shows
|
387
|
+
# the current line number when we print it out
|
388
|
+
num += start_line + 1
|
231
389
|
if num == linenumber
|
232
390
|
STDERR.printf("%04d> %s\n".red, num, line)
|
233
391
|
else
|
@@ -238,10 +396,17 @@ class Biosphere
|
|
238
396
|
raise e
|
239
397
|
end
|
240
398
|
|
399
|
+
# Second phase: Parse YAML into an array of KubeResource.
|
400
|
+
# Again handle error reporting on the rescue clause.
|
241
401
|
begin
|
242
402
|
Psych.load_stream(str) do |document|
|
243
403
|
kind = document["kind"]
|
244
|
-
resource =
|
404
|
+
resource = KubeResource.new(document, file)
|
405
|
+
|
406
|
+
# The preserve_current_values is a metadata field which is used later
|
407
|
+
# when merging the updated resource with the current object from apiserver.
|
408
|
+
resource.preserve_current_values = contextstruct.preserve_current_values
|
409
|
+
|
245
410
|
resources << resource
|
246
411
|
end
|
247
412
|
rescue Psych::SyntaxError => e
|
@@ -251,14 +416,13 @@ class Biosphere
|
|
251
416
|
STDERR.puts "Notice that yaml is very picky about indentation when you have arrays and maps. Check those first."
|
252
417
|
lines = str.split("\n")
|
253
418
|
linenumber = e.line
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
|
258
|
-
start_line = [0, linenumber - 3].max
|
259
|
-
end_line = [lines.length - 1, linenumber + 3].min
|
419
|
+
start_line = [0, linenumber - 4].max
|
420
|
+
end_line = [lines.length - 1, linenumber + 2].min
|
260
421
|
lines[start_line..end_line].each_with_index do |line, num|
|
261
|
-
num
|
422
|
+
# num is the current line number from the subset of the lines
|
423
|
+
# We need to add start_line + 1 to it so that it shows
|
424
|
+
# the current line number when we print it out
|
425
|
+
num += start_line + 1
|
262
426
|
if num == linenumber
|
263
427
|
STDERR.printf("%04d> %s\n".red, num, line)
|
264
428
|
else
|
@@ -282,30 +446,5 @@ class Biosphere
|
|
282
446
|
end
|
283
447
|
end
|
284
448
|
|
285
|
-
#
|
286
|
-
# Applies seleted properties from the current resource (as fetched from apiserver) to the new_version (as read from manifest file)
|
287
|
-
#
|
288
|
-
#
|
289
|
-
def self.kube_merge_resource_for_put!(current, new_version)
|
290
|
-
if current[:metadata]
|
291
|
-
new_version[:metadata][:selfLink] = current[:metadata][:selfLink] if current[:metadata][:selfLink]
|
292
|
-
new_version[:metadata][:uid] = current[:metadata][:uid] if current[:metadata][:uid]
|
293
|
-
new_version[:metadata][:resourceVersion] = current[:metadata][:resourceVersion] if current[:metadata][:resourceVersion]
|
294
|
-
end
|
295
|
-
|
296
|
-
if current[:spec]
|
297
|
-
new_version[:spec] = {} if !new_version[:spec]
|
298
|
-
|
299
|
-
# handle spec.clusterIP
|
300
|
-
if new_version[:spec][:clusterIP] && new_version[:spec][:clusterIP] != current[:spec][:clusterIP]
|
301
|
-
raise ArgumentError, "Tried to modify spec.clusterIP from #{current[:spec][:clusterIP]} to #{new_version[:spec][:clusterIP]} but the field is immutable"
|
302
|
-
end
|
303
|
-
new_version[:spec][:clusterIP] = current[:spec][:clusterIP] if current[:spec][:clusterIP]
|
304
|
-
|
305
|
-
end
|
306
|
-
return new_version
|
307
|
-
end
|
308
|
-
|
309
|
-
|
310
449
|
end
|
311
450
|
end
|
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.19
|
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-
|
11
|
+
date: 2017-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - '='
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: 2.0.2
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: jsonpath
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.8.5
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - '='
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.8.5
|
167
181
|
description: "Terraform's HCL lacks quite many programming features like iterators,
|
168
182
|
true variables, advanced string manipulation, functions etc.\n\n This Ruby tool
|
169
183
|
provides an easy-to-use DSL to define Terraform compatible .json files which can
|
@@ -177,7 +191,9 @@ files:
|
|
177
191
|
- bin/biosphere
|
178
192
|
- examples/example.rb
|
179
193
|
- lib/biosphere.rb
|
194
|
+
- lib/biosphere/cli/renamedeployment.rb
|
180
195
|
- lib/biosphere/cli/terraformplanning.rb
|
196
|
+
- lib/biosphere/cli/terraformutils.rb
|
181
197
|
- lib/biosphere/cli/updatemanager.rb
|
182
198
|
- lib/biosphere/deployment.rb
|
183
199
|
- lib/biosphere/exceptions.rb
|