bosh_cli 0.19.4 → 0.19.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cli/commands/base.rb +30 -12
- data/lib/cli/commands/biff.rb +19 -10
- data/lib/cli/commands/cloudcheck.rb +3 -3
- data/lib/cli/commands/deployment.rb +6 -6
- data/lib/cli/commands/misc.rb +48 -13
- data/lib/cli/commands/property_management.rb +6 -2
- data/lib/cli/commands/release.rb +20 -4
- data/lib/cli/commands/stemcell.rb +8 -1
- data/lib/cli/config.rb +33 -15
- data/lib/cli/deployment_helper.rb +61 -9
- data/lib/cli/director.rb +6 -8
- data/lib/cli/errors.rb +1 -1
- data/lib/cli/package_builder.rb +37 -19
- data/lib/cli/runner.rb +18 -2
- data/lib/cli/templates/help_message.erb +3 -0
- data/lib/cli/version.rb +1 -1
- data/spec/assets/biff/ip_out_of_range.yml +63 -0
- data/spec/unit/base_command_spec.rb +32 -6
- data/spec/unit/biff_spec.rb +16 -7
- data/spec/unit/cli_commands_spec.rb +11 -11
- data/spec/unit/config_spec.rb +6 -20
- data/spec/unit/deployment_manifest_spec.rb +117 -0
- data/spec/unit/package_builder_spec.rb +22 -4
- data/spec/unit/runner_spec.rb +17 -0
- metadata +8 -4
data/lib/cli/commands/base.rb
CHANGED
@@ -3,12 +3,11 @@
|
|
3
3
|
module Bosh::Cli
|
4
4
|
module Command
|
5
5
|
class Base
|
6
|
-
BLOBS_DIR = "blobs"
|
7
|
-
BLOBS_INDEX_FILE = "blob_index.yml"
|
8
|
-
|
9
6
|
attr_reader :cache, :config, :options, :work_dir
|
10
7
|
attr_accessor :out, :usage
|
11
8
|
|
9
|
+
DEFAULT_DIRECTOR_PORT = 25555
|
10
|
+
|
12
11
|
def initialize(options = {})
|
13
12
|
@options = options.dup
|
14
13
|
@work_dir = Dir.pwd
|
@@ -16,6 +15,8 @@ module Bosh::Cli
|
|
16
15
|
@config = Config.new(config_file)
|
17
16
|
@cache = Config.cache
|
18
17
|
@exit_code = 0
|
18
|
+
@out = nil
|
19
|
+
@usage = nil
|
19
20
|
end
|
20
21
|
|
21
22
|
class << self
|
@@ -81,17 +82,31 @@ module Bosh::Cli
|
|
81
82
|
|
82
83
|
def confirmed?(question = "Are you sure?")
|
83
84
|
non_interactive? ||
|
84
|
-
|
85
|
+
ask("#{question} (type 'yes' to continue): ") == "yes"
|
85
86
|
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
# @return [String] Target director URL
|
89
|
+
def target
|
90
|
+
url = options[:target] || config.target
|
91
|
+
config.resolve_alias(:target, url) || url
|
91
92
|
end
|
92
|
-
|
93
93
|
alias_method :target_url, :target
|
94
94
|
|
95
|
+
# @return [String] Deployment manifest path
|
96
|
+
def deployment
|
97
|
+
options[:deployment] || config.deployment
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [String] Director username
|
101
|
+
def username
|
102
|
+
options[:username] || ENV["BOSH_USER"] || config.username(target)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [String] Director password
|
106
|
+
def password
|
107
|
+
options[:password] || ENV["BOSH_PASSWORD"] || config.password(target)
|
108
|
+
end
|
109
|
+
|
95
110
|
def target_name
|
96
111
|
config.target_name || target_url
|
97
112
|
end
|
@@ -175,8 +190,8 @@ module Bosh::Cli
|
|
175
190
|
|
176
191
|
def in_release_dir?
|
177
192
|
File.directory?("packages") &&
|
178
|
-
|
179
|
-
|
193
|
+
File.directory?("jobs") &&
|
194
|
+
File.directory?("src")
|
180
195
|
end
|
181
196
|
|
182
197
|
def dirty_state?
|
@@ -186,8 +201,11 @@ module Bosh::Cli
|
|
186
201
|
end
|
187
202
|
|
188
203
|
def normalize_url(url)
|
204
|
+
had_port = url.to_s =~ /:\d+$/
|
189
205
|
url = "http://#{url}" unless url.match(/^https?/)
|
190
|
-
URI.parse(url)
|
206
|
+
uri = URI.parse(url)
|
207
|
+
uri.port = DEFAULT_DIRECTOR_PORT unless had_port
|
208
|
+
uri.to_s.strip.gsub(/\/$/, "")
|
191
209
|
end
|
192
210
|
|
193
211
|
end
|
data/lib/cli/commands/biff.rb
CHANGED
@@ -244,8 +244,17 @@ module Bosh::Cli::Command
|
|
244
244
|
def ip_range(range, netw_name)
|
245
245
|
netw_cidr = get_helper(netw_name)
|
246
246
|
first, last = get_first_last_from_range(range, netw_cidr)
|
247
|
-
|
248
|
-
|
247
|
+
raise_range_err = false
|
248
|
+
begin
|
249
|
+
unless netw_cidr[first] and netw_cidr[last]
|
250
|
+
raise_range_err = true
|
251
|
+
end
|
252
|
+
rescue NetAddr::BoundaryError => e
|
253
|
+
raise_range_err = true
|
254
|
+
end
|
255
|
+
if raise_range_err
|
256
|
+
err("IP range '#{range}' is not within the bounds of network " +
|
257
|
+
"'#{netw_name}', which only has #{netw_cidr.size} IPs.")
|
249
258
|
end
|
250
259
|
first == last ? "#{netw_cidr[first].ip}" :
|
251
260
|
"#{netw_cidr[first].ip} - #{netw_cidr[last].ip}"
|
@@ -303,7 +312,7 @@ module Bosh::Cli::Command
|
|
303
312
|
helper = {}
|
304
313
|
netw_arr = find("networks")
|
305
314
|
if netw_arr.nil?
|
306
|
-
|
315
|
+
err("Must have a network section.")
|
307
316
|
end
|
308
317
|
netw_arr.each do |netw|
|
309
318
|
subnets = netw["subnets"]
|
@@ -322,18 +331,18 @@ module Bosh::Cli::Command
|
|
322
331
|
# @param [Array] subnets The subnets in the network.
|
323
332
|
def check_valid_network_config(netw, subnets)
|
324
333
|
if subnets.nil?
|
325
|
-
|
334
|
+
err("You must have subnets in #{netw["name"]}")
|
326
335
|
end
|
327
336
|
unless subnets.length == 1
|
328
|
-
|
329
|
-
|
337
|
+
err("Biff doesn't know how to deal with anything other than one " +
|
338
|
+
"subnet in #{netw["name"]}")
|
330
339
|
end
|
331
340
|
if subnets.first["range"].nil? || subnets.first["dns"].nil?
|
332
|
-
|
341
|
+
err("Biff requires each network to have range and dns entries.")
|
333
342
|
end
|
334
343
|
if subnets.first["gateway"] && subnets.first["gateway"].match(/.*\.1$/).nil?
|
335
|
-
|
336
|
-
|
344
|
+
err("Biff only supports configurations where the gateway is the " +
|
345
|
+
"first IP (e.g. 172.31.196.1).")
|
337
346
|
end
|
338
347
|
end
|
339
348
|
|
@@ -355,7 +364,7 @@ module Bosh::Cli::Command
|
|
355
364
|
def setup(template)
|
356
365
|
@template_file = template
|
357
366
|
@deployment_file = deployment
|
358
|
-
|
367
|
+
err("Deployment not set.") if @deployment_file.nil?
|
359
368
|
@deployment_obj = load_yaml_file(@deployment_file)
|
360
369
|
@template_obj = load_template_as_yaml
|
361
370
|
@ip_helper = create_ip_helper
|
@@ -6,6 +6,8 @@ module Bosh::Cli::Command
|
|
6
6
|
|
7
7
|
def perform(*options)
|
8
8
|
auth_required
|
9
|
+
# TODO: introduce option helpers
|
10
|
+
deployment_name = options.shift unless options[0] =~ /^--/
|
9
11
|
|
10
12
|
@auto_mode = options.delete("--auto")
|
11
13
|
@report_mode = options.delete("--report")
|
@@ -24,9 +26,7 @@ module Bosh::Cli::Command
|
|
24
26
|
end
|
25
27
|
|
26
28
|
say("Performing cloud check...")
|
27
|
-
|
28
|
-
manifest = prepare_deployment_manifest
|
29
|
-
deployment_name = manifest["name"]
|
29
|
+
deployment_name ||= prepare_deployment_manifest["name"]
|
30
30
|
|
31
31
|
status, _ = director.perform_cloud_scan(deployment_name)
|
32
32
|
|
@@ -6,15 +6,15 @@ module Bosh::Cli::Command
|
|
6
6
|
|
7
7
|
def show_current
|
8
8
|
say(deployment ?
|
9
|
-
|
10
|
-
|
9
|
+
"Current deployment is `#{deployment.green}'" :
|
10
|
+
"Deployment not set".red)
|
11
11
|
end
|
12
12
|
|
13
13
|
def set_current(name)
|
14
14
|
manifest_filename = find_deployment(name)
|
15
15
|
|
16
16
|
unless File.exists?(manifest_filename)
|
17
|
-
err("Missing manifest for #{name} (tried
|
17
|
+
err("Missing manifest for #{name} (tried `#{manifest_filename}')")
|
18
18
|
end
|
19
19
|
|
20
20
|
manifest = load_yaml_file(manifest_filename)
|
@@ -62,7 +62,7 @@ module Bosh::Cli::Command
|
|
62
62
|
"changed to `#{target.red}'!")
|
63
63
|
end
|
64
64
|
|
65
|
-
say("Deployment set to
|
65
|
+
say("Deployment set to `#{manifest_filename.green}'")
|
66
66
|
config.set_deployment(manifest_filename)
|
67
67
|
config.save
|
68
68
|
end
|
@@ -71,8 +71,8 @@ module Bosh::Cli::Command
|
|
71
71
|
auth_required
|
72
72
|
recreate = options.include?("--recreate")
|
73
73
|
|
74
|
-
manifest_yaml =
|
75
|
-
|
74
|
+
manifest_yaml =
|
75
|
+
prepare_deployment_manifest(:yaml => true, :resolve_properties => true)
|
76
76
|
|
77
77
|
if interactive?
|
78
78
|
inspect_deployment_changes(YAML.load(manifest_yaml))
|
data/lib/cli/commands/misc.rb
CHANGED
@@ -96,10 +96,10 @@ module Bosh::Cli::Command
|
|
96
96
|
director = Bosh::Cli::Director.new(target, username, password)
|
97
97
|
|
98
98
|
if director.authenticated?
|
99
|
-
say("Logged in as
|
99
|
+
say("Logged in as `#{username}'")
|
100
100
|
logged_in = true
|
101
101
|
else
|
102
|
-
say("Cannot log in as
|
102
|
+
say("Cannot log in as `#{username}', please try again")
|
103
103
|
unless options[:non_interactive]
|
104
104
|
redirect(:misc, :login, username, nil)
|
105
105
|
end
|
@@ -121,7 +121,7 @@ module Bosh::Cli::Command
|
|
121
121
|
|
122
122
|
def purge_cache
|
123
123
|
if cache.cache_dir != Bosh::Cli::DEFAULT_CACHE_DIR
|
124
|
-
say("Cache directory
|
124
|
+
say("Cache directory `#{@cache.cache_dir}' differs from default, " +
|
125
125
|
"please remove manually")
|
126
126
|
else
|
127
127
|
FileUtils.rm_rf(cache.cache_dir)
|
@@ -131,14 +131,14 @@ module Bosh::Cli::Command
|
|
131
131
|
|
132
132
|
def show_target
|
133
133
|
say(target ?
|
134
|
-
"Current target is
|
135
|
-
"Target not set")
|
134
|
+
"Current target is `#{full_target_name.green}'" :
|
135
|
+
"Target not set".red)
|
136
136
|
end
|
137
137
|
|
138
138
|
def set_target(director_url, name = nil)
|
139
139
|
if name.nil?
|
140
|
-
director_url =
|
141
|
-
|
140
|
+
director_url =
|
141
|
+
config.resolve_alias(:target, director_url) || director_url
|
142
142
|
end
|
143
143
|
|
144
144
|
if director_url.blank?
|
@@ -146,8 +146,8 @@ module Bosh::Cli::Command
|
|
146
146
|
end
|
147
147
|
|
148
148
|
director_url = normalize_url(director_url)
|
149
|
-
if director_url == target
|
150
|
-
say("Target already set to
|
149
|
+
if target && director_url == normalize_url(target)
|
150
|
+
say("Target already set to `#{full_target_name.green}'")
|
151
151
|
return
|
152
152
|
end
|
153
153
|
|
@@ -159,11 +159,11 @@ module Bosh::Cli::Command
|
|
159
159
|
rescue Bosh::Cli::AuthError
|
160
160
|
status = {}
|
161
161
|
rescue Bosh::Cli::DirectorError
|
162
|
-
err("Cannot talk to director at
|
162
|
+
err("Cannot talk to director at `#{director_url}', " +
|
163
163
|
"please set correct target")
|
164
164
|
end
|
165
165
|
else
|
166
|
-
status = {
|
166
|
+
status = {"name" => "Unknown Director", "version" => "n/a"}
|
167
167
|
end
|
168
168
|
|
169
169
|
config.target = director_url
|
@@ -180,19 +180,54 @@ module Bosh::Cli::Command
|
|
180
180
|
end
|
181
181
|
|
182
182
|
config.save
|
183
|
-
say("Target set to
|
183
|
+
say("Target set to `#{full_target_name.green}'")
|
184
184
|
|
185
|
-
if interactive? &&
|
185
|
+
if interactive? && !logged_in?
|
186
186
|
redirect(:misc, :login)
|
187
187
|
end
|
188
188
|
end
|
189
189
|
|
190
|
+
def list_targets
|
191
|
+
# TODO: Bonus point will be checking each director status
|
192
|
+
# (maybe an --status option?)
|
193
|
+
targets = config.aliases(:target) || {}
|
194
|
+
|
195
|
+
err("No targets found") if targets.size == 0
|
196
|
+
|
197
|
+
targets_table = table do |t|
|
198
|
+
t.headings = [ "Name", "Director URL" ]
|
199
|
+
targets.each { |row| t << [row[0], row[1]] }
|
200
|
+
end
|
201
|
+
|
202
|
+
say("\n")
|
203
|
+
say(targets_table)
|
204
|
+
say("\n")
|
205
|
+
say("Targets total: %d" % targets.size)
|
206
|
+
end
|
207
|
+
|
190
208
|
def set_alias(name, value)
|
191
209
|
config.set_alias(:cli, name, value.to_s.strip)
|
192
210
|
config.save
|
193
211
|
say("Alias `#{name.green}' created for command `#{value.green}'")
|
194
212
|
end
|
195
213
|
|
214
|
+
def list_aliases
|
215
|
+
aliases = config.aliases(:cli) || {}
|
216
|
+
|
217
|
+
err("No aliases found") if aliases.size == 0
|
218
|
+
|
219
|
+
sorted = aliases.sort_by { |name, value| name }
|
220
|
+
aliases_table = table do |t|
|
221
|
+
t.headings = [ "Alias", "Command" ]
|
222
|
+
sorted.each { |row| t << [row[0], row[1]] }
|
223
|
+
end
|
224
|
+
|
225
|
+
say("\n")
|
226
|
+
say(aliases_table)
|
227
|
+
say("\n")
|
228
|
+
say("Aliases total: %d" % aliases.size)
|
229
|
+
end
|
230
|
+
|
196
231
|
private
|
197
232
|
|
198
233
|
def print_specs(entity, dir)
|
@@ -8,8 +8,12 @@ module Bosh::Cli::Command
|
|
8
8
|
prepare
|
9
9
|
show_header
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
begin
|
12
|
+
status, body = director.get_property(@deployment_name, name)
|
13
|
+
existing_property = status == 200
|
14
|
+
rescue Bosh::Cli::DirectorError
|
15
|
+
existing_property = false
|
16
|
+
end
|
13
17
|
|
14
18
|
if existing_property
|
15
19
|
say("Current `#{name.green}' value is " +
|
data/lib/cli/commands/release.rb
CHANGED
@@ -264,10 +264,20 @@ module Bosh::Cli::Command
|
|
264
264
|
end
|
265
265
|
|
266
266
|
header("Building packages")
|
267
|
-
Dir[File.join(work_dir, "packages", "*"
|
267
|
+
Dir[File.join(work_dir, "packages", "*")].each do |package_dir|
|
268
|
+
next unless File.directory?(package_dir)
|
269
|
+
package_dirname = File.basename(package_dir)
|
270
|
+
package_spec = load_yaml_file(File.join(package_dir, "spec"))
|
271
|
+
|
272
|
+
if package_spec["name"] != package_dirname
|
273
|
+
err("Found `#{package_spec["name"]}' package in " +
|
274
|
+
"`#{package_dirname}' directory, please fix it")
|
275
|
+
end
|
276
|
+
|
268
277
|
package = Bosh::Cli::PackageBuilder.new(package_spec, work_dir,
|
269
278
|
final, release.blobstore)
|
270
|
-
package.dry_run = dry_run
|
279
|
+
package.dry_run = true if dry_run
|
280
|
+
|
271
281
|
say("Building #{package.name.green}...")
|
272
282
|
package.build
|
273
283
|
packages << package
|
@@ -293,14 +303,20 @@ module Bosh::Cli::Command
|
|
293
303
|
header("Building jobs")
|
294
304
|
Dir[File.join(work_dir, "jobs", "*")].each do |job_dir|
|
295
305
|
next unless File.directory?(job_dir)
|
296
|
-
|
297
|
-
job_spec = File.join(job_dir, "spec")
|
306
|
+
job_dirname = File.basename(job_dir)
|
298
307
|
|
308
|
+
prepare_script = File.join(job_dir, "prepare")
|
299
309
|
if File.exists?(prepare_script)
|
300
310
|
say("Found prepare script in `#{File.basename(job_dir)}'")
|
301
311
|
Bosh::Cli::JobBuilder.run_prepare_script(prepare_script)
|
302
312
|
end
|
303
313
|
|
314
|
+
job_spec = load_yaml_file(File.join(job_dir, "spec"))
|
315
|
+
if job_spec["name"] != job_dirname
|
316
|
+
err("Found `#{job_spec["name"]}' job in " +
|
317
|
+
"`#{job_dirname}' directory, please fix it")
|
318
|
+
end
|
319
|
+
|
304
320
|
job = Bosh::Cli::JobBuilder.new(job_spec, work_dir, final,
|
305
321
|
release.blobstore, built_package_names)
|
306
322
|
job.dry_run = dry_run
|
@@ -137,6 +137,7 @@ module Bosh::Cli::Command
|
|
137
137
|
|
138
138
|
url = yaml[stemcell_name]["url"]
|
139
139
|
size = yaml[stemcell_name]["size"]
|
140
|
+
sha1 = yaml[stemcell_name]["sha"]
|
140
141
|
progress_bar = ProgressBar.new(stemcell_name, size)
|
141
142
|
progress_bar.file_transfer_mode
|
142
143
|
File.open("#{stemcell_name}", "w") { |file|
|
@@ -146,7 +147,13 @@ module Bosh::Cli::Command
|
|
146
147
|
end
|
147
148
|
}
|
148
149
|
progress_bar.finish
|
149
|
-
|
150
|
+
file_sha1 = Digest::SHA1.file(stemcell_name).hexdigest
|
151
|
+
if file_sha1 != sha1
|
152
|
+
err("The downloaded file sha1 '#{file_sha1}' does not match the " +
|
153
|
+
"expected sha1 '#{sha1}'.")
|
154
|
+
else
|
155
|
+
puts("Download complete.")
|
156
|
+
end
|
150
157
|
end
|
151
158
|
|
152
159
|
def delete(name, version)
|
data/lib/cli/config.rb
CHANGED
@@ -23,33 +23,47 @@ module Bosh::Cli
|
|
23
23
|
@config_file = load_yaml_file(@filename, nil)
|
24
24
|
|
25
25
|
unless @config_file.is_a?(Hash)
|
26
|
-
@config_file = {
|
26
|
+
@config_file = {} # Just ignore it if it's malformed
|
27
27
|
end
|
28
28
|
|
29
29
|
rescue SystemCallError => e
|
30
30
|
raise ConfigError, "Cannot read config file: #{e.message}"
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
# @return [Hash] Director credentials
|
34
|
+
def credentials_for(target)
|
35
|
+
if @config_file["auth"].is_a?(Hash) && @config_file["auth"][target]
|
35
36
|
@config_file["auth"][target]
|
36
37
|
else
|
37
|
-
|
38
|
+
{
|
39
|
+
"username" => nil,
|
40
|
+
"password" => nil
|
41
|
+
}
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
45
|
def set_credentials(target, username, password)
|
42
|
-
@config_file["auth"] ||= {
|
43
|
-
@config_file["auth"][target] = {
|
44
|
-
|
46
|
+
@config_file["auth"] ||= {}
|
47
|
+
@config_file["auth"][target] = {
|
48
|
+
"username" => username,
|
49
|
+
"password" => password
|
50
|
+
}
|
45
51
|
end
|
46
52
|
|
47
53
|
def set_alias(category, alias_name, value)
|
48
|
-
@config_file["aliases"] ||= {
|
49
|
-
@config_file["aliases"][category.to_s] ||= {
|
54
|
+
@config_file["aliases"] ||= {}
|
55
|
+
@config_file["aliases"][category.to_s] ||= {}
|
50
56
|
@config_file["aliases"][category.to_s][alias_name] = value
|
51
57
|
end
|
52
58
|
|
59
|
+
def aliases(category)
|
60
|
+
if @config_file.has_key?("aliases") && @config_file["aliases"].is_a?(Hash)
|
61
|
+
@config_file["aliases"][category.to_s]
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
53
67
|
def resolve_alias(category, alias_name)
|
54
68
|
category = category.to_s
|
55
69
|
|
@@ -64,16 +78,20 @@ module Bosh::Cli
|
|
64
78
|
end
|
65
79
|
end
|
66
80
|
|
67
|
-
|
68
|
-
|
81
|
+
# @param [String] target Target director url
|
82
|
+
# @return [String] Username associated with target
|
83
|
+
def username(target)
|
84
|
+
credentials_for(target)["username"]
|
69
85
|
end
|
70
86
|
|
71
|
-
|
72
|
-
|
87
|
+
# @param [String] target Target director url
|
88
|
+
# @return [String] Password associated with target
|
89
|
+
def password(target)
|
90
|
+
credentials_for(target)["password"]
|
73
91
|
end
|
74
92
|
|
75
93
|
# Deployment used to be a string that was only stored for your
|
76
|
-
# current target.
|
94
|
+
# current target. As soon as you switched targets, the deployment
|
77
95
|
# was erased. If the user has the old config we convert it to the
|
78
96
|
# new config.
|
79
97
|
#
|
@@ -106,7 +124,7 @@ module Bosh::Cli
|
|
106
124
|
# @param [String] deployment_file_path The string path to the
|
107
125
|
# deployment file.
|
108
126
|
def set_deployment(deployment_file_path)
|
109
|
-
raise MissingTarget, "Must have a target set
|
127
|
+
raise MissingTarget, "Must have a target set" if target.nil?
|
110
128
|
@config_file["deployment"] = {} if is_old_deployment_config?
|
111
129
|
@config_file["deployment"] ||= {}
|
112
130
|
@config_file["deployment"][target] = deployment_file_path
|