bosh_cli 0.19.6 → 1.0.rc1

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 (64) hide show
  1. data/bin/bosh +3 -0
  2. data/lib/cli.rb +15 -5
  3. data/lib/cli/{commands/base.rb → base_command.rb} +38 -44
  4. data/lib/cli/command_discovery.rb +40 -0
  5. data/lib/cli/command_handler.rb +135 -0
  6. data/lib/cli/commands/biff.rb +16 -12
  7. data/lib/cli/commands/blob_management.rb +10 -3
  8. data/lib/cli/commands/cloudcheck.rb +13 -11
  9. data/lib/cli/commands/complete.rb +29 -0
  10. data/lib/cli/commands/deployment.rb +137 -28
  11. data/lib/cli/commands/help.rb +96 -0
  12. data/lib/cli/commands/job.rb +4 -1
  13. data/lib/cli/commands/job_management.rb +36 -23
  14. data/lib/cli/commands/job_rename.rb +11 -12
  15. data/lib/cli/commands/log_management.rb +28 -32
  16. data/lib/cli/commands/maintenance.rb +6 -1
  17. data/lib/cli/commands/misc.rb +129 -87
  18. data/lib/cli/commands/package.rb +6 -65
  19. data/lib/cli/commands/property_management.rb +20 -8
  20. data/lib/cli/commands/release.rb +211 -206
  21. data/lib/cli/commands/ssh.rb +178 -188
  22. data/lib/cli/commands/stemcell.rb +114 -51
  23. data/lib/cli/commands/task.rb +74 -56
  24. data/lib/cli/commands/user.rb +6 -3
  25. data/lib/cli/commands/vms.rb +17 -15
  26. data/lib/cli/config.rb +27 -1
  27. data/lib/cli/core_ext.rb +27 -1
  28. data/lib/cli/deployment_helper.rb +47 -0
  29. data/lib/cli/director.rb +18 -9
  30. data/lib/cli/errors.rb +6 -0
  31. data/lib/cli/job_builder.rb +75 -23
  32. data/lib/cli/job_property_collection.rb +87 -0
  33. data/lib/cli/job_property_validator.rb +130 -0
  34. data/lib/cli/package_builder.rb +32 -5
  35. data/lib/cli/release.rb +2 -0
  36. data/lib/cli/release_builder.rb +9 -13
  37. data/lib/cli/release_compiler.rb +5 -34
  38. data/lib/cli/release_tarball.rb +4 -19
  39. data/lib/cli/runner.rb +118 -694
  40. data/lib/cli/version.rb +1 -1
  41. data/spec/assets/config/swift-hp/config/final.yml +6 -0
  42. data/spec/assets/config/swift-hp/config/private.yml +7 -0
  43. data/spec/assets/config/swift-rackspace/config/final.yml +6 -0
  44. data/spec/assets/config/swift-rackspace/config/private.yml +6 -0
  45. data/spec/spec_helper.rb +0 -5
  46. data/spec/unit/base_command_spec.rb +32 -37
  47. data/spec/unit/biff_spec.rb +11 -10
  48. data/spec/unit/cli_commands_spec.rb +96 -88
  49. data/spec/unit/core_ext_spec.rb +1 -1
  50. data/spec/unit/deployment_manifest_spec.rb +36 -0
  51. data/spec/unit/director_spec.rb +17 -3
  52. data/spec/unit/job_builder_spec.rb +2 -2
  53. data/spec/unit/job_property_collection_spec.rb +111 -0
  54. data/spec/unit/job_property_validator_spec.rb +7 -0
  55. data/spec/unit/job_rename_spec.rb +7 -6
  56. data/spec/unit/package_builder_spec.rb +2 -2
  57. data/spec/unit/release_builder_spec.rb +33 -0
  58. data/spec/unit/release_spec.rb +54 -0
  59. data/spec/unit/release_tarball_spec.rb +2 -7
  60. data/spec/unit/runner_spec.rb +1 -151
  61. data/spec/unit/ssh_spec.rb +15 -9
  62. metadata +41 -12
  63. data/lib/cli/command_definition.rb +0 -52
  64. data/lib/cli/templates/help_message.erb +0 -80
@@ -3,22 +3,61 @@
3
3
  module Bosh::Cli::Command
4
4
  class Task < Base
5
5
 
6
- # Tracks a running task or outputs the logs from an old task. Triggered
7
- # with 'bosh task <task_num>'. Check parse_flags to see what flags can be
8
- # used with this.
9
- #
10
- # @param [Array] args The arguments from the command line command.
11
- def track(*args)
6
+ INCLUDE_ALL = "Include all task types (ssh, logs, vms, etc)"
7
+
8
+ # bosh task
9
+ usage "task"
10
+ desc "Show task status and start tracking its output"
11
+ option "--no-cache", "Don't cache output locally"
12
+ option "--event", "Track event log"
13
+ option "--soap", "Track CPI log"
14
+ option "--debug", "Track debug log"
15
+ option "--result", "Track result log"
16
+ option "--raw", "Show raw log"
17
+ option "--no-filter", INCLUDE_ALL
18
+ def track(task_id = nil)
12
19
  auth_required
20
+ use_filter = !options.key?(:no_filter)
21
+ use_cache = !options.key?(:no_cache)
22
+ raw_output = options[:raw]
23
+
24
+ log_type = "event"
25
+ n_types = 0
26
+ if options[:soap]
27
+ log_type = "soap"
28
+ n_types += 1
29
+ end
30
+
31
+ if options[:debug]
32
+ log_type = "debug"
33
+ n_types += 1
34
+ end
13
35
 
14
- task_id, log_type, no_cache, raw_output = parse_flags(args)
36
+ if options[:event]
37
+ log_type = "event"
38
+ n_types += 1
39
+ end
40
+
41
+ if options[:result]
42
+ log_type = "result"
43
+ raw_output = true
44
+ n_types += 1
45
+ end
46
+
47
+ if n_types > 1
48
+ err("Cannot track more than one log type")
49
+ end
15
50
 
16
51
  track_options = {
17
52
  :log_type => log_type,
18
- :use_cache => no_cache ? false : true,
53
+ :use_cache => use_cache,
19
54
  :raw_output => raw_output
20
55
  }
21
56
 
57
+ if task_id.nil? || %w(last latest).include?(task_id)
58
+ task_id = get_last_task_id(get_verbose_level(use_filter))
59
+ end
60
+
22
61
  if task_id.to_i <= 0
23
62
  err("Task id must be a positive integer")
24
63
  end
@@ -27,83 +66,55 @@ module Bosh::Cli::Command
27
66
  tracker.track
28
67
  end
29
68
 
69
+ # bosh tasks
70
+ usage "tasks"
71
+ desc "Show running tasks"
72
+ option "--no-filter", INCLUDE_ALL
30
73
  def list_running
31
74
  auth_required
32
- tasks = director.list_running_tasks
75
+ use_filter = !options.key?(:no_filter)
76
+ tasks = director.list_running_tasks(get_verbose_level(use_filter))
33
77
  err("No running tasks") if tasks.empty?
34
78
  show_tasks_table(tasks.sort_by { |t| t["id"].to_i * -1 })
35
79
  say("Total tasks running now: %d" % [tasks.size])
36
80
  end
37
81
 
82
+ # bosh tasks recent
83
+ usage "tasks recent"
84
+ desc "Show <number> recent tasks"
85
+ option "--no-filter", INCLUDE_ALL
38
86
  def list_recent(count = 30)
39
87
  auth_required
40
- tasks = director.list_recent_tasks(count)
88
+ use_filter = !options.key?(:no_filter)
89
+ tasks = director.list_recent_tasks(count, get_verbose_level(use_filter))
41
90
  err("No recent tasks") if tasks.empty?
42
91
  show_tasks_table(tasks)
43
- say("Showing %d recent %s" % [tasks.size,
44
- tasks.size == 1 ? "task" : "tasks"])
92
+ say("Showing #{tasks.size} recent #{tasks.size == 1 ? "task" : "tasks"}")
45
93
  end
46
94
 
95
+ # bosh cancel task
96
+ usage "cancel task"
97
+ desc "Cancel task once it reaches the next checkpoint"
47
98
  def cancel(task_id)
48
99
  auth_required
49
100
  task = Bosh::Cli::DirectorTask.new(director, task_id)
50
101
  task.cancel
51
- say("Cancelling task #{task_id}")
102
+ say("Task #{task_id} is getting canceled")
52
103
  end
53
104
 
54
105
  private
55
106
 
56
- # Parses the command line args to see what options have been specified.
57
- #
58
- # @param [Array] flags The args that were passed in from the command line.
59
- # @return [String, String, Boolean, Boolean] The task id, the type of log
60
- # output, whether to use cache or not, whether to output the raw log.
61
- def parse_flags(flags)
62
- task_id = flags.shift
63
- task_id = get_last_task_id if asking_for_last_task?(task_id)
64
-
65
- log_type = get_log_type(flags)
66
- no_cache = flags.include?("--no-cache")
67
- raw_output = flags.include?("--raw")
68
-
69
- [task_id, log_type, no_cache, raw_output]
70
- end
71
-
72
- # Whether the bosh user has asked for the last (most recently run) task.
73
- #
74
- # @param [String] task_id The task id specified by the user. Could be a
75
- # number as a string or it could be "last" or "latest".
76
- # @return [Boolean] Whether the user is asking for the most recent task.
77
- def asking_for_last_task?(task_id)
78
- task_id.nil? || %w(last latest).include?(task_id)
79
- end
80
-
81
107
  # Returns the task id of the most recently run task.
82
- #
83
108
  # @return [String] The task id of the most recently run task.
84
- def get_last_task_id
85
- last = director.list_recent_tasks(1)
86
- if last.size == 0
109
+ def get_last_task_id(verbose = 1)
110
+ last = director.list_recent_tasks(1, verbose)
111
+ if last.empty?
87
112
  err("No tasks found")
88
113
  end
89
114
 
90
115
  last[0]["id"]
91
116
  end
92
117
 
93
- # Returns what type of log output the user is asking for.
94
- #
95
- # @param [Array] flags The args that were passed in from the command line.
96
- # @return [String] The type of log output the user is asking for.
97
- def get_log_type(flags)
98
- if flags.include?("--soap")
99
- "soap"
100
- elsif flags.include?("--debug")
101
- "debug"
102
- else
103
- "event"
104
- end
105
- end
106
-
107
118
  def show_tasks_table(tasks)
108
119
  return if tasks.empty?
109
120
  tasks_table = table do |t|
@@ -118,5 +129,12 @@ module Bosh::Cli::Command
118
129
  say(tasks_table)
119
130
  say("\n")
120
131
  end
132
+
133
+ # Returns the verbose level for the given no_filter flag
134
+ # @param [Boolean] use_filter Is filtering performed?
135
+ # @return [Number] director verbose level
136
+ def get_verbose_level(use_filter)
137
+ use_filter ? 1 : 2
138
+ end
121
139
  end
122
140
  end
@@ -3,10 +3,13 @@
3
3
  module Bosh::Cli::Command
4
4
  class User < Base
5
5
 
6
+ # bosh create user
7
+ usage "create user"
8
+ desc "Create user"
6
9
  def create(username = nil, password = nil)
7
10
  auth_required
8
11
 
9
- unless options[:non_interactive]
12
+ if interactive?
10
13
  username = ask("Enter username: ") if username.blank?
11
14
  if password.blank?
12
15
  password = ask("Enter password: ") { |q| q.echo = "*" }
@@ -18,9 +21,9 @@ module Bosh::Cli::Command
18
21
  end
19
22
 
20
23
  if director.create_user(username, password)
21
- say("User #{username} has been created")
24
+ say("User `#{username}' has been created".green)
22
25
  else
23
- say("Error creating user")
26
+ err("Error creating user")
24
27
  end
25
28
  end
26
29
 
@@ -4,22 +4,22 @@ module Bosh::Cli::Command
4
4
  class Vms < Base
5
5
  include Bosh::Cli::DeploymentHelper
6
6
 
7
- def list(*args)
7
+ usage "vms"
8
+ desc "List all VMs that in a deployment"
9
+ option "--full", "Return detailed VM information"
10
+ def list(deployment_name = nil)
8
11
  auth_required
12
+ show_full_stats = options[:full]
9
13
 
10
- show_full_stats = !args.delete("--full").nil?
11
- name = args.first
12
-
13
- if name.nil?
14
+ if deployment_name.nil?
14
15
  deployment_required
15
16
  manifest = prepare_deployment_manifest
16
- name = manifest["name"]
17
+ deployment_name = manifest["name"]
17
18
  end
18
19
 
19
- say("Deployment #{name.green}")
20
-
21
- vms = director.fetch_vm_state(name)
22
- err("No VMs") if vms.size == 0
20
+ say("Deployment `#{deployment_name.green}'")
21
+ vms = director.fetch_vm_state(deployment_name)
22
+ err("No VMs") if vms.empty?
23
23
 
24
24
  sorted = vms.sort do |a, b|
25
25
  s = a["job_name"].to_s <=> b["job_name"].to_s
@@ -35,17 +35,19 @@ module Bosh::Cli::Command
35
35
  t.headings = headings
36
36
 
37
37
  sorted.each do |vm|
38
- job = "#{vm["job_name"]}/#{vm["index"]}" if vm["job_name"]
39
- row = [job, vm["job_state"],
40
- vm["resource_pool"], Array(vm["ips"]).join(", ")]
38
+ job = "#{vm["job_name"] || "unknown"}/#{vm["index"] || "unknown"}"
39
+ ips = Array(vm["ips"]).join(", ")
40
+
41
+ row = [job, vm["job_state"], vm["resource_pool"], ips]
41
42
  row += [vm["vm_cid"], vm["agent_id"]] if show_full_stats
43
+
42
44
  t << row
43
45
  end
44
46
  end
45
47
 
46
- say("\n")
48
+ nl
47
49
  say(vms_table)
48
- say("\n")
50
+ nl
49
51
  say("VMs total: %d" % vms.size)
50
52
  end
51
53
 
data/lib/cli/config.rb CHANGED
@@ -5,14 +5,40 @@ module Bosh::Cli
5
5
  VALID_ID = /^[-a-z0-9_.]+$/i
6
6
 
7
7
  class << self
8
+ # @return [Hash<String,Bosh::Cli::CommandDefinition>] Available commands
9
+ attr_reader :commands
10
+
11
+ # @return [Boolean] Should CLI output be colorized?
8
12
  attr_accessor :colorize
13
+
14
+ # @return [IO] Where output goes
9
15
  attr_accessor :output
16
+
17
+ # @return [Boolean] Is CLI being used interactively?
10
18
  attr_accessor :interactive
19
+
20
+ # @return [Bosh::Cli::Cache] CLI cache (to save task logs etc.)
11
21
  attr_accessor :cache
12
22
  end
13
23
 
24
+ @commands = {}
25
+ @colorize = true
26
+ @output = nil
27
+ @interactive = false
28
+ @cache = nil
29
+
30
+ # Register command with BOSH CLI
31
+ # @param [Bosh::Cli::CommandDefinition] command
32
+ # @return [void]
33
+ def self.register_command(command)
34
+ if @commands.has_key?(command.usage)
35
+ raise CliError, "Duplicate command `#{command.usage}'"
36
+ end
37
+ @commands[command.usage] = command
38
+ end
39
+
14
40
  def initialize(filename, work_dir = Dir.pwd)
15
- @filename = File.expand_path(filename)
41
+ @filename = File.expand_path(filename || Bosh::Cli::DEFAULT_CONFIG_PATH)
16
42
  @work_dir = work_dir
17
43
 
18
44
  unless File.exists?(@filename)
data/lib/cli/core_ext.rb CHANGED
@@ -27,7 +27,7 @@ module BoshExtensions
27
27
  end
28
28
 
29
29
  def err(message)
30
- raise Bosh::Cli::CliExit.new message
30
+ raise Bosh::Cli::CliError, message
31
31
  end
32
32
 
33
33
  def quit(message = nil)
@@ -85,6 +85,11 @@ module BoshExtensions
85
85
  file.write(yaml.gsub(" \n", "\n"))
86
86
  file.flush
87
87
  end
88
+
89
+ # @return [Fixnum]
90
+ def terminal_width
91
+ [HighLine::SystemExtensions.terminal_size[0], 120].min
92
+ end
88
93
  end
89
94
 
90
95
  module BoshStringExtensions
@@ -138,6 +143,27 @@ module BoshStringExtensions
138
143
  end
139
144
  end
140
145
 
146
+ def columnize(width = 80, left_margin = 0)
147
+ result = ""
148
+ buf = ""
149
+ self.split(/\s+/).each do |word|
150
+ if buf.size + word.size > width
151
+ result << buf << "\n" << " " * left_margin
152
+ buf = word + " "
153
+ else
154
+ buf << word << " "
155
+ end
156
+
157
+ end
158
+ result + buf
159
+ end
160
+
161
+ def indent(margin = 2)
162
+ self.split("\n").map { |line|
163
+ " " * margin + line
164
+ }.join("\n")
165
+ end
166
+
141
167
  end
142
168
 
143
169
  class Object
@@ -60,6 +60,7 @@ module Bosh::Cli
60
60
  end
61
61
 
62
62
  resolve_release_aliases(manifest)
63
+ resolve_stemcell_aliases(manifest)
63
64
 
64
65
  options[:yaml] ? YAML.dump(manifest) : manifest
65
66
  end
@@ -336,6 +337,52 @@ module Bosh::Cli
336
337
  end
337
338
  end
338
339
 
340
+ # @param [Hash] manifest Deployment manifest (will be modified)
341
+ # @return [void]
342
+ def resolve_stemcell_aliases(manifest)
343
+ return if manifest["resource_pools"].nil?
344
+
345
+ manifest["resource_pools"].each do |rp|
346
+ stemcell = rp["stemcell"]
347
+ unless stemcell.is_a?(Hash)
348
+ err("Invalid stemcell spec in the deployment manifest")
349
+ end
350
+ if stemcell["version"] == "latest"
351
+ latest_version = latest_stemcells[stemcell["name"]]
352
+ if latest_version.nil?
353
+ err("Latest version for stemcell `#{stemcell["name"]}' is unknown")
354
+ end
355
+ # Avoiding {Float,Fixnum} -> String noise in diff
356
+ if latest_version.to_s == latest_version.to_f.to_s
357
+ latest_version = latest_version.to_f
358
+ elsif latest_version.to_s == latest_version.to_i.to_s
359
+ latest_version = latest_version.to_i
360
+ end
361
+ stemcell["version"] = latest_version
362
+ end
363
+ end
364
+ end
365
+
366
+ # @return [Array]
367
+ def latest_stemcells
368
+ @_latest_stemcells ||= begin
369
+ stemcells = director.list_stemcells.inject({}) do |hash, stemcell|
370
+ unless stemcell.is_a?(Hash) && stemcell["name"] && stemcell["version"]
371
+ err("Invalid director stemcell list format")
372
+ end
373
+ hash[stemcell["name"]] ||= []
374
+ hash[stemcell["name"]] << stemcell["version"]
375
+ hash
376
+ end
377
+
378
+ stemcells.inject({}) do |hash, (name, versions)|
379
+ hash[name] = versions.sort { |v1, v2| version_cmp(v2, v1) }.first
380
+ hash
381
+ end
382
+ end
383
+ end
384
+
385
+ # @return [Array]
339
386
  def latest_releases
340
387
  @_latest_releases ||= begin
341
388
  director.list_releases.inject({}) do |hash, release|
data/lib/cli/director.rb CHANGED
@@ -12,6 +12,12 @@ module Bosh
12
12
 
13
13
  attr_reader :director_uri
14
14
 
15
+ # @return [String]
16
+ attr_accessor :user
17
+
18
+ # @return [String]
19
+ attr_accessor :password
20
+
15
21
  def initialize(director_uri, user = nil, password = nil)
16
22
  if director_uri.nil? || director_uri =~ /^\s*$/
17
23
  raise DirectorMissing, "no director URI given"
@@ -78,17 +84,18 @@ module Bosh
78
84
  get_json("/deployments")
79
85
  end
80
86
 
81
- def list_running_tasks
87
+ def list_running_tasks(verbose = 1)
82
88
  if version_less(get_version, "0.3.5")
83
89
  get_json("/tasks?state=processing")
84
90
  else
85
- get_json("/tasks?state=processing,cancelling,queued")
91
+ get_json("/tasks?state=processing,cancelling,queued" +
92
+ "&verbose=#{verbose}")
86
93
  end
87
94
  end
88
95
 
89
- def list_recent_tasks(count = 30)
96
+ def list_recent_tasks(count = 30, verbose = 1)
90
97
  count = [count.to_i, 100].min
91
- get_json("/tasks?limit=#{count}")
98
+ get_json("/tasks?limit=#{count}&verbose=#{verbose}")
92
99
  end
93
100
 
94
101
  def get_release(name)
@@ -123,6 +130,12 @@ module Bosh
123
130
  upload_and_track(:post, "/releases", filename, options)
124
131
  end
125
132
 
133
+ def rebase_release(filename, options = {})
134
+ options = options.dup
135
+ options[:content_type] = "application/x-compressed"
136
+ upload_and_track(:post, "/releases?rebase=true", filename, options)
137
+ end
138
+
126
139
  def delete_stemcell(name, version, options = {})
127
140
  options = options.dup
128
141
  request_and_track(:delete, "/stemcells/#{name}/#{version}", options)
@@ -192,11 +205,7 @@ module Bosh
192
205
  options[:payload] = JSON.generate(payload)
193
206
  options[:content_type] = "application/json"
194
207
 
195
- status, task_id = request_and_track(:post, url, options)
196
-
197
- # TODO: this needs to be done in command handler, not in director.rb
198
- return nil if status != :done
199
- JSON.parse(get_task_result_log(task_id))
208
+ request_and_track(:post, url, options)
200
209
  end
201
210
 
202
211
  def cleanup_ssh(deployment_name, job, user_regex, indexes, options = {})