bosh_cli 0.19.1 → 0.19.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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