bosh_cli 1.0.3 → 1.5.0.pre.1113

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.
Files changed (144) hide show
  1. data/bin/bosh +0 -9
  2. data/lib/cli.rb +69 -64
  3. data/lib/cli/backup_destination_path.rb +33 -0
  4. data/lib/cli/base_command.rb +57 -56
  5. data/lib/cli/blob_manager.rb +12 -12
  6. data/lib/cli/changeset_helper.rb +6 -7
  7. data/lib/cli/client/director.rb +724 -0
  8. data/lib/cli/command_handler.rb +6 -7
  9. data/lib/cli/commands/backup.rb +39 -0
  10. data/lib/cli/commands/biff.rb +42 -21
  11. data/lib/cli/commands/blob_management.rb +1 -1
  12. data/lib/cli/commands/cloudcheck.rb +11 -13
  13. data/lib/cli/commands/deployment.rb +53 -37
  14. data/lib/cli/commands/help.rb +3 -2
  15. data/lib/cli/commands/job_management.rb +67 -103
  16. data/lib/cli/commands/job_rename.rb +6 -8
  17. data/lib/cli/commands/log_management.rb +78 -55
  18. data/lib/cli/commands/maintenance.rb +36 -30
  19. data/lib/cli/commands/misc.rb +72 -51
  20. data/lib/cli/commands/package.rb +2 -2
  21. data/lib/cli/commands/property_management.rb +10 -12
  22. data/lib/cli/commands/release.rb +236 -133
  23. data/lib/cli/commands/snapshot.rb +93 -0
  24. data/lib/cli/commands/ssh.rb +216 -213
  25. data/lib/cli/commands/stemcell.rb +46 -34
  26. data/lib/cli/commands/task.rb +2 -2
  27. data/lib/cli/commands/user.rb +27 -3
  28. data/lib/cli/commands/vm.rb +28 -0
  29. data/lib/cli/commands/vms.rb +81 -23
  30. data/lib/cli/config.rb +6 -2
  31. data/lib/cli/core_ext.rb +31 -30
  32. data/lib/cli/deployment_helper.rb +134 -159
  33. data/lib/cli/deployment_manifest.rb +66 -0
  34. data/lib/cli/deployment_manifest_compiler.rb +0 -3
  35. data/lib/cli/event_log_renderer.rb +10 -10
  36. data/lib/cli/file_with_progress_bar.rb +52 -0
  37. data/lib/cli/job_builder.rb +1 -1
  38. data/lib/cli/job_command_args.rb +23 -0
  39. data/lib/cli/job_property_collection.rb +4 -7
  40. data/lib/cli/job_property_validator.rb +22 -12
  41. data/lib/cli/job_state.rb +54 -0
  42. data/lib/cli/line_wrap.rb +54 -0
  43. data/lib/cli/packaging_helper.rb +10 -10
  44. data/lib/cli/release.rb +18 -15
  45. data/lib/cli/release_builder.rb +9 -4
  46. data/lib/cli/release_compiler.rb +9 -9
  47. data/lib/cli/release_tarball.rb +3 -6
  48. data/lib/cli/resurrection.rb +31 -0
  49. data/lib/cli/runner.rb +56 -30
  50. data/lib/cli/stemcell.rb +25 -10
  51. data/lib/cli/task_log_renderer.rb +1 -1
  52. data/lib/cli/task_tracker.rb +10 -9
  53. data/lib/cli/validation.rb +3 -1
  54. data/lib/cli/version.rb +1 -1
  55. data/lib/cli/version_calc.rb +5 -18
  56. data/lib/cli/versions_index.rb +1 -1
  57. data/lib/cli/vm_state.rb +43 -0
  58. data/lib/cli/yaml_helper.rb +26 -35
  59. metadata +75 -208
  60. data/Rakefile +0 -56
  61. data/lib/cli/director.rb +0 -628
  62. data/spec/assets/biff/bad_gateway_config.yml +0 -28
  63. data/spec/assets/biff/good_simple_config.yml +0 -63
  64. data/spec/assets/biff/good_simple_golden_config.yml +0 -63
  65. data/spec/assets/biff/good_simple_template.erb +0 -69
  66. data/spec/assets/biff/ip_out_of_range.yml +0 -63
  67. data/spec/assets/biff/multiple_subnets_config.yml +0 -40
  68. data/spec/assets/biff/network_only_template.erb +0 -34
  69. data/spec/assets/biff/no_cc_config.yml +0 -27
  70. data/spec/assets/biff/no_range_config.yml +0 -27
  71. data/spec/assets/biff/no_subnet_config.yml +0 -16
  72. data/spec/assets/biff/ok_network_config.yml +0 -30
  73. data/spec/assets/biff/properties_template.erb +0 -6
  74. data/spec/assets/config/atmos/config/final.yml +0 -6
  75. data/spec/assets/config/atmos/config/private.yml +0 -4
  76. data/spec/assets/config/bad-providers/config/final.yml +0 -5
  77. data/spec/assets/config/bad-providers/config/private.yml +0 -4
  78. data/spec/assets/config/deprecation/config/final.yml +0 -5
  79. data/spec/assets/config/deprecation/config/private.yml +0 -2
  80. data/spec/assets/config/local/config/final.yml +0 -5
  81. data/spec/assets/config/local/config/private.yml +0 -1
  82. data/spec/assets/config/s3/config/final.yml +0 -5
  83. data/spec/assets/config/s3/config/private.yml +0 -5
  84. data/spec/assets/config/swift-hp/config/final.yml +0 -6
  85. data/spec/assets/config/swift-hp/config/private.yml +0 -7
  86. data/spec/assets/config/swift-rackspace/config/final.yml +0 -6
  87. data/spec/assets/config/swift-rackspace/config/private.yml +0 -6
  88. data/spec/assets/deployment.MF +0 -0
  89. data/spec/assets/plugins/bosh/cli/commands/echo.rb +0 -43
  90. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +0 -24
  91. data/spec/assets/release/jobs/cacher.tgz +0 -0
  92. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  93. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  94. data/spec/assets/release/jobs/cacher/job.MF +0 -6
  95. data/spec/assets/release/jobs/cacher/monit +0 -1
  96. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  97. data/spec/assets/release/jobs/cleaner/job.MF +0 -4
  98. data/spec/assets/release/jobs/cleaner/monit +0 -1
  99. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  100. data/spec/assets/release/jobs/sweeper/config/test.conf +0 -1
  101. data/spec/assets/release/jobs/sweeper/job.MF +0 -5
  102. data/spec/assets/release/jobs/sweeper/monit +0 -1
  103. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  104. data/spec/assets/release/packages/stuff.tgz +0 -0
  105. data/spec/assets/release/release.MF +0 -17
  106. data/spec/assets/release_invalid_checksum.tgz +0 -0
  107. data/spec/assets/release_invalid_jobs.tgz +0 -0
  108. data/spec/assets/release_no_name.tgz +0 -0
  109. data/spec/assets/release_no_version.tgz +0 -0
  110. data/spec/assets/stemcell/image +0 -1
  111. data/spec/assets/stemcell/stemcell.MF +0 -6
  112. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  113. data/spec/assets/stemcell_no_image.tgz +0 -0
  114. data/spec/assets/valid_release.tgz +0 -0
  115. data/spec/assets/valid_stemcell.tgz +0 -0
  116. data/spec/spec_helper.rb +0 -28
  117. data/spec/unit/base_command_spec.rb +0 -87
  118. data/spec/unit/biff_spec.rb +0 -172
  119. data/spec/unit/blob_manager_spec.rb +0 -288
  120. data/spec/unit/cache_spec.rb +0 -36
  121. data/spec/unit/cli_commands_spec.rb +0 -356
  122. data/spec/unit/config_spec.rb +0 -125
  123. data/spec/unit/core_ext_spec.rb +0 -81
  124. data/spec/unit/dependency_helper_spec.rb +0 -52
  125. data/spec/unit/deployment_manifest_compiler_spec.rb +0 -63
  126. data/spec/unit/deployment_manifest_spec.rb +0 -153
  127. data/spec/unit/director_spec.rb +0 -471
  128. data/spec/unit/director_task_spec.rb +0 -48
  129. data/spec/unit/event_log_renderer_spec.rb +0 -171
  130. data/spec/unit/hash_changeset_spec.rb +0 -73
  131. data/spec/unit/job_builder_spec.rb +0 -455
  132. data/spec/unit/job_property_collection_spec.rb +0 -111
  133. data/spec/unit/job_property_validator_spec.rb +0 -7
  134. data/spec/unit/job_rename_spec.rb +0 -200
  135. data/spec/unit/package_builder_spec.rb +0 -593
  136. data/spec/unit/release_builder_spec.rb +0 -120
  137. data/spec/unit/release_spec.rb +0 -173
  138. data/spec/unit/release_tarball_spec.rb +0 -29
  139. data/spec/unit/runner_spec.rb +0 -7
  140. data/spec/unit/ssh_spec.rb +0 -84
  141. data/spec/unit/stemcell_spec.rb +0 -17
  142. data/spec/unit/task_tracker_spec.rb +0 -131
  143. data/spec/unit/version_calc_spec.rb +0 -27
  144. data/spec/unit/versions_index_spec.rb +0 -144
data/Rakefile DELETED
@@ -1,56 +0,0 @@
1
- # Copyright (c) 2009-2012 VMware, Inc.
2
-
3
- $:.unshift(File.expand_path("../../rake", __FILE__))
4
-
5
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
6
-
7
- require "rubygems"
8
- require "bundler"
9
-
10
- Bundler.setup(:default, :test)
11
-
12
- require "rake"
13
- begin
14
- require "rspec/core/rake_task"
15
- rescue LoadError
16
- end
17
-
18
- require "bundler_task"
19
- require "ci_task"
20
-
21
- if defined?(RSpec)
22
- namespace :spec do
23
- SPEC_OPTS = %w(--format progress --colour)
24
-
25
- desc "Run unit tests"
26
- unit_rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
27
- t.pattern = "spec/unit/**/*_spec.rb"
28
- t.rspec_opts = SPEC_OPTS
29
- end
30
-
31
- CiTask.new do |task|
32
- task.rspec_task = unit_rspec_task
33
- end
34
- end
35
-
36
- desc "Run tests"
37
- task :spec => %w(spec:unit)
38
-
39
- desc "Run tests for CIs"
40
- task "spec:ci" => %w(spec:unit:ci)
41
- end
42
-
43
- BundlerTask.new
44
-
45
- gem_helper = Bundler::GemHelper.new(Dir.pwd)
46
-
47
- desc "Build BOSH CLI gem into the pkg directory"
48
- task "build" do
49
- gem_helper.build_gem
50
- end
51
-
52
- desc "Build and install BOSH CLI into system gems"
53
- task "install" do
54
- gem_helper.install_gem
55
- end
56
- task :install_head => :install
@@ -1,628 +0,0 @@
1
- # Copyright (c) 2009-2012 VMware, Inc.
2
-
3
- module Bosh
4
- module Cli
5
- class Director
6
- include Bosh::Cli::VersionCalc
7
-
8
- DIRECTOR_HTTP_ERROR_CODES = [400, 403, 404, 500]
9
-
10
- API_TIMEOUT = 86400 * 3
11
- CONNECT_TIMEOUT = 30
12
-
13
- attr_reader :director_uri
14
-
15
- # @return [String]
16
- attr_accessor :user
17
-
18
- # @return [String]
19
- attr_accessor :password
20
-
21
- # Options can include:
22
- # * :no_track => true - do not use +TaskTracker+ for long-running
23
- # +request_and_track+ calls
24
- def initialize(director_uri, user = nil, password = nil, options = {})
25
- if director_uri.nil? || director_uri =~ /^\s*$/
26
- raise DirectorMissing, "no director URI given"
27
- end
28
-
29
- @director_uri = director_uri
30
- @user = user
31
- @password = password
32
- @track_tasks = !options.delete(:no_track)
33
- end
34
-
35
- def uuid
36
- @uuid ||= get_status["uuid"]
37
- end
38
-
39
- def exists?
40
- get_status
41
- true
42
- rescue AuthError
43
- true # For compatibility with directors that return 401 for /info
44
- rescue DirectorError
45
- false
46
- end
47
-
48
- def authenticated?
49
- status = get_status
50
- # Backward compatibility: older directors return 200
51
- # only for logged in users
52
- return true if !status.has_key?("version")
53
- !status["user"].nil?
54
- rescue DirectorError
55
- false
56
- end
57
-
58
- def create_user(username, password)
59
- payload = JSON.generate("username" => username, "password" => password)
60
- response_code, _ = post("/users", "application/json", payload)
61
- response_code == 204
62
- end
63
-
64
- def upload_stemcell(filename, options = {})
65
- options = options.dup
66
- options[:content_type] = "application/x-compressed"
67
-
68
- upload_and_track(:post, "/stemcells", filename, options)
69
- end
70
-
71
- def get_version
72
- get_status["version"]
73
- end
74
-
75
- def get_status
76
- get_json("/info")
77
- end
78
-
79
- def list_stemcells
80
- get_json("/stemcells")
81
- end
82
-
83
- def list_releases
84
- get_json("/releases")
85
- end
86
-
87
- def list_deployments
88
- get_json("/deployments")
89
- end
90
-
91
- def list_running_tasks(verbose = 1)
92
- if version_less(get_version, "0.3.5")
93
- get_json("/tasks?state=processing")
94
- else
95
- get_json("/tasks?state=processing,cancelling,queued" +
96
- "&verbose=#{verbose}")
97
- end
98
- end
99
-
100
- def list_recent_tasks(count = 30, verbose = 1)
101
- get_json("/tasks?limit=#{count}&verbose=#{verbose}")
102
- end
103
-
104
- def get_release(name)
105
- get_json("/releases/#{name}")
106
- end
107
-
108
- def match_packages(manifest_yaml)
109
- url = "/packages/matches"
110
- status, body = post(url, "text/yaml", manifest_yaml)
111
-
112
- if status == 200
113
- JSON.parse(body)
114
- else
115
- err(parse_error_message(status, body))
116
- end
117
- end
118
-
119
- def get_deployment(name)
120
- status, body = get_json_with_status("/deployments/#{name}")
121
- body
122
- end
123
-
124
- def list_vms(name)
125
- status, body = get_json_with_status("/deployments/#{name}/vms")
126
- body
127
- end
128
-
129
- def upload_release(filename, options = {})
130
- options = options.dup
131
- options[:content_type] = "application/x-compressed"
132
-
133
- upload_and_track(:post, "/releases", filename, options)
134
- end
135
-
136
- def rebase_release(filename, options = {})
137
- options = options.dup
138
- options[:content_type] = "application/x-compressed"
139
- upload_and_track(:post, "/releases?rebase=true", filename, options)
140
- end
141
-
142
- def delete_stemcell(name, version, options = {})
143
- options = options.dup
144
- request_and_track(:delete, "/stemcells/#{name}/#{version}", options)
145
- end
146
-
147
- def delete_deployment(name, options = {})
148
- options = options.dup
149
- force = options.delete(:force)
150
-
151
- url = "/deployments/#{name}"
152
-
153
- extras = []
154
- extras << "force=true" if force
155
-
156
- request_and_track(:delete, add_query_string(url, extras), options)
157
- end
158
-
159
- def delete_release(name, options = {})
160
- options = options.dup
161
- force = options.delete(:force)
162
- version = options.delete(:version)
163
-
164
- url = "/releases/#{name}"
165
-
166
- extras = []
167
- extras << "force=true" if force
168
- extras << "version=#{version}" if version
169
-
170
- request_and_track(:delete, add_query_string(url, extras), options)
171
- end
172
-
173
- def deploy(manifest_yaml, options = {})
174
- options = options.dup
175
-
176
- recreate = options.delete(:recreate)
177
- options[:content_type] = "text/yaml"
178
- options[:payload] = manifest_yaml
179
-
180
- url = "/deployments"
181
-
182
- extras = []
183
- extras << "recreate=true" if recreate
184
-
185
- request_and_track(:post, add_query_string(url, extras), options)
186
- end
187
-
188
- def setup_ssh(deployment_name, job, index, user,
189
- public_key, password, options = {})
190
- options = options.dup
191
-
192
- url = "/deployments/#{deployment_name}/ssh"
193
-
194
- payload = {
195
- "command" => "setup",
196
- "deployment_name" => deployment_name,
197
- "target" => {
198
- "job" => job,
199
- "indexes" => [index].compact
200
- },
201
- "params" => {
202
- "user" => user,
203
- "public_key" => public_key,
204
- "password" => password
205
- }
206
- }
207
-
208
- options[:payload] = JSON.generate(payload)
209
- options[:content_type] = "application/json"
210
-
211
- request_and_track(:post, url, options)
212
- end
213
-
214
- def cleanup_ssh(deployment_name, job, user_regex, indexes, options = {})
215
- options = options.dup
216
-
217
- url = "/deployments/#{deployment_name}/ssh"
218
-
219
- payload = {
220
- "command" => "cleanup",
221
- "deployment_name" => deployment_name,
222
- "target" => {
223
- "job" => job,
224
- "indexes" => (indexes || []).compact
225
- },
226
- "params" => { "user_regex" => user_regex }
227
- }
228
-
229
- options[:payload] = JSON.generate(payload)
230
- options[:content_type] = "application/json"
231
-
232
- request_and_track(:post, url, options)
233
- end
234
-
235
- def change_job_state(deployment_name, manifest_yaml,
236
- job_name, index, new_state, options = {})
237
- options = options.dup
238
-
239
- url = "/deployments/#{deployment_name}/jobs/#{job_name}"
240
- url += "/#{index}" if index
241
- url += "?state=#{new_state}"
242
-
243
- options[:payload] = manifest_yaml
244
- options[:content_type] = "text/yaml"
245
-
246
- request_and_track(:put, url, options)
247
- end
248
-
249
- # TODO: should pass 'force' with options, not as a separate argument
250
- def rename_job(deployment_name, manifest_yaml, old_name, new_name,
251
- force = false, options = {})
252
- options = options.dup
253
-
254
- url = "/deployments/#{deployment_name}/jobs/#{old_name}"
255
-
256
- extras = []
257
- extras << "new_name=#{new_name}"
258
- extras << "force=true" if force
259
-
260
- options[:content_type] = "text/yaml"
261
- options[:payload] = manifest_yaml
262
-
263
- request_and_track(:put, add_query_string(url, extras), options)
264
- end
265
-
266
- def fetch_logs(deployment_name, job_name, index, log_type,
267
- filters = nil, options = {})
268
- options = options.dup
269
-
270
- url = "/deployments/#{deployment_name}/jobs/#{job_name}"
271
- url += "/#{index}/logs?type=#{log_type}&filters=#{filters}"
272
-
273
- status, task_id = request_and_track(:get, url, options)
274
-
275
- # TODO: this should be done in command handler, not in director.rb
276
- return nil if status != :done
277
- get_task_result(task_id)
278
- end
279
-
280
- def fetch_vm_state(deployment_name, options = {})
281
- options = options.dup
282
-
283
- url = "/deployments/#{deployment_name}/vms?format=full"
284
-
285
- status, task_id = request_and_track(:get, url, options)
286
-
287
- # TODO: this should be done in command handler, not in director.rb
288
- if status != :done
289
- raise DirectorError, "Failed to fetch VMs information from director"
290
- end
291
-
292
- output = get_task_result_log(task_id)
293
-
294
- output.to_s.split("\n").map do |vm_state|
295
- JSON.parse(vm_state)
296
- end
297
- end
298
-
299
- def download_resource(id)
300
- status, tmp_file, _ = get("/resources/#{id}",
301
- nil, nil, {}, :file => true)
302
-
303
- if status == 200
304
- tmp_file
305
- else
306
- raise DirectorError,
307
- "Cannot download resource `#{id}': HTTP status #{status}"
308
- end
309
- end
310
-
311
- def create_property(deployment_name, property_name, value)
312
- url = "/deployments/#{deployment_name}/properties"
313
- payload = JSON.generate("name" => property_name, "value" => value)
314
- post(url, "application/json", payload)
315
- end
316
-
317
- def update_property(deployment_name, property_name, value)
318
- url = "/deployments/#{deployment_name}/properties/#{property_name}"
319
- payload = JSON.generate("value" => value)
320
- put(url, "application/json", payload)
321
- end
322
-
323
- def delete_property(deployment_name, property_name)
324
- url = "/deployments/#{deployment_name}/properties/#{property_name}"
325
- delete(url, "application/json")
326
- end
327
-
328
- def get_property(deployment_name, property_name)
329
- url = "/deployments/#{deployment_name}/properties/#{property_name}"
330
- get_json_with_status(url)
331
- end
332
-
333
- def list_properties(deployment_name)
334
- url = "/deployments/#{deployment_name}/properties"
335
- get_json(url)
336
- end
337
-
338
- def perform_cloud_scan(deployment_name, options = {})
339
- options = options.dup
340
- url = "/deployments/#{deployment_name}/scans"
341
-
342
- request_and_track(:post, url, options)
343
- end
344
-
345
- def list_problems(deployment_name)
346
- url = "/deployments/#{deployment_name}/problems"
347
- get_json(url)
348
- end
349
-
350
- def apply_resolutions(deployment_name, resolutions, options = {})
351
- options = options.dup
352
-
353
- url = "/deployments/#{deployment_name}/problems"
354
- options[:content_type] = "application/json"
355
- options[:payload] = JSON.generate("resolutions" => resolutions)
356
-
357
- request_and_track(:put, url, options)
358
- end
359
-
360
- def get_current_time
361
- _, _, headers = get("/info")
362
- Time.parse(headers[:date]) rescue nil
363
- end
364
-
365
- def get_time_difference
366
- # This includes the round-trip to director
367
- ctime = get_current_time
368
- ctime ? Time.now - ctime : 0
369
- end
370
-
371
- def get_task(task_id)
372
- response_code, body = get("/tasks/#{task_id}")
373
- raise AuthError if response_code == 401
374
- raise MissingTask, "Task #{task_id} not found" if response_code == 404
375
-
376
- if response_code != 200
377
- raise TaskTrackError, "Got HTTP #{response_code} " +
378
- "while tracking task state"
379
- end
380
-
381
- JSON.parse(body)
382
- rescue JSON::ParserError
383
- raise TaskTrackError, "Cannot parse task JSON, " +
384
- "incompatible director version"
385
- end
386
-
387
- def get_task_state(task_id)
388
- get_task(task_id)["state"]
389
- end
390
-
391
- def get_task_result(task_id)
392
- get_task(task_id)["result"]
393
- end
394
-
395
- def get_task_result_log(task_id)
396
- log, _ = get_task_output(task_id, 0, "result")
397
- log
398
- end
399
-
400
- def get_task_output(task_id, offset, log_type = nil)
401
- uri = "/tasks/#{task_id}/output"
402
- uri += "?type=#{log_type}" if log_type
403
-
404
- headers = {"Range" => "bytes=#{offset}-"}
405
- response_code, body, headers = get(uri, nil, nil, headers)
406
-
407
- if response_code == 206 &&
408
- headers[:content_range].to_s =~ /bytes \d+-(\d+)\/\d+/
409
- new_offset = $1.to_i + 1
410
- else
411
- new_offset = nil
412
- end
413
-
414
- # backward compatible with renaming soap log to cpi log
415
- if response_code == 204 && log_type == "cpi"
416
- get_task_output(task_id, offset, "soap")
417
- else
418
- [body, new_offset]
419
- end
420
- end
421
-
422
- def cancel_task(task_id)
423
- response_code, body = delete("/task/#{task_id}")
424
- raise AuthError if response_code == 401
425
- raise MissingTask, "No task##{task_id} found" if response_code == 404
426
- [body, response_code]
427
- end
428
-
429
- [:post, :put, :get, :delete].each do |method_name|
430
- define_method method_name do |*args|
431
- request(method_name, *args)
432
- end
433
- end
434
-
435
- # Perform director HTTP request and track director task (if request
436
- # started one).
437
- # @param [Symbol] method HTTP method
438
- # @param [String] uri URI
439
- # @param [Hash] options Request and tracking options
440
- def request_and_track(method, uri, options = {})
441
- options = options.dup
442
-
443
- content_type = options.delete(:content_type)
444
- payload = options.delete(:payload)
445
- track_opts = options
446
-
447
- http_status, _, headers = request(method, uri, content_type, payload)
448
- location = headers[:location]
449
- redirected = http_status == 302
450
- task_id = nil
451
-
452
- if redirected
453
- if location =~ /\/tasks\/(\d+)\/?$/ # Looks like we received task URI
454
- task_id = $1
455
- if @track_tasks
456
- tracker = Bosh::Cli::TaskTracker.new(self, task_id, track_opts)
457
- status = tracker.track
458
- else
459
- status = :running
460
- end
461
- else
462
- status = :non_trackable
463
- end
464
- else
465
- status = :failed
466
- end
467
-
468
- [status, task_id]
469
- end
470
-
471
- def upload_and_track(method, uri, filename, options = {})
472
- file = FileWithProgressBar.open(filename, "r")
473
- request_and_track(method, uri, options.merge(:payload => file))
474
- ensure
475
- file.stop_progress_bar if file
476
- end
477
-
478
- def request(method, uri, content_type = nil, payload = nil,
479
- headers = {}, options = {})
480
- headers = headers.dup
481
- tmp_file = nil
482
-
483
- headers["Content-Type"] = content_type if content_type
484
-
485
- if options[:file]
486
- tmp_file = File.open(File.join(Dir.mktmpdir, "streamed-response"),
487
- "w")
488
-
489
- response_reader = lambda do |part|
490
- tmp_file.write(part)
491
- end
492
- else
493
- response_reader = nil
494
- end
495
-
496
- response = perform_http_request(method, @director_uri + uri,
497
- payload, headers, &response_reader)
498
-
499
- if options[:file]
500
- tmp_file.close
501
- body = tmp_file.path
502
- else
503
- body = response.body
504
- end
505
-
506
- if DIRECTOR_HTTP_ERROR_CODES.include?(response.code)
507
- if response.code == 404
508
- raise ResourceNotFound, parse_error_message(response.code, body)
509
- else
510
- raise DirectorError, parse_error_message(response.code, body)
511
- end
512
- end
513
-
514
- headers = response.headers.inject({}) do |hash, (k, v)|
515
- # Some HTTP clients symbolize headers, some do not.
516
- # To make it easier to switch between them, we try
517
- # to symbolize them ourselves.
518
- hash[k.to_s.downcase.gsub(/-/, "_").to_sym] = v
519
- hash
520
- end
521
-
522
- [response.code, body, headers]
523
-
524
- rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
525
- raise DirectorInaccessible,
526
- "cannot access director (#{e.message})"
527
- rescue SystemCallError => e
528
- raise DirectorError, "System call error while talking to director: #{e}"
529
- end
530
-
531
- def parse_error_message(status, body)
532
- parsed_body = JSON.parse(body.to_s) rescue {}
533
-
534
- if parsed_body["code"] && parsed_body["description"]
535
- "Error %s: %s" % [parsed_body["code"],
536
- parsed_body["description"]]
537
- else
538
- "HTTP %s: %s" % [status, body]
539
- end
540
- end
541
-
542
- private
543
-
544
- def perform_http_request(method, uri, payload = nil, headers = {}, &block)
545
- http_client = HTTPClient.new
546
-
547
- http_client.send_timeout = API_TIMEOUT
548
- http_client.receive_timeout = API_TIMEOUT
549
- http_client.connect_timeout = CONNECT_TIMEOUT
550
-
551
- # HTTPClient#set_auth doesn't seem to work properly,
552
- # injecting header manually instead.
553
- # TODO: consider using vanilla Net::HTTP
554
- if @user && @password
555
- headers["Authorization"] = "Basic " +
556
- Base64.encode64("#{@user}:#{@password}").strip
557
- end
558
-
559
- http_client.request(method, uri, :body => payload,
560
- :header => headers, &block)
561
-
562
- rescue HTTPClient::BadResponseError => e
563
- err("Received bad HTTP response from director: #{e}")
564
- rescue URI::Error, SocketError, SystemCallError
565
- raise # We handle these upstream
566
- rescue => e
567
- # httpclient (sadly) doesn't have a generic exception
568
- err("REST API call exception: #{e}")
569
- end
570
-
571
- def get_json(url)
572
- status, body = get_json_with_status(url)
573
- raise AuthError if status == 401
574
- raise DirectorError, "Director HTTP #{status}" if status != 200
575
- body
576
- end
577
-
578
- def get_json_with_status(url)
579
- status, body, _ = get(url, "application/json")
580
- body = JSON.parse(body) if status == 200
581
- [status, body]
582
- rescue JSON::ParserError
583
- raise DirectorError, "Cannot parse director response: #{body}"
584
- end
585
-
586
- def add_query_string(url, parts)
587
- if parts.size > 0
588
- "#{url}?#{parts.join("&")}"
589
- else
590
- url
591
- end
592
- end
593
-
594
- end
595
-
596
- class FileWithProgressBar < ::File
597
- def progress_bar
598
- return @progress_bar if @progress_bar
599
- out = Bosh::Cli::Config.output || StringIO.new
600
- @progress_bar = ProgressBar.new(File.basename(self.path),
601
- File.size(self.path), out)
602
- @progress_bar.file_transfer_mode
603
- @progress_bar
604
- end
605
-
606
- def stop_progress_bar
607
- progress_bar.halt unless progress_bar.finished?
608
- end
609
-
610
- def size
611
- File.size(self.path)
612
- end
613
-
614
- def read(*args)
615
- result = super(*args)
616
-
617
- if result && result.size > 0
618
- progress_bar.inc(result.size)
619
- else
620
- progress_bar.finish
621
- end
622
-
623
- result
624
- end
625
- end
626
-
627
- end
628
- end