bosh_cli 0.19.1 → 0.19.2

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.
@@ -22,9 +22,10 @@ module Bosh::Cli::Command
22
22
  args.shift
23
23
  end
24
24
  rescue ArgumentError, TypeError
25
+ # No index given
25
26
  end
26
27
 
27
- ["public_key", "gateway_host", "gateway_user"].each do |option|
28
+ %w(public_key gateway_host gateway_user).each do |option|
28
29
  pos = args.index("--#{option}")
29
30
  if pos
30
31
  options[option] = args[pos + 1]
@@ -98,13 +99,13 @@ module Bosh::Cli::Command
98
99
  encrypt_password(password))
99
100
 
100
101
  unless results && results.kind_of?(Array) && !results.empty?
101
- err("Error setting up ssh, #{results.inspect}, " \
102
+ err("Error setting up ssh, #{results.inspect}, " +
102
103
  "check task logs for more details")
103
104
  end
104
105
 
105
106
  results.each do |result|
106
107
  unless result.kind_of?(Hash)
107
- err("Unexpected results #{results.inspect}, " \
108
+ err("Unexpected results #{results.inspect}, " +
108
109
  "check task logs for more details")
109
110
  end
110
111
  end
@@ -59,19 +59,9 @@ module Bosh::Cli::Command
59
59
 
60
60
  say("\nUploading stemcell...\n")
61
61
 
62
- status, message = director.upload_stemcell(stemcell.stemcell_file)
63
-
64
- responses = {
65
- :done => "Stemcell uploaded and created",
66
- :non_trackable => "Uploaded stemcell but director at '#{target}' " +
67
- "doesn't support creation tracking",
68
- :track_timeout => "Uploaded stemcell but timed out out " +
69
- "while tracking status",
70
- :error => "Uploaded stemcell but received an error " +
71
- "while tracking status",
72
- }
62
+ status, _ = director.upload_stemcell(stemcell.stemcell_file)
73
63
 
74
- say(responses[status] || "Cannot upload stemcell: #{message}")
64
+ task_report(status, "Stemcell uploaded and created")
75
65
  end
76
66
 
77
67
  def list
@@ -147,41 +137,31 @@ module Bosh::Cli::Command
147
137
 
148
138
  url = yaml[stemcell_name]["url"]
149
139
  size = yaml[stemcell_name]["size"]
150
- pBar = ProgressBar.new(stemcell_name, size)
151
- pBar.file_transfer_mode
140
+ progress_bar = ProgressBar.new(stemcell_name, size)
141
+ progress_bar.file_transfer_mode
152
142
  File.open("#{stemcell_name}", "w") { |file|
153
143
  @http_client.get(url) do |chunk|
154
144
  file.write(chunk)
155
- pBar.inc(chunk.size)
145
+ progress_bar.inc(chunk.size)
156
146
  end
157
147
  }
158
- pBar.finish
148
+ progress_bar.finish
159
149
  puts("Download complete.")
160
150
  end
161
151
 
162
152
  def delete(name, version)
163
153
  auth_required
164
154
 
165
- say("You are going to delete stemcell `#{name} (#{version})'".red)
155
+ say("You are going to delete stemcell `#{name}/#{version}'".red)
166
156
 
167
157
  unless confirmed?
168
158
  say("Canceled deleting stemcell".green)
169
159
  return
170
160
  end
171
161
 
172
- status, message = director.delete_stemcell(name, version)
173
-
174
- responses = {
175
- :done => "Deleted stemcell #{name} (#{version})",
176
- :non_trackable => "Stemcell delete in progress but director " +
177
- "at '#{target}' doesn't support task tracking",
178
- :track_timeout => "Timed out out while tracking " +
179
- "stemcell deletion progress",
180
- :error => "Attempted to delete stemcell but received an error " +
181
- "while tracking status",
182
- }
162
+ status, _ = director.delete_stemcell(name, version)
183
163
 
184
- say(responses[status] || "Cannot delete stemcell: #{message}")
164
+ task_report(status, "Deleted stemcell `#{name}/#{version}'")
185
165
  end
186
166
  end
187
167
  end
@@ -107,8 +107,8 @@ module Bosh::Cli
107
107
  # deployment file.
108
108
  def set_deployment(deployment_file_path)
109
109
  raise MissingTarget, "Must have a target set." if target.nil?
110
- @config_file["deployment"] = { } if is_old_deployment_config?
111
- @config_file["deployment"] ||= { }
110
+ @config_file["deployment"] = {} if is_old_deployment_config?
111
+ @config_file["deployment"] ||= {}
112
112
  @config_file["deployment"][target] = deployment_file_path
113
113
  end
114
114
 
@@ -108,7 +108,10 @@ module BoshStringExtensions
108
108
  end
109
109
 
110
110
  def colorize(color_code)
111
- if Bosh::Cli::Config.colorize && COLOR_CODES[color_code]
111
+ if Bosh::Cli::Config.output.tty? &&
112
+ Bosh::Cli::Config.colorize &&
113
+ COLOR_CODES[color_code]
114
+
112
115
  "#{COLOR_CODES[color_code]}#{self}\e[0m"
113
116
  else
114
117
  self
@@ -163,7 +163,7 @@ module Bosh::Cli
163
163
  end
164
164
 
165
165
  if old_stemcells.size != new_stemcells.size
166
- say("Stemcell update seems to be inconsistent with current " +
166
+ say("Stemcell update seems to be inconsistent with current ".red +
167
167
  "deployment. Please carefully review changes above.".red)
168
168
  unless confirmed?("Are you sure this configuration is correct?")
169
169
  cancel_deployment
@@ -47,12 +47,15 @@ module Bosh
47
47
 
48
48
  def create_user(username, password)
49
49
  payload = JSON.generate("username" => username, "password" => password)
50
- response_code, body = post("/users", "application/json", payload)
50
+ response_code, _ = post("/users", "application/json", payload)
51
51
  response_code == 204
52
52
  end
53
53
 
54
- def upload_stemcell(filename)
55
- upload_and_track("/stemcells", "application/x-compressed", filename)
54
+ def upload_stemcell(filename, options = {})
55
+ options = options.dup
56
+ options[:content_type] = "application/x-compressed"
57
+
58
+ upload_and_track(:post, "/stemcells", filename, options)
56
59
  end
57
60
 
58
61
  def get_version
@@ -92,6 +95,17 @@ module Bosh
92
95
  get_json("/releases/#{name}")
93
96
  end
94
97
 
98
+ def match_packages(manifest_yaml)
99
+ url = "/packages/matches"
100
+ status, body = post(url, "text/yaml", manifest_yaml)
101
+
102
+ if status == 200
103
+ JSON.parse(body)
104
+ else
105
+ err(parse_error_message(status, body))
106
+ end
107
+ end
108
+
95
109
  def get_deployment(name)
96
110
  status, body = get_json_with_status("/deployments/#{name}")
97
111
  if status == 404
@@ -108,43 +122,65 @@ module Bosh
108
122
  body
109
123
  end
110
124
 
111
- def upload_release(filename)
112
- upload_and_track("/releases", "application/x-compressed", filename)
125
+ def upload_release(filename, options = {})
126
+ options = options.dup
127
+ options[:content_type] = "application/x-compressed"
128
+
129
+ upload_and_track(:post, "/releases", filename, options)
113
130
  end
114
131
 
115
132
  def delete_stemcell(name, version, options = {})
116
- request_and_track(:delete, "/stemcells/#{name}/#{version}")
133
+ options = options.dup
134
+ request_and_track(:delete, "/stemcells/#{name}/#{version}", options)
117
135
  end
118
136
 
119
137
  def delete_deployment(name, options = {})
138
+ options = options.dup
139
+ force = options.delete(:force)
140
+
120
141
  url = "/deployments/#{name}"
121
- query_params = []
122
- query_params << "force=true" if options[:force]
123
- url += "?#{query_params.join("&")}" if query_params.size > 0
124
142
 
125
- request_and_track(:delete, url)
143
+ extras = []
144
+ extras << "force=true" if force
145
+
146
+ request_and_track(:delete, add_query_string(url, extras), options)
126
147
  end
127
148
 
128
149
  def delete_release(name, options = {})
129
- url = "/releases/#{name}"
150
+ options = options.dup
151
+ force = options.delete(:force)
152
+ version = options.delete(:version)
130
153
 
131
- query_params = []
132
- query_params << "force=true" if options[:force]
133
- query_params << "version=#{options[:version]}" if options[:version]
154
+ url = "/releases/#{name}"
134
155
 
135
- url += "?#{query_params.join("&")}" if query_params.size > 0
156
+ extras = []
157
+ extras << "force=true" if force
158
+ extras << "version=#{version}" if version
136
159
 
137
- request_and_track(:delete, url)
160
+ request_and_track(:delete, add_query_string(url, extras), options)
138
161
  end
139
162
 
140
163
  def deploy(manifest_yaml, options = {})
164
+ options = options.dup
165
+
166
+ recreate = options.delete(:recreate)
167
+ options[:content_type] = "text/yaml"
168
+ options[:payload] = manifest_yaml
169
+
141
170
  url = "/deployments"
142
- url += "?recreate=true" if options[:recreate]
143
- request_and_track(:post, url, "text/yaml", manifest_yaml)
171
+
172
+ extras = []
173
+ extras << "recreate=true" if recreate
174
+
175
+ request_and_track(:post, add_query_string(url, extras), options)
144
176
  end
145
177
 
146
- def setup_ssh(deployment_name, job, index, user, public_key, password)
178
+ def setup_ssh(deployment_name, job, index, user,
179
+ public_key, password, options = {})
180
+ options = options.dup
181
+
147
182
  url = "/deployments/#{deployment_name}/ssh"
183
+
148
184
  payload = {
149
185
  "command" => "setup",
150
186
  "deployment_name" => deployment_name,
@@ -159,17 +195,19 @@ module Bosh
159
195
  }
160
196
  }
161
197
 
162
- status, task_id, output =
163
- request_and_track(:post, url, "application/json",
164
- JSON.generate(payload), :log_type => "result")
198
+ options[:payload] = JSON.generate(payload)
199
+ options[:content_type] = "application/json"
165
200
 
166
- return nil if status != :done || task_id.nil?
201
+ status, task_id = request_and_track(:post, url, options)
167
202
 
168
- JSON.parse(output)
203
+ # TODO: this needs to be done in command handler, not in director.rb
204
+ return nil if status != :done
205
+ JSON.parse(get_task_result_log(task_id))
169
206
  end
170
207
 
171
- def cleanup_ssh(deployment_name, job, user_regex, indexes)
172
- indexes ||= []
208
+ def cleanup_ssh(deployment_name, job, user_regex, indexes, options = {})
209
+ options = options.dup
210
+
173
211
  url = "/deployments/#{deployment_name}/ssh"
174
212
 
175
213
  payload = {
@@ -177,68 +215,90 @@ module Bosh
177
215
  "deployment_name" => deployment_name,
178
216
  "target" => {
179
217
  "job" => job,
180
- "indexes" => indexes.compact
218
+ "indexes" => (indexes || []).compact
181
219
  },
182
220
  "params" => { "user_regex" => user_regex }
183
221
  }
184
222
 
185
- request_and_track(:post, url, "application/json",
186
- JSON.generate(payload))
223
+ options[:payload] = JSON.generate(payload)
224
+ options[:content_type] = "application/json"
225
+
226
+ request_and_track(:post, url, options)
187
227
  end
188
228
 
189
229
  def change_job_state(deployment_name, manifest_yaml,
190
- job_name, index, new_state)
230
+ job_name, index, new_state, options = {})
231
+ options = options.dup
232
+
191
233
  url = "/deployments/#{deployment_name}/jobs/#{job_name}"
192
234
  url += "/#{index}" if index
193
235
  url += "?state=#{new_state}"
194
- request_and_track(:put, url, "text/yaml", manifest_yaml)
236
+
237
+ options[:payload] = manifest_yaml
238
+ options[:content_type] = "text/yaml"
239
+
240
+ request_and_track(:put, url, options)
195
241
  end
196
242
 
243
+ # TODO: should pass 'force' with options, not as a separate argument
197
244
  def rename_job(deployment_name, manifest_yaml, old_name, new_name,
198
- force = nil)
245
+ force = false, options = {})
246
+ options = options.dup
247
+
199
248
  url = "/deployments/#{deployment_name}/jobs/#{old_name}"
200
249
 
201
- rename_params = ["new_name=#{new_name}"]
202
- rename_params << "force=true" if force
250
+ extras = []
251
+ extras << "new_name=#{new_name}"
252
+ extras << "force=true" if force
203
253
 
204
- url += "?#{rename_params.join("&")}" if rename_params.size > 0
205
- request_and_track(:put, url, "text/yaml",
206
- manifest_yaml, :log_type => "event")
254
+ options[:content_type] = "text/yaml"
255
+ options[:payload] = manifest_yaml
256
+
257
+ request_and_track(:put, add_query_string(url, extras), options)
207
258
  end
208
259
 
209
- def fetch_logs(deployment_name, job_name, index, log_type, filters = nil)
260
+ def fetch_logs(deployment_name, job_name, index, log_type,
261
+ filters = nil, options = {})
262
+ options = options.dup
263
+
210
264
  url = "/deployments/#{deployment_name}/jobs/#{job_name}"
211
265
  url += "/#{index}/logs?type=#{log_type}&filters=#{filters}"
212
266
 
213
- status, task_id = request_and_track(:get, url)
214
- return nil if status != :done || task_id.nil?
267
+ status, task_id = request_and_track(:get, url, options)
268
+
269
+ # TODO: this should be done in command handler, not in director.rb
270
+ return nil if status != :done
215
271
  get_task_result(task_id)
216
272
  end
217
273
 
218
- def fetch_vm_state(deployment_name)
274
+ def fetch_vm_state(deployment_name, options = {})
275
+ options = options.dup
276
+
219
277
  url = "/deployments/#{deployment_name}/vms?format=full"
220
278
 
221
- status, task_id, output =
222
- request_and_track(:get, url, nil, nil, :log_type => "result")
279
+ status, task_id = request_and_track(:get, url, options)
223
280
 
224
- if status != :done || task_id.nil?
281
+ # TODO: this should be done in command handler, not in director.rb
282
+ if status != :done
225
283
  raise DirectorError, "Failed to fetch VMs information from director"
226
284
  end
227
285
 
286
+ output = get_task_result_log(task_id)
287
+
228
288
  output.to_s.split("\n").map do |vm_state|
229
289
  JSON.parse(vm_state)
230
290
  end
231
291
  end
232
292
 
233
293
  def download_resource(id)
234
- status, tmp_file, headers = get("/resources/#{id}", nil,
235
- nil, {}, :file => true)
294
+ status, tmp_file, _ = get("/resources/#{id}",
295
+ nil, nil, {}, :file => true)
236
296
 
237
297
  if status == 200
238
298
  tmp_file
239
299
  else
240
- raise DirectorError, "Cannot download resource `#{id}': " +
241
- "HTTP status #{status}"
300
+ raise DirectorError,
301
+ "Cannot download resource `#{id}': HTTP status #{status}"
242
302
  end
243
303
  end
244
304
 
@@ -269,9 +329,11 @@ module Bosh
269
329
  get_json(url)
270
330
  end
271
331
 
272
- def perform_cloud_scan(deployment_name)
332
+ def perform_cloud_scan(deployment_name, options = {})
333
+ options = options.dup
273
334
  url = "/deployments/#{deployment_name}/scans"
274
- request_and_track(:post, url)
335
+
336
+ request_and_track(:post, url, options)
275
337
  end
276
338
 
277
339
  def list_problems(deployment_name)
@@ -279,14 +341,18 @@ module Bosh
279
341
  get_json(url)
280
342
  end
281
343
 
282
- def apply_resolutions(deployment_name, resolutions)
344
+ def apply_resolutions(deployment_name, resolutions, options = {})
345
+ options = options.dup
346
+
283
347
  url = "/deployments/#{deployment_name}/problems"
284
- request_and_track(:put, url, "application/json",
285
- JSON.generate("resolutions" => resolutions))
348
+ options[:content_type] = "application/json"
349
+ options[:payload] = JSON.generate("resolutions" => resolutions)
350
+
351
+ request_and_track(:put, url, options)
286
352
  end
287
353
 
288
354
  def get_current_time
289
- status, body, headers = get("/info")
355
+ _, _, headers = get("/info")
290
356
  Time.parse(headers[:date]) rescue nil
291
357
  end
292
358
 
@@ -320,11 +386,16 @@ module Bosh
320
386
  get_task(task_id)["result"]
321
387
  end
322
388
 
389
+ def get_task_result_log(task_id)
390
+ log, _ = get_task_output(task_id, 0, "result")
391
+ log
392
+ end
393
+
323
394
  def get_task_output(task_id, offset, log_type = nil)
324
395
  uri = "/tasks/#{task_id}/output"
325
396
  uri += "?type=#{log_type}" if log_type
326
397
 
327
- headers = { "Range" => "bytes=#{offset}-" }
398
+ headers = {"Range" => "bytes=#{offset}-"}
328
399
  response_code, body, headers = get(uri, nil, nil, headers)
329
400
 
330
401
  if response_code == 206 &&
@@ -349,20 +420,28 @@ module Bosh
349
420
  end
350
421
  end
351
422
 
352
- def request_and_track(method, uri, content_type = nil,
353
- payload = nil,options = {})
354
- http_status, body, headers = request(method, uri, content_type, payload)
423
+ # Perform director HTTP request and track director task (if request
424
+ # started one).
425
+ # @param [Symbol] method HTTP method
426
+ # @param [String] uri URI
427
+ # @param [Hash] options Request and tracking options
428
+ def request_and_track(method, uri, options = {})
429
+ options = options.dup
430
+
431
+ content_type = options.delete(:content_type)
432
+ payload = options.delete(:payload)
433
+ track_opts = options
434
+
435
+ http_status, _, headers = request(method, uri, content_type, payload)
355
436
  location = headers[:location]
356
437
  redirected = http_status == 302
357
438
  task_id = nil
358
- output = nil
359
439
 
360
440
  if redirected
361
441
  if location =~ /\/tasks\/(\d+)\/?$/ # Looks like we received task URI
362
442
  task_id = $1
363
- tracker = Bosh::Cli::TaskTracker.new(self, task_id, options)
443
+ tracker = Bosh::Cli::TaskTracker.new(self, task_id, track_opts)
364
444
  status = tracker.track
365
- output = tracker.output
366
445
  else
367
446
  status = :non_trackable
368
447
  end
@@ -370,13 +449,12 @@ module Bosh
370
449
  status = :failed
371
450
  end
372
451
 
373
- [status, task_id, output]
452
+ [status, task_id]
374
453
  end
375
454
 
376
- def upload_and_track(uri, content_type, filename, options = {})
455
+ def upload_and_track(method, uri, filename, options = {})
377
456
  file = FileWithProgressBar.open(filename, "r")
378
- method = options[:method] || :post
379
- request_and_track(method, uri, content_type, file, options)
457
+ request_and_track(method, uri, options.merge(:payload => file))
380
458
  ensure
381
459
  file.stop_progress_bar if file
382
460
  end
@@ -384,6 +462,8 @@ module Bosh
384
462
  def request(method, uri, content_type = nil, payload = nil,
385
463
  headers = {}, options = {})
386
464
  headers = headers.dup
465
+ tmp_file = nil
466
+
387
467
  headers["Content-Type"] = content_type if content_type
388
468
 
389
469
  if options[:file]
@@ -429,16 +509,14 @@ module Bosh
429
509
  end
430
510
 
431
511
  def parse_error_message(status, body)
432
- parsed_body = JSON.parse(body.to_s)
512
+ parsed_body = JSON.parse(body.to_s) rescue {}
433
513
 
434
514
  if parsed_body["code"] && parsed_body["description"]
435
- "Director error %s: %s" % [parsed_body["code"],
436
- parsed_body["description"]]
515
+ "Error %s: %s" % [parsed_body["code"],
516
+ parsed_body["description"]]
437
517
  else
438
- "Director error (HTTP %s): %s" % [status, body]
518
+ "HTTP %s: %s" % [status, body]
439
519
  end
440
- rescue JSON::ParserError
441
- "Director error (HTTP %s): %s" % [status, body]
442
520
  end
443
521
 
444
522
  private
@@ -463,7 +541,7 @@ module Bosh
463
541
 
464
542
  rescue HTTPClient::BadResponseError => e
465
543
  err("Received bad HTTP response from director: #{e}")
466
- rescue URI::Error, SocketError, Errno::ECONNREFUSED, SystemCallError
544
+ rescue URI::Error, SocketError, SystemCallError
467
545
  raise # We handle these upstream
468
546
  rescue => e
469
547
  # httpclient (sadly) doesn't have a generic exception
@@ -478,13 +556,21 @@ module Bosh
478
556
  end
479
557
 
480
558
  def get_json_with_status(url)
481
- status, body, headers = get(url, "application/json")
559
+ status, body, _ = get(url, "application/json")
482
560
  body = JSON.parse(body) if status == 200
483
561
  [status, body]
484
562
  rescue JSON::ParserError
485
563
  raise DirectorError, "Cannot parse director response: #{body}"
486
564
  end
487
565
 
566
+ def add_query_string(url, parts)
567
+ if parts.size > 0
568
+ "#{url}?#{parts.join("&")}"
569
+ else
570
+ url
571
+ end
572
+ end
573
+
488
574
  end
489
575
 
490
576
  class FileWithProgressBar < ::File