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.
- data/bin/bosh +0 -9
- data/lib/cli.rb +69 -64
- data/lib/cli/backup_destination_path.rb +33 -0
- data/lib/cli/base_command.rb +57 -56
- data/lib/cli/blob_manager.rb +12 -12
- data/lib/cli/changeset_helper.rb +6 -7
- data/lib/cli/client/director.rb +724 -0
- data/lib/cli/command_handler.rb +6 -7
- data/lib/cli/commands/backup.rb +39 -0
- data/lib/cli/commands/biff.rb +42 -21
- data/lib/cli/commands/blob_management.rb +1 -1
- data/lib/cli/commands/cloudcheck.rb +11 -13
- data/lib/cli/commands/deployment.rb +53 -37
- data/lib/cli/commands/help.rb +3 -2
- data/lib/cli/commands/job_management.rb +67 -103
- data/lib/cli/commands/job_rename.rb +6 -8
- data/lib/cli/commands/log_management.rb +78 -55
- data/lib/cli/commands/maintenance.rb +36 -30
- data/lib/cli/commands/misc.rb +72 -51
- data/lib/cli/commands/package.rb +2 -2
- data/lib/cli/commands/property_management.rb +10 -12
- data/lib/cli/commands/release.rb +236 -133
- data/lib/cli/commands/snapshot.rb +93 -0
- data/lib/cli/commands/ssh.rb +216 -213
- data/lib/cli/commands/stemcell.rb +46 -34
- data/lib/cli/commands/task.rb +2 -2
- data/lib/cli/commands/user.rb +27 -3
- data/lib/cli/commands/vm.rb +28 -0
- data/lib/cli/commands/vms.rb +81 -23
- data/lib/cli/config.rb +6 -2
- data/lib/cli/core_ext.rb +31 -30
- data/lib/cli/deployment_helper.rb +134 -159
- data/lib/cli/deployment_manifest.rb +66 -0
- data/lib/cli/deployment_manifest_compiler.rb +0 -3
- data/lib/cli/event_log_renderer.rb +10 -10
- data/lib/cli/file_with_progress_bar.rb +52 -0
- data/lib/cli/job_builder.rb +1 -1
- data/lib/cli/job_command_args.rb +23 -0
- data/lib/cli/job_property_collection.rb +4 -7
- data/lib/cli/job_property_validator.rb +22 -12
- data/lib/cli/job_state.rb +54 -0
- data/lib/cli/line_wrap.rb +54 -0
- data/lib/cli/packaging_helper.rb +10 -10
- data/lib/cli/release.rb +18 -15
- data/lib/cli/release_builder.rb +9 -4
- data/lib/cli/release_compiler.rb +9 -9
- data/lib/cli/release_tarball.rb +3 -6
- data/lib/cli/resurrection.rb +31 -0
- data/lib/cli/runner.rb +56 -30
- data/lib/cli/stemcell.rb +25 -10
- data/lib/cli/task_log_renderer.rb +1 -1
- data/lib/cli/task_tracker.rb +10 -9
- data/lib/cli/validation.rb +3 -1
- data/lib/cli/version.rb +1 -1
- data/lib/cli/version_calc.rb +5 -18
- data/lib/cli/versions_index.rb +1 -1
- data/lib/cli/vm_state.rb +43 -0
- data/lib/cli/yaml_helper.rb +26 -35
- metadata +75 -208
- data/Rakefile +0 -56
- data/lib/cli/director.rb +0 -628
- data/spec/assets/biff/bad_gateway_config.yml +0 -28
- data/spec/assets/biff/good_simple_config.yml +0 -63
- data/spec/assets/biff/good_simple_golden_config.yml +0 -63
- data/spec/assets/biff/good_simple_template.erb +0 -69
- data/spec/assets/biff/ip_out_of_range.yml +0 -63
- data/spec/assets/biff/multiple_subnets_config.yml +0 -40
- data/spec/assets/biff/network_only_template.erb +0 -34
- data/spec/assets/biff/no_cc_config.yml +0 -27
- data/spec/assets/biff/no_range_config.yml +0 -27
- data/spec/assets/biff/no_subnet_config.yml +0 -16
- data/spec/assets/biff/ok_network_config.yml +0 -30
- data/spec/assets/biff/properties_template.erb +0 -6
- data/spec/assets/config/atmos/config/final.yml +0 -6
- data/spec/assets/config/atmos/config/private.yml +0 -4
- data/spec/assets/config/bad-providers/config/final.yml +0 -5
- data/spec/assets/config/bad-providers/config/private.yml +0 -4
- data/spec/assets/config/deprecation/config/final.yml +0 -5
- data/spec/assets/config/deprecation/config/private.yml +0 -2
- data/spec/assets/config/local/config/final.yml +0 -5
- data/spec/assets/config/local/config/private.yml +0 -1
- data/spec/assets/config/s3/config/final.yml +0 -5
- data/spec/assets/config/s3/config/private.yml +0 -5
- data/spec/assets/config/swift-hp/config/final.yml +0 -6
- data/spec/assets/config/swift-hp/config/private.yml +0 -7
- data/spec/assets/config/swift-rackspace/config/final.yml +0 -6
- data/spec/assets/config/swift-rackspace/config/private.yml +0 -6
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +0 -43
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +0 -24
- data/spec/assets/release/jobs/cacher.tgz +0 -0
- data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
- data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
- data/spec/assets/release/jobs/cacher/job.MF +0 -6
- data/spec/assets/release/jobs/cacher/monit +0 -1
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +0 -4
- data/spec/assets/release/jobs/cleaner/monit +0 -1
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +0 -1
- data/spec/assets/release/jobs/sweeper/job.MF +0 -5
- data/spec/assets/release/jobs/sweeper/monit +0 -1
- data/spec/assets/release/packages/mutator.tar.gz +0 -0
- data/spec/assets/release/packages/stuff.tgz +0 -0
- data/spec/assets/release/release.MF +0 -17
- data/spec/assets/release_invalid_checksum.tgz +0 -0
- data/spec/assets/release_invalid_jobs.tgz +0 -0
- data/spec/assets/release_no_name.tgz +0 -0
- data/spec/assets/release_no_version.tgz +0 -0
- data/spec/assets/stemcell/image +0 -1
- data/spec/assets/stemcell/stemcell.MF +0 -6
- data/spec/assets/stemcell_invalid_mf.tgz +0 -0
- data/spec/assets/stemcell_no_image.tgz +0 -0
- data/spec/assets/valid_release.tgz +0 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +0 -28
- data/spec/unit/base_command_spec.rb +0 -87
- data/spec/unit/biff_spec.rb +0 -172
- data/spec/unit/blob_manager_spec.rb +0 -288
- data/spec/unit/cache_spec.rb +0 -36
- data/spec/unit/cli_commands_spec.rb +0 -356
- data/spec/unit/config_spec.rb +0 -125
- data/spec/unit/core_ext_spec.rb +0 -81
- data/spec/unit/dependency_helper_spec.rb +0 -52
- data/spec/unit/deployment_manifest_compiler_spec.rb +0 -63
- data/spec/unit/deployment_manifest_spec.rb +0 -153
- data/spec/unit/director_spec.rb +0 -471
- data/spec/unit/director_task_spec.rb +0 -48
- data/spec/unit/event_log_renderer_spec.rb +0 -171
- data/spec/unit/hash_changeset_spec.rb +0 -73
- data/spec/unit/job_builder_spec.rb +0 -455
- data/spec/unit/job_property_collection_spec.rb +0 -111
- data/spec/unit/job_property_validator_spec.rb +0 -7
- data/spec/unit/job_rename_spec.rb +0 -200
- data/spec/unit/package_builder_spec.rb +0 -593
- data/spec/unit/release_builder_spec.rb +0 -120
- data/spec/unit/release_spec.rb +0 -173
- data/spec/unit/release_tarball_spec.rb +0 -29
- data/spec/unit/runner_spec.rb +0 -7
- data/spec/unit/ssh_spec.rb +0 -84
- data/spec/unit/stemcell_spec.rb +0 -17
- data/spec/unit/task_tracker_spec.rb +0 -131
- data/spec/unit/version_calc_spec.rb +0 -27
- 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
|
data/lib/cli/director.rb
DELETED
|
@@ -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
|