bosh_cli 0.19.6 → 1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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 = {})