biosphere 0.2.18 → 0.2.19
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 +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
|