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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4240a0aae37ea32a3009555e90b6fe696c87b3cd
4
- data.tar.gz: 63d451c4752617971b87f0044f06353e95d14904
3
+ metadata.gz: 26bb5b9c83385f48f0fb3039200337e9a38d9c9f
4
+ data.tar.gz: be31681e9d3442d22e9b6a0f8099e291efff6649
5
5
  SHA512:
6
- metadata.gz: 032af64c9500c6e7b6ef43fa1451512cf400fd196839d1b6b284f8f4d8d8f1e3b1299ae0b650a5ae84afe1ceede8bbebb84803d4d2342c75861a9537f2e633b2
7
- data.tar.gz: bf6187383e49fc4fdc7be8a8c9a511b7dece267a37df6b0c19afe78466423ab1339755554a458affb869ee86d776142f1ac160c1c2f3a50f4cc5bfbc96eb5a3e
6
+ metadata.gz: 27e7d343a95f38e83d3f4c31515f196a9df41c7f767d8e6d51dfdac580eb332958d54e9cf70c4064b334d53b169765bfea8ccff988ca36d45b4d274a2cd6be64
7
+ data.tar.gz: 1c52390455f83cc190afdf9de14e1665416dcac88ea45951c37f98169bde96f6ead943bf740b9ad23a3331c33a699ebaf50e520a38f1d6d6b7088af260e6e821
@@ -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
- answer = ""
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
-
@@ -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
@@ -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[:kind].underscore_case
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[:kind].underscore_case
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[:apiVersion]
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[:metadata][:name]
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[:metadata][:namespace])
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(resource.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(client.instance_variable_get("@headers")))
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 resource.to_h.to_json
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, :symbolize_names => true)
188
+ body: JSON.parse(ret.body)
105
189
  }
106
190
  end
107
191
 
108
192
  def get(resource)
109
- name = resource[:metadata][:name]
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[:kind]} of #{name} for kubernetes. Maybe this is in a new extension api?"
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[:metadata][:namespace])
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, :symbolize_names => true)
207
+ body: JSON.parse(ret.body)
124
208
  }
125
209
  end
126
210
 
127
211
  def put(resource)
128
- name = resource[:metadata][:name]
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[:kind]} of #{name} for kubernetes. Maybe this is in a new extension api?"
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[:metadata][:namespace])
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, :symbolize_names => true)
226
+ body: JSON.parse(ret.body)
143
227
  }
144
228
 
145
229
  end
146
230
 
147
- def apply_resource(resource)
148
- name = resource[:metadata][:name]
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 = Kube.kube_merge_resource_for_put!(current_resource, 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(OpenStruct.new(context).instance_eval { binding })
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 - 3].max
228
- end_line = [lines.length - 1, linenumber + 3].min
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
- num += start_line
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 = ::Kubeclient::Resource.new(document)
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
- if linenumber > 0 # Linenumbers seems to be off with 1 as the array is starting at zero
255
- linenumber = linenumber - 1
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 += start_line
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
@@ -1,3 +1,3 @@
1
1
  class Biosphere
2
- Version = "0.2.18"
2
+ Version = "0.2.19"
3
3
  end
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.18
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-06-07 00:00:00.000000000 Z
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