bosh_cli 0.16
Sign up to get free protection for your applications and to get access to all the features.
- data/README +4 -0
- data/Rakefile +55 -0
- data/bin/bosh +17 -0
- data/lib/cli.rb +76 -0
- data/lib/cli/cache.rb +44 -0
- data/lib/cli/changeset_helper.rb +142 -0
- data/lib/cli/command_definition.rb +52 -0
- data/lib/cli/commands/base.rb +245 -0
- data/lib/cli/commands/biff.rb +300 -0
- data/lib/cli/commands/blob.rb +125 -0
- data/lib/cli/commands/cloudcheck.rb +169 -0
- data/lib/cli/commands/deployment.rb +147 -0
- data/lib/cli/commands/job.rb +42 -0
- data/lib/cli/commands/job_management.rb +117 -0
- data/lib/cli/commands/log_management.rb +81 -0
- data/lib/cli/commands/maintenance.rb +131 -0
- data/lib/cli/commands/misc.rb +240 -0
- data/lib/cli/commands/package.rb +112 -0
- data/lib/cli/commands/property_management.rb +125 -0
- data/lib/cli/commands/release.rb +469 -0
- data/lib/cli/commands/ssh.rb +271 -0
- data/lib/cli/commands/stemcell.rb +184 -0
- data/lib/cli/commands/task.rb +213 -0
- data/lib/cli/commands/user.rb +28 -0
- data/lib/cli/commands/vms.rb +53 -0
- data/lib/cli/config.rb +154 -0
- data/lib/cli/core_ext.rb +145 -0
- data/lib/cli/dependency_helper.rb +62 -0
- data/lib/cli/deployment_helper.rb +263 -0
- data/lib/cli/deployment_manifest_compiler.rb +28 -0
- data/lib/cli/director.rb +633 -0
- data/lib/cli/director_task.rb +64 -0
- data/lib/cli/errors.rb +48 -0
- data/lib/cli/event_log_renderer.rb +351 -0
- data/lib/cli/job_builder.rb +226 -0
- data/lib/cli/package_builder.rb +254 -0
- data/lib/cli/packaging_helper.rb +248 -0
- data/lib/cli/release.rb +176 -0
- data/lib/cli/release_builder.rb +215 -0
- data/lib/cli/release_compiler.rb +178 -0
- data/lib/cli/release_tarball.rb +272 -0
- data/lib/cli/runner.rb +771 -0
- data/lib/cli/stemcell.rb +83 -0
- data/lib/cli/task_log_renderer.rb +40 -0
- data/lib/cli/templates/help_message.erb +75 -0
- data/lib/cli/validation.rb +42 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/version_calc.rb +48 -0
- data/lib/cli/versions_index.rb +126 -0
- data/lib/cli/yaml_helper.rb +62 -0
- data/spec/assets/biff/bad_gateway_config.yml +28 -0
- data/spec/assets/biff/good_simple_config.yml +63 -0
- data/spec/assets/biff/good_simple_golden_config.yml +63 -0
- data/spec/assets/biff/good_simple_template.erb +69 -0
- data/spec/assets/biff/multiple_subnets_config.yml +40 -0
- data/spec/assets/biff/network_only_template.erb +34 -0
- data/spec/assets/biff/no_cc_config.yml +27 -0
- data/spec/assets/biff/no_range_config.yml +27 -0
- data/spec/assets/biff/no_subnet_config.yml +16 -0
- data/spec/assets/biff/ok_network_config.yml +30 -0
- data/spec/assets/biff/properties_template.erb +6 -0
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
- 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 +6 -0
- data/spec/assets/release/jobs/cacher/monit +1 -0
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +4 -0
- data/spec/assets/release/jobs/cleaner/monit +1 -0
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
- data/spec/assets/release/jobs/sweeper/job.MF +5 -0
- data/spec/assets/release/jobs/sweeper/monit +1 -0
- 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 +17 -0
- 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 +1 -0
- data/spec/assets/stemcell/stemcell.MF +6 -0
- 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 +25 -0
- data/spec/unit/base_command_spec.rb +66 -0
- data/spec/unit/biff_spec.rb +135 -0
- data/spec/unit/cache_spec.rb +36 -0
- data/spec/unit/cli_commands_spec.rb +481 -0
- data/spec/unit/config_spec.rb +139 -0
- data/spec/unit/core_ext_spec.rb +77 -0
- data/spec/unit/dependency_helper_spec.rb +52 -0
- data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
- data/spec/unit/director_spec.rb +511 -0
- data/spec/unit/director_task_spec.rb +48 -0
- data/spec/unit/event_log_renderer_spec.rb +171 -0
- data/spec/unit/hash_changeset_spec.rb +73 -0
- data/spec/unit/job_builder_spec.rb +454 -0
- data/spec/unit/package_builder_spec.rb +567 -0
- data/spec/unit/release_builder_spec.rb +65 -0
- data/spec/unit/release_spec.rb +66 -0
- data/spec/unit/release_tarball_spec.rb +33 -0
- data/spec/unit/runner_spec.rb +140 -0
- data/spec/unit/ssh_spec.rb +78 -0
- data/spec/unit/stemcell_spec.rb +17 -0
- data/spec/unit/version_calc_spec.rb +27 -0
- data/spec/unit/versions_index_spec.rb +132 -0
- metadata +338 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::Cli::Command
|
4
|
+
class Ssh < Base
|
5
|
+
include Bosh::Cli::DeploymentHelper
|
6
|
+
CMD_UPLOAD = "upload"
|
7
|
+
CMD_DOWNLOAD = "download"
|
8
|
+
CMD_EXEC = "exec"
|
9
|
+
SSH_USER_PREFIX = "bosh_"
|
10
|
+
SSH_DEFAULT_PASSWORD = "bosh"
|
11
|
+
SSH_DSA_PUB = File.expand_path("~/.ssh/id_dsa.pub")
|
12
|
+
SSH_RSA_PUB = File.expand_path("~/.ssh/id_rsa.pub")
|
13
|
+
|
14
|
+
def parse_options(args)
|
15
|
+
options = {}
|
16
|
+
|
17
|
+
# Check if index is supplied on the command line
|
18
|
+
begin
|
19
|
+
# Ruby 1.8.7 treats Integer(nil) as 0, hence the if check
|
20
|
+
if args.size > 0
|
21
|
+
options["index"] = Integer(args[0])
|
22
|
+
args.shift
|
23
|
+
end
|
24
|
+
rescue ArgumentError, TypeError
|
25
|
+
end
|
26
|
+
|
27
|
+
["public_key", "gateway_host", "gateway_user"].each do |option|
|
28
|
+
pos = args.index("--#{option}")
|
29
|
+
if pos
|
30
|
+
options[option] = args[pos + 1]
|
31
|
+
args.delete_at(pos + 1)
|
32
|
+
args.delete_at(pos)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
options
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_public_key(options)
|
39
|
+
# Get public key
|
40
|
+
public_key = nil
|
41
|
+
if options["public_key"]
|
42
|
+
unless File.file?(options["public_key"])
|
43
|
+
err("Please specify a valid public key file")
|
44
|
+
end
|
45
|
+
public_key = File.read(options["public_key"])
|
46
|
+
else
|
47
|
+
# See if ssh-add can be used
|
48
|
+
%x[ssh-add -L 1>/dev/null 2>&1]
|
49
|
+
if $?.exitstatus == 0
|
50
|
+
keys = %x[ssh-add -L]
|
51
|
+
public_key = keys.split("\n")[0]
|
52
|
+
else
|
53
|
+
# Pick up public key from home dir
|
54
|
+
[SSH_DSA_PUB, SSH_RSA_PUB].each do |key_file|
|
55
|
+
if File.file?(key_file)
|
56
|
+
public_key = File.read(key_file)
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
err("Please specify a public key file") if public_key.nil?
|
63
|
+
public_key
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_salt_charset
|
67
|
+
charset = []
|
68
|
+
charset.concat(("a".."z").to_a)
|
69
|
+
charset.concat(("A".."Z").to_a)
|
70
|
+
charset.concat(("0".."9").to_a)
|
71
|
+
charset << "."
|
72
|
+
charset << "/"
|
73
|
+
charset
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypt_password(plain_text)
|
77
|
+
return unless plain_text
|
78
|
+
@salt_charset ||= get_salt_charset
|
79
|
+
salt = ""
|
80
|
+
8.times do
|
81
|
+
salt << @salt_charset[rand(@salt_charset.size)]
|
82
|
+
end
|
83
|
+
plain_text.crypt(salt)
|
84
|
+
end
|
85
|
+
|
86
|
+
def setup_ssh(job, index, password, options, &block)
|
87
|
+
# Get public key
|
88
|
+
public_key = get_public_key(options)
|
89
|
+
|
90
|
+
# Generate a random user name
|
91
|
+
user = SSH_USER_PREFIX + rand(36**9).to_s(36)
|
92
|
+
|
93
|
+
# Get deployment name
|
94
|
+
manifest_name = prepare_deployment_manifest["name"]
|
95
|
+
|
96
|
+
say "Target deployment is #{manifest_name}"
|
97
|
+
results = director.setup_ssh(manifest_name, job, index, user, public_key,
|
98
|
+
encrypt_password(password))
|
99
|
+
|
100
|
+
unless results && results.kind_of?(Array) && !results.empty?
|
101
|
+
err("Error setting up ssh, #{results.inspect}, " \
|
102
|
+
"check task logs for more details")
|
103
|
+
end
|
104
|
+
|
105
|
+
results.each do |result|
|
106
|
+
unless result.kind_of?(Hash)
|
107
|
+
err("Unexpected results #{results.inspect}, " \
|
108
|
+
"check task logs for more details")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if block_given?
|
113
|
+
yield results, user
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
if results
|
117
|
+
say("Cleaning up ssh artifacts")
|
118
|
+
indexes = results.map {|result| result["index"]}
|
119
|
+
# Cleanup only this one 'user'
|
120
|
+
director.cleanup_ssh(manifest_name, job, "^#{user}$", indexes)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_free_port
|
125
|
+
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
126
|
+
socket.bind(Addrinfo.tcp("127.0.0.1", 0))
|
127
|
+
port = socket.local_address.ip_port
|
128
|
+
socket.close
|
129
|
+
|
130
|
+
# The port could get used in the interim, but unlikely in real life
|
131
|
+
port
|
132
|
+
end
|
133
|
+
|
134
|
+
def setup_interactive_shell(job, password, options)
|
135
|
+
index = options["index"]
|
136
|
+
err("Please specify a job index") if index.nil?
|
137
|
+
|
138
|
+
if password.nil?
|
139
|
+
password_retries = 0
|
140
|
+
while password.blank? && password_retries < 3
|
141
|
+
password = ask("Enter password " +
|
142
|
+
"(use it to sudo on remote host): ") { |q| q.echo = "*" }
|
143
|
+
password_retries += 1
|
144
|
+
end
|
145
|
+
err("Please provide ssh password") if password.blank?
|
146
|
+
end
|
147
|
+
|
148
|
+
setup_ssh(job, index, password, options) do |results, user|
|
149
|
+
result = results.first
|
150
|
+
unless result["status"] && result["status"] == "success" && result["ip"]
|
151
|
+
err("Failed to setup ssh on index #{index} #{results.inspect}")
|
152
|
+
end
|
153
|
+
|
154
|
+
say("Starting interactive shell on job #{job}, index #{index}")
|
155
|
+
# Start interactive session
|
156
|
+
if options["gateway_host"]
|
157
|
+
local_port = get_free_port
|
158
|
+
say("Connecting to local port #{local_port}")
|
159
|
+
# Create the ssh tunnel
|
160
|
+
fork do
|
161
|
+
gateway = (options["gateway_user"] ?
|
162
|
+
"#{options["gateway_user"]}@" : "") +
|
163
|
+
options["gateway_host"]
|
164
|
+
# Tunnel will close after 30 seconds,
|
165
|
+
# so no need to worry about cleaning it up
|
166
|
+
exec("ssh -f -L#{local_port}:#{result["ip"]}:22 #{gateway} " +
|
167
|
+
"sleep 30")
|
168
|
+
end
|
169
|
+
result["ip"] = "localhost -p #{local_port}"
|
170
|
+
# Wait for tunnel to get established
|
171
|
+
sleep 3
|
172
|
+
end
|
173
|
+
ssh_session = fork do
|
174
|
+
exec("ssh #{user}@#{result["ip"]}")
|
175
|
+
end
|
176
|
+
Process.waitpid(ssh_session)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def shell(*args)
|
181
|
+
job = args.shift
|
182
|
+
password = args.delete("--default_password") && SSH_DEFAULT_PASSWORD
|
183
|
+
options = parse_options(args)
|
184
|
+
|
185
|
+
if args.size == 0
|
186
|
+
setup_interactive_shell(job, password, options)
|
187
|
+
else
|
188
|
+
say("Executing command '#{args.join(" ")}' on job #{job}")
|
189
|
+
execute_command(CMD_EXEC, job, options, args)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def with_ssh(gateway, ip, user, &block)
|
194
|
+
if gateway
|
195
|
+
gateway.ssh(ip, user) do |ssh|
|
196
|
+
yield(ssh)
|
197
|
+
end
|
198
|
+
else
|
199
|
+
Net::SSH.start(ip, user) do |ssh|
|
200
|
+
yield(ssh)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def with_gateway(host, user, &block)
|
206
|
+
gateway = Net::SSH::Gateway.new(host, user || ENV['USER']) if host
|
207
|
+
yield(gateway ||= nil)
|
208
|
+
ensure
|
209
|
+
gateway.shutdown! if gateway
|
210
|
+
end
|
211
|
+
|
212
|
+
def execute_command(command, job, options, args)
|
213
|
+
setup_ssh(job, options["index"], nil, options) do |results, user|
|
214
|
+
with_gateway(options["gateway_host"],
|
215
|
+
options["gateway_user"]) do |gateway|
|
216
|
+
results.each do | result|
|
217
|
+
unless result["status"] && result["status"] == "success" &&
|
218
|
+
result["ip"]
|
219
|
+
err("Failed to setup ssh on index #{options["index"]}, " +
|
220
|
+
"error: #{result.inspect}")
|
221
|
+
end
|
222
|
+
with_ssh(gateway, result["ip"], user) do |ssh|
|
223
|
+
case command
|
224
|
+
when CMD_EXEC
|
225
|
+
say("\nJob #{job} index #{result["index"]}")
|
226
|
+
puts ssh.exec!(args.join(" "))
|
227
|
+
when CMD_UPLOAD
|
228
|
+
ssh.scp.upload!(args[0], args[1])
|
229
|
+
when CMD_DOWNLOAD
|
230
|
+
file = File.basename(args[0])
|
231
|
+
path = "#{args[1]}/#{file}.#{job}.#{result["index"]}"
|
232
|
+
ssh.scp.download!(args[0], path)
|
233
|
+
say("Downloaded file to #{path}")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def scp(*args)
|
242
|
+
job = args.shift
|
243
|
+
options = parse_options(args)
|
244
|
+
upload = args.delete("--upload")
|
245
|
+
download = args.delete("--download")
|
246
|
+
if upload.nil? && download.nil?
|
247
|
+
err("Please specify one of --upload or --download")
|
248
|
+
end
|
249
|
+
|
250
|
+
if args.empty? || args.size < 2
|
251
|
+
err("Please enter valid source and destination paths")
|
252
|
+
end
|
253
|
+
say("Executing file operations on job #{job}")
|
254
|
+
execute_command(upload ? CMD_UPLOAD : CMD_DOWNLOAD, job, options, args)
|
255
|
+
end
|
256
|
+
|
257
|
+
def cleanup(*args)
|
258
|
+
job = args.shift
|
259
|
+
options = parse_options(args)
|
260
|
+
manifest_name = prepare_deployment_manifest["name"]
|
261
|
+
results = nil
|
262
|
+
if options["index"]
|
263
|
+
results = []
|
264
|
+
results << { "index" => options["index"] }
|
265
|
+
end
|
266
|
+
say "Cleaning up ssh artifacts from job #{job}, index #{options["index"]}"
|
267
|
+
director.cleanup_ssh(manifest_name, job, "^#{SSH_USER_PREFIX}",
|
268
|
+
[options["index"]])
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::Cli::Command
|
4
|
+
class Stemcell < Base
|
5
|
+
include Bosh::Cli::VersionCalc
|
6
|
+
|
7
|
+
# The filename of the public stemcell index.
|
8
|
+
PUBLIC_STEMCELL_INDEX = "public_stemcells_index.yml"
|
9
|
+
|
10
|
+
# The URL of the public stemcell index.
|
11
|
+
PUBLIC_STEMCELL_INDEX_URL = "https://blob.cfblob.com/rest/objects/4e4e78bca2" +
|
12
|
+
"1e121204e4e86ee151bc04f6a19ce46b22?uid=bb6a0c89ef4048a8a0f814e2538" +
|
13
|
+
"5d1c5/user1&expires=1893484800&signature=NJuAr9c8eOid7dKFmOEN7bmzAlI="
|
14
|
+
|
15
|
+
def verify(tarball_path)
|
16
|
+
stemcell = Bosh::Cli::Stemcell.new(tarball_path, cache)
|
17
|
+
|
18
|
+
say("\nVerifying stemcell...")
|
19
|
+
stemcell.validate
|
20
|
+
say("\n")
|
21
|
+
|
22
|
+
if stemcell.valid?
|
23
|
+
say("'%s' is a valid stemcell" % [tarball_path])
|
24
|
+
else
|
25
|
+
say("'%s' is not a valid stemcell:" % [tarball_path])
|
26
|
+
for error in stemcell.errors
|
27
|
+
say("- %s" % [error])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def upload(tarball_path)
|
33
|
+
auth_required
|
34
|
+
|
35
|
+
stemcell = Bosh::Cli::Stemcell.new(tarball_path, cache)
|
36
|
+
|
37
|
+
say("\nVerifying stemcell...")
|
38
|
+
stemcell.validate
|
39
|
+
say("\n")
|
40
|
+
|
41
|
+
unless stemcell.valid?
|
42
|
+
err("Stemcell is invalid, please fix, verify and upload again")
|
43
|
+
end
|
44
|
+
|
45
|
+
say("Checking if stemcell already exists...")
|
46
|
+
name = stemcell.manifest["name"]
|
47
|
+
version = stemcell.manifest["version"]
|
48
|
+
|
49
|
+
existing = director.list_stemcells.select do |sc|
|
50
|
+
sc["name"] == name and sc["version"] == version
|
51
|
+
end
|
52
|
+
|
53
|
+
if existing.empty?
|
54
|
+
say("No")
|
55
|
+
else
|
56
|
+
err("Stemcell \"#{name}\":\"#{version}\" already exists, " +
|
57
|
+
"increment the version if it has changed")
|
58
|
+
end
|
59
|
+
|
60
|
+
say("\nUploading stemcell...\n")
|
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
|
+
}
|
73
|
+
|
74
|
+
say(responses[status] || "Cannot upload stemcell: #{message}")
|
75
|
+
end
|
76
|
+
|
77
|
+
def list
|
78
|
+
auth_required
|
79
|
+
stemcells = director.list_stemcells.sort do |sc1, sc2|
|
80
|
+
sc1["name"] == sc2["name"] ?
|
81
|
+
version_cmp(sc1["version"], sc2["version"]) :
|
82
|
+
sc1["name"] <=> sc2["name"]
|
83
|
+
end
|
84
|
+
|
85
|
+
err("No stemcells") if stemcells.size == 0
|
86
|
+
|
87
|
+
stemcells_table = table do |t|
|
88
|
+
t.headings = "Name", "Version", "CID"
|
89
|
+
stemcells.each do |sc|
|
90
|
+
t << [sc["name"], sc["version"], sc["cid"]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
say("\n")
|
95
|
+
say(stemcells_table)
|
96
|
+
say("\n")
|
97
|
+
say("Stemcells total: %d" % stemcells.size)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Grabs the index file for the publicly available stemcells.
|
101
|
+
# @return [Hash] The index file YAML as a hash.
|
102
|
+
def get_public_stemcell_list
|
103
|
+
@http_client = HTTPClient.new
|
104
|
+
response = @http_client.get(PUBLIC_STEMCELL_INDEX_URL)
|
105
|
+
status_code = response.http_header.status_code
|
106
|
+
if status_code != HTTP::Status::OK
|
107
|
+
raise "Received HTTP #{status_code} from #{index_url}."
|
108
|
+
end
|
109
|
+
YAML.load(response.body)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Prints out the publicly available stemcells.
|
113
|
+
def list_public
|
114
|
+
yaml = get_public_stemcell_list
|
115
|
+
stemcells_table = table do |t|
|
116
|
+
t.headings = "Name", "Url"
|
117
|
+
yaml.each do |name, value|
|
118
|
+
if name != PUBLIC_STEMCELL_INDEX
|
119
|
+
t << [name, value["url"]]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
puts(stemcells_table)
|
124
|
+
puts("To download use 'bosh download public stemcell <stemcell_name>'.")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Downloads one of the publicly available stemcells.
|
128
|
+
# @param [String] stemcell_name The name of the stemcell, as seen in the
|
129
|
+
# public stemcell index file.
|
130
|
+
def download_public(stemcell_name)
|
131
|
+
yaml = get_public_stemcell_list
|
132
|
+
yaml.delete(PUBLIC_STEMCELL_INDEX) if yaml.has_key?(PUBLIC_STEMCELL_INDEX)
|
133
|
+
|
134
|
+
unless yaml.has_key?(stemcell_name)
|
135
|
+
available_stemcells = yaml.map { |k| k }.join(", ")
|
136
|
+
puts("'#{stemcell_name}' not found in '#{available_stemcells}'.".red)
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
if File.exists?(stemcell_name) &&
|
141
|
+
!agree("#{stemcell_name} exists locally. Overwrite it? [yn]")
|
142
|
+
return
|
143
|
+
end
|
144
|
+
|
145
|
+
url = yaml[stemcell_name]["url"]
|
146
|
+
size = yaml[stemcell_name]["size"]
|
147
|
+
pBar = ProgressBar.new(stemcell_name, size)
|
148
|
+
pBar.file_transfer_mode
|
149
|
+
File.open("#{stemcell_name}", "w") { |file|
|
150
|
+
@http_client.get(url) do |chunk|
|
151
|
+
file.write(chunk)
|
152
|
+
pBar.inc(chunk.size)
|
153
|
+
end
|
154
|
+
}
|
155
|
+
pBar.finish
|
156
|
+
puts("Download complete.")
|
157
|
+
end
|
158
|
+
|
159
|
+
def delete(name, version)
|
160
|
+
auth_required
|
161
|
+
|
162
|
+
say("You are going to delete stemcell `#{name} (#{version})'".red)
|
163
|
+
|
164
|
+
unless confirmed?
|
165
|
+
say("Canceled deleting stemcell".green)
|
166
|
+
return
|
167
|
+
end
|
168
|
+
|
169
|
+
status, message = director.delete_stemcell(name, version)
|
170
|
+
|
171
|
+
responses = {
|
172
|
+
:done => "Deleted stemcell #{name} (#{version})",
|
173
|
+
:non_trackable => "Stemcell delete in progress but director " +
|
174
|
+
"at '#{target}' doesn't support task tracking",
|
175
|
+
:track_timeout => "Timed out out while tracking " +
|
176
|
+
"stemcell deletion progress",
|
177
|
+
:error => "Attempted to delete stemcell but received an error " +
|
178
|
+
"while tracking status",
|
179
|
+
}
|
180
|
+
|
181
|
+
say(responses[status] || "Cannot delete stemcell: #{message}")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|