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.
- data/bin/bosh +3 -0
- data/lib/cli.rb +15 -5
- data/lib/cli/{commands/base.rb → base_command.rb} +38 -44
- data/lib/cli/command_discovery.rb +40 -0
- data/lib/cli/command_handler.rb +135 -0
- data/lib/cli/commands/biff.rb +16 -12
- data/lib/cli/commands/blob_management.rb +10 -3
- data/lib/cli/commands/cloudcheck.rb +13 -11
- data/lib/cli/commands/complete.rb +29 -0
- data/lib/cli/commands/deployment.rb +137 -28
- data/lib/cli/commands/help.rb +96 -0
- data/lib/cli/commands/job.rb +4 -1
- data/lib/cli/commands/job_management.rb +36 -23
- data/lib/cli/commands/job_rename.rb +11 -12
- data/lib/cli/commands/log_management.rb +28 -32
- data/lib/cli/commands/maintenance.rb +6 -1
- data/lib/cli/commands/misc.rb +129 -87
- data/lib/cli/commands/package.rb +6 -65
- data/lib/cli/commands/property_management.rb +20 -8
- data/lib/cli/commands/release.rb +211 -206
- data/lib/cli/commands/ssh.rb +178 -188
- data/lib/cli/commands/stemcell.rb +114 -51
- data/lib/cli/commands/task.rb +74 -56
- data/lib/cli/commands/user.rb +6 -3
- data/lib/cli/commands/vms.rb +17 -15
- data/lib/cli/config.rb +27 -1
- data/lib/cli/core_ext.rb +27 -1
- data/lib/cli/deployment_helper.rb +47 -0
- data/lib/cli/director.rb +18 -9
- data/lib/cli/errors.rb +6 -0
- data/lib/cli/job_builder.rb +75 -23
- data/lib/cli/job_property_collection.rb +87 -0
- data/lib/cli/job_property_validator.rb +130 -0
- data/lib/cli/package_builder.rb +32 -5
- data/lib/cli/release.rb +2 -0
- data/lib/cli/release_builder.rb +9 -13
- data/lib/cli/release_compiler.rb +5 -34
- data/lib/cli/release_tarball.rb +4 -19
- data/lib/cli/runner.rb +118 -694
- data/lib/cli/version.rb +1 -1
- data/spec/assets/config/swift-hp/config/final.yml +6 -0
- data/spec/assets/config/swift-hp/config/private.yml +7 -0
- data/spec/assets/config/swift-rackspace/config/final.yml +6 -0
- data/spec/assets/config/swift-rackspace/config/private.yml +6 -0
- data/spec/spec_helper.rb +0 -5
- data/spec/unit/base_command_spec.rb +32 -37
- data/spec/unit/biff_spec.rb +11 -10
- data/spec/unit/cli_commands_spec.rb +96 -88
- data/spec/unit/core_ext_spec.rb +1 -1
- data/spec/unit/deployment_manifest_spec.rb +36 -0
- data/spec/unit/director_spec.rb +17 -3
- data/spec/unit/job_builder_spec.rb +2 -2
- data/spec/unit/job_property_collection_spec.rb +111 -0
- data/spec/unit/job_property_validator_spec.rb +7 -0
- data/spec/unit/job_rename_spec.rb +7 -6
- data/spec/unit/package_builder_spec.rb +2 -2
- data/spec/unit/release_builder_spec.rb +33 -0
- data/spec/unit/release_spec.rb +54 -0
- data/spec/unit/release_tarball_spec.rb +2 -7
- data/spec/unit/runner_spec.rb +1 -151
- data/spec/unit/ssh_spec.rb +15 -9
- metadata +41 -12
- data/lib/cli/command_definition.rb +0 -52
- data/lib/cli/templates/help_message.erb +0 -80
data/lib/cli/release.rb
CHANGED
@@ -64,6 +64,8 @@ module Bosh::Cli
|
|
64
64
|
has_legacy_secret? ||
|
65
65
|
has_blobstore_secrets?(bs, "atmos", "secret") ||
|
66
66
|
has_blobstore_secrets?(bs, "simple", "user", "password") ||
|
67
|
+
has_blobstore_secrets?(bs, "swift", "rackspace") ||
|
68
|
+
has_blobstore_secrets?(bs, "swift", "hp") ||
|
67
69
|
has_blobstore_secrets?(bs, "s3", "access_key_id", "secret_access_key")
|
68
70
|
end
|
69
71
|
|
data/lib/cli/release_builder.rb
CHANGED
@@ -7,7 +7,7 @@ module Bosh::Cli
|
|
7
7
|
|
8
8
|
DEFAULT_RELEASE_NAME = "bosh_release"
|
9
9
|
|
10
|
-
attr_reader :release, :packages, :jobs, :version
|
10
|
+
attr_reader :release, :packages, :jobs, :version, :build_dir
|
11
11
|
|
12
12
|
# @param [Bosh::Cli::Release] release Current release
|
13
13
|
# @param [Array<Bosh::Cli::PackageBuilder>] packages Built packages
|
@@ -23,7 +23,12 @@ module Bosh::Cli
|
|
23
23
|
@dev_index = VersionsIndex.new(dev_releases_dir, release_name)
|
24
24
|
@index = @final ? @final_index : @dev_index
|
25
25
|
|
26
|
-
|
26
|
+
@build_dir = Dir.mktmpdir
|
27
|
+
|
28
|
+
in_build_dir do
|
29
|
+
FileUtils.mkdir("packages")
|
30
|
+
FileUtils.mkdir("jobs")
|
31
|
+
end
|
27
32
|
end
|
28
33
|
|
29
34
|
# @return [String] Release name
|
@@ -107,6 +112,7 @@ module Bosh::Cli
|
|
107
112
|
"name" => package.name,
|
108
113
|
"version" => package.version,
|
109
114
|
"sha1" => package.checksum,
|
115
|
+
"fingerprint" => package.fingerprint,
|
110
116
|
"dependencies" => package.dependencies
|
111
117
|
}
|
112
118
|
end
|
@@ -115,6 +121,7 @@ module Bosh::Cli
|
|
115
121
|
{
|
116
122
|
"name" => job.name,
|
117
123
|
"version" => job.version,
|
124
|
+
"fingerprint" => job.fingerprint,
|
118
125
|
"sha1" => job.checksum,
|
119
126
|
}
|
120
127
|
end
|
@@ -229,17 +236,6 @@ module Bosh::Cli
|
|
229
236
|
end
|
230
237
|
end
|
231
238
|
|
232
|
-
def build_dir
|
233
|
-
@build_dir ||= Dir.mktmpdir
|
234
|
-
end
|
235
|
-
|
236
|
-
def create_release_build_dir
|
237
|
-
in_build_dir do
|
238
|
-
FileUtils.mkdir("packages")
|
239
|
-
FileUtils.mkdir("jobs")
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
239
|
def in_build_dir(&block)
|
244
240
|
Dir.chdir(build_dir) { yield }
|
245
241
|
end
|
data/lib/cli/release_compiler.rb
CHANGED
@@ -12,11 +12,10 @@ module Bosh::Cli
|
|
12
12
|
|
13
13
|
# @param [String] manifest_file Release manifest path
|
14
14
|
# @param [Bosh::Blobstore::Client] blobstore Blobstore client
|
15
|
-
# @param [Hash] remote_release Remote release info from director
|
16
15
|
# @param [Array] package_matches List of package checksums that director
|
17
16
|
# can match
|
18
17
|
# @param [String] release_dir Release directory
|
19
|
-
def initialize(manifest_file, blobstore,
|
18
|
+
def initialize(manifest_file, blobstore,
|
20
19
|
package_matches = [], release_dir = nil)
|
21
20
|
|
22
21
|
@blobstore = blobstore
|
@@ -30,28 +29,11 @@ module Bosh::Cli
|
|
30
29
|
|
31
30
|
@package_matches = Set.new(package_matches)
|
32
31
|
|
33
|
-
at_exit { FileUtils.rm_rf(@build_dir) }
|
34
|
-
|
35
32
|
FileUtils.mkdir_p(@jobs_dir)
|
36
33
|
FileUtils.mkdir_p(@packages_dir)
|
37
34
|
|
38
35
|
@manifest = load_yaml_file(manifest_file)
|
39
36
|
|
40
|
-
if remote_release
|
41
|
-
# TODO: instead of OpenStruct conversion we should probably
|
42
|
-
# introduce proper abstractions for things below
|
43
|
-
@remote_packages = remote_release["packages"].map do |pkg|
|
44
|
-
OpenStruct.new(pkg)
|
45
|
-
end
|
46
|
-
|
47
|
-
@remote_jobs = remote_release["jobs"].map do |job|
|
48
|
-
OpenStruct.new(job)
|
49
|
-
end
|
50
|
-
else
|
51
|
-
@remote_packages = []
|
52
|
-
@remote_jobs = []
|
53
|
-
end
|
54
|
-
|
55
37
|
@name = @manifest["name"]
|
56
38
|
@version = @manifest["version"]
|
57
39
|
@packages = @manifest["packages"].map { |pkg| OpenStruct.new(pkg) }
|
@@ -185,28 +167,17 @@ module Bosh::Cli
|
|
185
167
|
# @return [Boolean]
|
186
168
|
def remote_package_exists?(local_package)
|
187
169
|
# If checksum is known to director we can always match it
|
188
|
-
|
189
|
-
|
190
|
-
|
170
|
+
@package_matches.include?(local_package.sha1) ||
|
171
|
+
(local_package.fingerprint &&
|
172
|
+
@package_matches.include?(local_package.fingerprint))
|
191
173
|
end
|
192
174
|
|
193
175
|
# Checks if local job is already known remotely
|
194
176
|
# @param [#name, #version] local_job
|
195
177
|
# @return [Boolean]
|
196
178
|
def remote_job_exists?(local_job)
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
# @param [Enumerable] remote_objects Remote object collection
|
201
|
-
# @param [#name, #version] local_object
|
202
|
-
# @return [Boolean]
|
203
|
-
def remote_object_exists?(remote_objects, local_object)
|
204
|
-
remote_objects.any? do |remote_object|
|
205
|
-
remote_object.name == local_object.name &&
|
206
|
-
remote_object.version.to_s == local_object.version.to_s
|
207
|
-
end
|
179
|
+
false
|
208
180
|
end
|
209
|
-
|
210
181
|
end
|
211
182
|
|
212
183
|
end
|
data/lib/cli/release_tarball.rb
CHANGED
@@ -32,25 +32,17 @@ module Bosh::Cli
|
|
32
32
|
|
33
33
|
# Repacks tarball according to the structure of remote release
|
34
34
|
# Return path to repackaged tarball or nil if repack has failed
|
35
|
-
def repack(
|
35
|
+
def repack(package_matches = [])
|
36
36
|
return nil unless valid?
|
37
37
|
unpack
|
38
38
|
|
39
39
|
tmpdir = Dir.mktmpdir
|
40
40
|
repacked_path = File.join(tmpdir, "release-repack.tgz")
|
41
41
|
|
42
|
-
at_exit { FileUtils.rm_rf(tmpdir) }
|
43
|
-
|
44
42
|
manifest = load_yaml_file(File.join(@unpack_dir, "release.MF"))
|
45
43
|
|
46
|
-
# Remote release could be not-existent, then package matches are supposed
|
47
|
-
# to satisfy everything
|
48
|
-
remote_release ||= {"jobs" => [], "packages" => []}
|
49
|
-
|
50
44
|
local_packages = manifest["packages"]
|
51
45
|
local_jobs = manifest["jobs"]
|
52
|
-
remote_packages = remote_release["packages"]
|
53
|
-
remote_jobs = remote_release["jobs"]
|
54
46
|
|
55
47
|
@skipped = 0
|
56
48
|
|
@@ -61,8 +53,8 @@ module Bosh::Cli
|
|
61
53
|
local_packages.each do |package|
|
62
54
|
say("#{package["name"]} (#{package["version"]})".ljust(30), " ")
|
63
55
|
if package_matches.include?(package["sha1"]) ||
|
64
|
-
|
65
|
-
package["
|
56
|
+
(package["fingerprint"] &&
|
57
|
+
package_matches.include?(package["fingerprint"]))
|
66
58
|
say("SKIP".green)
|
67
59
|
@skipped += 1
|
68
60
|
FileUtils.rm_rf(File.join("packages", "#{package["name"]}.tgz"))
|
@@ -73,14 +65,7 @@ module Bosh::Cli
|
|
73
65
|
|
74
66
|
local_jobs.each do |job|
|
75
67
|
say("#{job["name"]} (#{job["version"]})".ljust(30), " ")
|
76
|
-
|
77
|
-
job["version"].to_s == rj["version"].to_s }
|
78
|
-
say("SKIP".green)
|
79
|
-
@skipped += 1
|
80
|
-
FileUtils.rm_rf(File.join("jobs", "#{job["name"]}.tgz"))
|
81
|
-
else
|
82
|
-
say("UPLOAD".red)
|
83
|
-
end
|
68
|
+
say("UPLOAD".red)
|
84
69
|
end
|
85
70
|
|
86
71
|
return nil if @skipped == 0
|
data/lib/cli/runner.rb
CHANGED
@@ -6,667 +6,186 @@ module Bosh::Cli
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class Runner
|
9
|
-
COMMANDS = { }
|
10
|
-
ALL_KEYWORDS = []
|
11
9
|
|
12
|
-
|
13
|
-
attr_reader :namespace
|
14
|
-
attr_reader :action
|
10
|
+
# @return [Array]
|
15
11
|
attr_reader :args
|
16
|
-
attr_reader :options
|
17
12
|
|
18
|
-
#
|
19
|
-
|
20
|
-
# @return [Bosh::Cli::Command::<type>] Instance of the command instance.
|
21
|
-
attr_accessor :runner
|
13
|
+
# @return [Hash]
|
14
|
+
attr_reader :options
|
22
15
|
|
16
|
+
# @param [Array] args
|
23
17
|
def self.run(args)
|
24
18
|
new(args).run
|
25
19
|
end
|
26
20
|
|
27
|
-
|
21
|
+
# @param [Array] args
|
22
|
+
def initialize(args, options = {})
|
28
23
|
@args = args
|
29
|
-
@options =
|
30
|
-
:director_checks => true,
|
31
|
-
:colorize => true,
|
32
|
-
}
|
33
|
-
end
|
24
|
+
@options = options.dup
|
34
25
|
|
35
|
-
|
36
|
-
|
37
|
-
parse_options!
|
38
|
-
Config.output ||= STDOUT unless @options[:quiet]
|
39
|
-
Config.interactive = !@options[:non_interactive]
|
40
|
-
Config.colorize = @options.delete(:colorize)
|
41
|
-
Config.cache = Bosh::Cli::Cache.new(@options[:cache_dir] ||
|
42
|
-
Bosh::Cli::DEFAULT_CACHE_DIR)
|
26
|
+
banner = "Usage: bosh [<options>] <command> [<args>]"
|
27
|
+
@option_parser = OptionParser.new(banner)
|
43
28
|
|
44
|
-
|
45
|
-
|
46
|
-
add_shortcuts
|
29
|
+
Config.colorize = true
|
30
|
+
Config.output ||= STDOUT
|
47
31
|
end
|
48
32
|
|
33
|
+
# Find and run CLI command
|
34
|
+
# @return [void]
|
49
35
|
def run
|
50
|
-
|
51
|
-
dispatch unless @namespace && @action
|
52
|
-
|
53
|
-
if @namespace && @action
|
54
|
-
ns_class_name = @namespace.to_s.gsub(/(?:_|^)(.)/) { $1.upcase }
|
55
|
-
klass = eval("Bosh::Cli::Command::#{ns_class_name}")
|
56
|
-
runner = klass.new(@options)
|
57
|
-
runner.usage = @usage
|
36
|
+
parse_global_options
|
58
37
|
|
59
|
-
|
60
|
-
|
38
|
+
Config.interactive = !@options[:non_interactive]
|
39
|
+
Config.cache = Bosh::Cli::Cache.new(@options[:cache_dir])
|
61
40
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if action_arity >= 0 && n_required_args < @args.size
|
66
|
-
err("Too many arguments, correct usage is: bosh #{@usage}")
|
67
|
-
end
|
41
|
+
build_parse_tree
|
42
|
+
load_plugins
|
43
|
+
add_shortcuts
|
68
44
|
|
69
|
-
|
70
|
-
|
71
|
-
elsif @args.empty? || @args == %w(help)
|
72
|
-
say(help_message)
|
73
|
-
say(plugin_help_message) if @plugins
|
74
|
-
elsif @args[0] == "complete"
|
75
|
-
unless ENV.has_key?('COMP_LINE')
|
76
|
-
$stderr.puts "COMP_LINE must be set when calling bosh complete"
|
77
|
-
exit(1)
|
78
|
-
end
|
79
|
-
line = ENV['COMP_LINE'].gsub(/^\S*bosh\s*/, '')
|
80
|
-
puts complete(line).join("\n")
|
45
|
+
if @args.empty?
|
46
|
+
say(usage)
|
81
47
|
exit(0)
|
82
|
-
|
83
|
-
cmd_args = @args[1..-1]
|
84
|
-
suggestions = command_suggestions(cmd_args).map do |cmd|
|
85
|
-
command_usage(cmd, 0)
|
86
|
-
end
|
87
|
-
if suggestions.empty?
|
88
|
-
unknown_command(cmd_args.join(" "))
|
89
|
-
else
|
90
|
-
say(suggestions.uniq.join("\n"))
|
91
|
-
end
|
92
|
-
else
|
93
|
-
unknown_command(@args.join(" "))
|
48
|
+
end
|
94
49
|
|
95
|
-
|
96
|
-
|
97
|
-
|
50
|
+
command = search_parse_tree(@parse_tree)
|
51
|
+
if command.nil? && Config.interactive
|
52
|
+
command = try_alias
|
53
|
+
end
|
54
|
+
|
55
|
+
if command.nil?
|
56
|
+
err("Unknown command: #{@args.join(" ")}")
|
57
|
+
end
|
98
58
|
|
99
|
-
|
100
|
-
|
101
|
-
|
59
|
+
command.runner = self
|
60
|
+
begin
|
61
|
+
exit_code = command.run(@args, @options)
|
62
|
+
exit(exit_code)
|
63
|
+
rescue OptionParser::ParseError => e
|
64
|
+
say(e.message.red)
|
65
|
+
say("Usage: bosh #{command.usage_with_params.columnize(60, 7)}")
|
66
|
+
if command.has_options?
|
67
|
+
say(command.options_summary.indent(7))
|
102
68
|
end
|
103
|
-
exit(1)
|
104
69
|
end
|
105
70
|
|
106
|
-
rescue OptionParser::
|
107
|
-
say(e.message.red + "\n" + basic_usage)
|
108
|
-
exit(1)
|
109
|
-
rescue Bosh::Cli::GracefulExit => e
|
110
|
-
# Redirected bosh commands end up
|
111
|
-
# generating this exception (kind of goto)
|
112
|
-
rescue Bosh::Cli::CliExit, Bosh::Cli::DirectorError => e
|
71
|
+
rescue OptionParser::ParseError => e
|
113
72
|
say(e.message.red)
|
114
|
-
|
73
|
+
say(@option_parser.to_s)
|
74
|
+
exit(1)
|
115
75
|
rescue Bosh::Cli::CliError => e
|
116
|
-
say(
|
76
|
+
say(e.message.red)
|
117
77
|
exit(e.exit_code)
|
118
|
-
rescue => e
|
119
|
-
if @options[:debug]
|
120
|
-
raise e
|
121
|
-
else
|
122
|
-
save_exception(e)
|
123
|
-
exit(1)
|
124
|
-
end
|
125
78
|
end
|
126
79
|
|
127
|
-
#
|
128
|
-
|
80
|
+
# Finds command completions in the parse tree
|
81
|
+
# @param [Array] words Completion prefix
|
82
|
+
# @param [Bosh::Cli::ParseTreeNode] node Current parse tree node
|
83
|
+
def find_completions(words, node = @parse_tree, index = 0)
|
129
84
|
word = words[index]
|
130
85
|
|
131
86
|
# exact match and not on the last word
|
132
87
|
if node[word] && words.length != index
|
133
|
-
|
88
|
+
find_completions(words, node[word], index + 1)
|
134
89
|
|
135
|
-
|
90
|
+
# exact match at the last word
|
136
91
|
elsif node[word]
|
137
92
|
node[word].values
|
138
93
|
|
139
|
-
|
94
|
+
# find all partial matches
|
140
95
|
else
|
141
96
|
node.keys.grep(/^#{word}/)
|
142
97
|
end
|
143
98
|
end
|
144
99
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
parse_tree_completion(@parse_tree, words, 0)
|
152
|
-
end
|
153
|
-
|
154
|
-
def command(name, &block)
|
155
|
-
cmd_def = CommandDefinition.new
|
156
|
-
cmd_def.instance_eval(&block)
|
157
|
-
COMMANDS[name] = cmd_def
|
158
|
-
ALL_KEYWORDS.push(*cmd_def.keywords)
|
159
|
-
end
|
160
|
-
|
161
|
-
def find_command(name)
|
162
|
-
COMMANDS[name] || raise("Unknown command definition: #{name}")
|
163
|
-
end
|
164
|
-
|
165
|
-
def dispatch(command = nil)
|
166
|
-
command ||= search_parse_tree(@parse_tree)
|
167
|
-
command = try_alias if command.nil? && Config.interactive
|
168
|
-
return if command.nil?
|
169
|
-
@usage = command.usage
|
170
|
-
|
171
|
-
case command.route
|
172
|
-
when Array
|
173
|
-
@namespace, @action = command.route
|
174
|
-
when Proc
|
175
|
-
@namespace, @action = command.route.call(@args)
|
176
|
-
else
|
177
|
-
raise "Command definition is invalid, " +
|
178
|
-
"route should be an Array or Proc"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
def define_commands
|
183
|
-
command :version do
|
184
|
-
usage "version"
|
185
|
-
desc "Show version"
|
186
|
-
route :misc, :version
|
187
|
-
end
|
188
|
-
|
189
|
-
command :alias do
|
190
|
-
usage "alias <name> <command>"
|
191
|
-
desc "Create an alias <name> for command <command>"
|
192
|
-
route :misc, :set_alias
|
193
|
-
end
|
194
|
-
|
195
|
-
command :list_aliases do
|
196
|
-
usage "aliases"
|
197
|
-
desc "Show the list of available command aliases"
|
198
|
-
route :misc, :list_aliases
|
199
|
-
end
|
200
|
-
|
201
|
-
command :target do
|
202
|
-
usage "target [<name>] [<alias>]"
|
203
|
-
desc "Choose director to talk to (optionally creating an alias). " +
|
204
|
-
"If no arguments given, show currently targeted director"
|
205
|
-
route do |args|
|
206
|
-
(args.size > 0) ? [:misc, :set_target] : [:misc, :show_target]
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
command :list_targets do
|
211
|
-
usage "targets"
|
212
|
-
desc "Show the list of available targets"
|
213
|
-
route :misc, :list_targets
|
214
|
-
end
|
215
|
-
|
216
|
-
command :deployment do
|
217
|
-
usage "deployment [<name>]"
|
218
|
-
desc "Choose deployment to work with " +
|
219
|
-
"(it also updates current target)"
|
220
|
-
route do |args|
|
221
|
-
if args.size > 0
|
222
|
-
[:deployment, :set_current]
|
223
|
-
else
|
224
|
-
[:deployment, :show_current]
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
command :deploy do
|
230
|
-
usage "deploy"
|
231
|
-
desc "Deploy according to the currently selected " +
|
232
|
-
"deployment manifest"
|
233
|
-
option "--recreate", "recreate all VMs in deployment"
|
234
|
-
route :deployment, :perform
|
235
|
-
end
|
236
|
-
|
237
|
-
command :edit_deployment do
|
238
|
-
usage "edit deployment"
|
239
|
-
desc "Edit current deployment manifest"
|
240
|
-
route :deployment, :edit
|
241
|
-
end
|
242
|
-
|
243
|
-
command :ssh do
|
244
|
-
usage "ssh <job> [index] [<options>] [command]"
|
245
|
-
desc "Given a job, execute the given command or " +
|
246
|
-
"start an interactive session"
|
247
|
-
option "--public_key <file>"
|
248
|
-
option "--gateway_host <host>"
|
249
|
-
option "--gateway_user <user>"
|
250
|
-
option "--default_password", "Use default ssh password. Not recommended."
|
251
|
-
route :ssh, :shell
|
252
|
-
end
|
253
|
-
|
254
|
-
command :ssh_cleanup do
|
255
|
-
usage "ssh_cleanup <job> [index]"
|
256
|
-
desc "Cleanup SSH artifacts"
|
257
|
-
route :ssh, :cleanup
|
258
|
-
end
|
259
|
-
|
260
|
-
command :scp do
|
261
|
-
usage "scp <job> [index] (--upload|--download) [options]" +
|
262
|
-
"/path/to/source /path/to/destination"
|
263
|
-
desc "upload/download the source file to the given job. " +
|
264
|
-
"Note: for dowload /path/to/destination is a directory"
|
265
|
-
option "--public_key <file>"
|
266
|
-
option "--gateway_host <host>"
|
267
|
-
option "--gateway_user <user>"
|
268
|
-
route :ssh, :scp
|
269
|
-
end
|
270
|
-
|
271
|
-
command :scp do
|
272
|
-
usage "scp <job> <--upload | --download> [options] " +
|
273
|
-
"/path/to/source /path/to/destination"
|
274
|
-
desc "upload/download the source file to the given job. " +
|
275
|
-
"Note: for download /path/to/destination is a directory"
|
276
|
-
option "--index <job_index>"
|
277
|
-
option "--public_key <file>"
|
278
|
-
option "--gateway_host <host>"
|
279
|
-
option "--gateway_user <user>"
|
280
|
-
route :ssh, :scp
|
281
|
-
end
|
282
|
-
|
283
|
-
command :status do
|
284
|
-
usage "status"
|
285
|
-
desc "Show current status (current target, " +
|
286
|
-
"user, deployment info etc.)"
|
287
|
-
route :misc, :status
|
288
|
-
end
|
289
|
-
|
290
|
-
command :login do
|
291
|
-
usage "login [<name>] [<password>]"
|
292
|
-
desc "Provide credentials for the subsequent interactions " +
|
293
|
-
"with targeted director"
|
294
|
-
route :misc, :login
|
295
|
-
end
|
296
|
-
|
297
|
-
command :logout do
|
298
|
-
usage "logout"
|
299
|
-
desc "Forget saved credentials for targeted director"
|
300
|
-
route :misc, :logout
|
301
|
-
end
|
302
|
-
|
303
|
-
command :purge do
|
304
|
-
usage "purge"
|
305
|
-
desc "Purge local manifest cache"
|
306
|
-
route :misc, :purge_cache
|
307
|
-
end
|
308
|
-
|
309
|
-
command :create_release do
|
310
|
-
usage "create release"
|
311
|
-
desc "Create release (assumes current directory " +
|
312
|
-
"to be a release repository)"
|
313
|
-
route :release, :create
|
314
|
-
option "--force", "bypass git dirty state check"
|
315
|
-
option "--final", "create production-ready release " +
|
316
|
-
"(stores artefacts in blobstore, bumps final version)"
|
317
|
-
option "--with-tarball", "create full release tarball" +
|
318
|
-
"(by default only manifest is created)"
|
319
|
-
option "--dry-run", "stop before writing release " +
|
320
|
-
"manifest (for diagnostics)"
|
321
|
-
end
|
322
|
-
|
323
|
-
command :create_user do
|
324
|
-
usage "create user [<name>] [<password>]"
|
325
|
-
desc "Create user"
|
326
|
-
route :user, :create
|
327
|
-
end
|
328
|
-
|
329
|
-
command :create_package do
|
330
|
-
usage "create package <name>|<path>"
|
331
|
-
desc "Build a single package"
|
332
|
-
route :package, :create
|
333
|
-
end
|
334
|
-
|
335
|
-
command :start_job do
|
336
|
-
usage "start <job> [<index>]"
|
337
|
-
desc "Start job/instance"
|
338
|
-
route :job_management, :start_job
|
339
|
-
|
340
|
-
power_option "--force"
|
341
|
-
end
|
342
|
-
|
343
|
-
command :stop_job do
|
344
|
-
usage "stop <job> [<index>]"
|
345
|
-
desc "Stop job/instance"
|
346
|
-
route :job_management, :stop_job
|
347
|
-
option "--soft", "stop process only"
|
348
|
-
option "--hard", "power off VM"
|
349
|
-
|
350
|
-
power_option "--force"
|
351
|
-
end
|
352
|
-
|
353
|
-
command :restart_job do
|
354
|
-
usage "restart <job> [<index>]"
|
355
|
-
desc "Restart job/instance (soft stop + start)"
|
356
|
-
route :job_management, :restart_job
|
357
|
-
|
358
|
-
power_option "--force"
|
359
|
-
end
|
360
|
-
|
361
|
-
command :recreate_job do
|
362
|
-
usage "recreate <job> [<index>]"
|
363
|
-
desc "Recreate job/instance (hard stop + start)"
|
364
|
-
route :job_management, :recreate_job
|
365
|
-
|
366
|
-
power_option "--force"
|
367
|
-
end
|
368
|
-
|
369
|
-
command :rename_job do
|
370
|
-
usage "rename <old_job_name> <new_job_name>"
|
371
|
-
desc "renames a job. NOTE, your deployment manifest must also be " +
|
372
|
-
"updated to reflect the new job name."
|
373
|
-
power_option "--force"
|
374
|
-
|
375
|
-
route :job_rename, :rename
|
376
|
-
end
|
377
|
-
|
378
|
-
command :fetch_logs do
|
379
|
-
usage "logs <job> <index>"
|
380
|
-
desc "Fetch job (default) or agent (if option provided) logs"
|
381
|
-
route :log_management, :fetch_logs
|
382
|
-
option "--agent", "fetch agent logs"
|
383
|
-
option "--only <filter1>[...]", "only fetch logs that satisfy " +
|
384
|
-
"given filters (defined in job spec)"
|
385
|
-
option "--all", "fetch all files in the job or agent log directory"
|
386
|
-
end
|
387
|
-
|
388
|
-
command :set_property do
|
389
|
-
usage "set property <name> <value>"
|
390
|
-
desc "Set deployment property"
|
391
|
-
route :property_management, :set
|
392
|
-
end
|
393
|
-
|
394
|
-
command :get_property do
|
395
|
-
usage "get property <name>"
|
396
|
-
desc "Get deployment property"
|
397
|
-
route :property_management, :get
|
398
|
-
end
|
399
|
-
|
400
|
-
command :unset_property do
|
401
|
-
usage "unset property <name>"
|
402
|
-
desc "Unset deployment property"
|
403
|
-
route :property_management, :unset
|
404
|
-
end
|
405
|
-
|
406
|
-
command :list_properties do
|
407
|
-
usage "properties"
|
408
|
-
desc "List current deployment properties"
|
409
|
-
route :property_management, :list
|
410
|
-
option "--terse", "easy to parse output"
|
411
|
-
end
|
412
|
-
|
413
|
-
command :init_release do
|
414
|
-
usage "init release [<path>]"
|
415
|
-
desc "Initialize release directory"
|
416
|
-
route :release, :init
|
417
|
-
option "--git", "initialize git repository"
|
418
|
-
end
|
419
|
-
|
420
|
-
command :generate_package do
|
421
|
-
usage "generate package <name>"
|
422
|
-
desc "Generate package template"
|
423
|
-
route :package, :generate
|
424
|
-
end
|
425
|
-
|
426
|
-
command :generate_job do
|
427
|
-
usage "generate job <name>"
|
428
|
-
desc "Generate job template"
|
429
|
-
route :job, :generate
|
430
|
-
end
|
431
|
-
|
432
|
-
command :upload_stemcell do
|
433
|
-
usage "upload stemcell <path>"
|
434
|
-
desc "Upload the stemcell"
|
435
|
-
route :stemcell, :upload
|
436
|
-
end
|
437
|
-
|
438
|
-
command :upload_release do
|
439
|
-
usage "upload release [<path>]"
|
440
|
-
desc "Upload release (<path> can point to tarball or manifest, " +
|
441
|
-
"defaults to the most recently created release)"
|
442
|
-
route :release, :upload
|
443
|
-
end
|
444
|
-
|
445
|
-
command :verify_stemcell do
|
446
|
-
usage "verify stemcell <path>"
|
447
|
-
desc "Verify stemcell"
|
448
|
-
route :stemcell, :verify
|
449
|
-
end
|
450
|
-
|
451
|
-
command :verify_release do
|
452
|
-
usage "verify release <path>"
|
453
|
-
desc "Verify release"
|
454
|
-
route :release, :verify
|
455
|
-
end
|
456
|
-
|
457
|
-
command :delete_deployment do
|
458
|
-
usage "delete deployment <name>"
|
459
|
-
desc "Delete deployment"
|
460
|
-
route :deployment, :delete
|
461
|
-
option "--force", "ignore all errors while deleting parts " +
|
462
|
-
"of the deployment"
|
463
|
-
end
|
464
|
-
|
465
|
-
command :delete_stemcell do
|
466
|
-
usage "delete stemcell <name> <version>"
|
467
|
-
desc "Delete the stemcell"
|
468
|
-
route :stemcell, :delete
|
469
|
-
end
|
470
|
-
|
471
|
-
command :delete_release do
|
472
|
-
usage "delete release <name> [<version>]"
|
473
|
-
desc "Delete release (or a particular release version)"
|
474
|
-
route :release, :delete
|
475
|
-
option "--force", "ignore errors during deletion"
|
476
|
-
end
|
477
|
-
|
478
|
-
command :reset_release do
|
479
|
-
usage "reset release"
|
480
|
-
desc "Reset release development environment " +
|
481
|
-
"(deletes all dev artifacts)"
|
482
|
-
route :release, :reset
|
483
|
-
end
|
484
|
-
|
485
|
-
command :cancel_task do
|
486
|
-
usage "cancel task <id>"
|
487
|
-
desc "Cancel task once it reaches the next cancel checkpoint"
|
488
|
-
route :task, :cancel
|
489
|
-
end
|
490
|
-
|
491
|
-
command :track_task do
|
492
|
-
usage "task [<task_id>|last]"
|
493
|
-
desc "Show task status and start tracking its output"
|
494
|
-
route :task, :track
|
495
|
-
option "--no-cache", "don't cache output locally"
|
496
|
-
option "--event|--soap|--debug", "different log types to track"
|
497
|
-
option "--raw", "don't beautify log"
|
498
|
-
end
|
499
|
-
|
500
|
-
command :list_stemcells do
|
501
|
-
usage "stemcells"
|
502
|
-
desc "Show the list of available stemcells"
|
503
|
-
route :stemcell, :list
|
504
|
-
end
|
505
|
-
|
506
|
-
command :list_public_stemcells do
|
507
|
-
usage "public stemcells"
|
508
|
-
desc "Show the list of publicly available stemcells for download."
|
509
|
-
route :stemcell, :list_public
|
510
|
-
option "--full", "show the full download url"
|
100
|
+
def parse_global_options
|
101
|
+
# -v is reserved for verbose but having 'bosh -v' is handy,
|
102
|
+
# hence the little hack
|
103
|
+
if @args.size == 1 && (@args[0] == "-v" || @args[0] == "--version")
|
104
|
+
@args = %w(version)
|
105
|
+
return
|
511
106
|
end
|
512
107
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
route :stemcell, :download_public
|
108
|
+
opts = @option_parser
|
109
|
+
opts.on("-c", "--config FILE", "Override configuration file") do |file|
|
110
|
+
@options[:config] = file
|
517
111
|
end
|
518
|
-
|
519
|
-
|
520
|
-
usage "releases"
|
521
|
-
desc "Show the list of available releases"
|
522
|
-
route :release, :list
|
112
|
+
opts.on("-C", "--cache-dir DIR", "Override cache directory") do |dir|
|
113
|
+
@options[:cache_dir] = dir
|
523
114
|
end
|
524
|
-
|
525
|
-
|
526
|
-
usage "deployments"
|
527
|
-
desc "Show the list of available deployments"
|
528
|
-
route :deployment, :list
|
115
|
+
opts.on("--[no-]color", "Toggle colorized output") do |v|
|
116
|
+
Config.colorize = v
|
529
117
|
end
|
530
118
|
|
531
|
-
|
532
|
-
|
533
|
-
desc "Diffs your current BOSH deployment configuration against " +
|
534
|
-
"the specified BOSH deployment configuration template so that " +
|
535
|
-
"you can keep your deployment configuration file up to date. " +
|
536
|
-
"A dev template can be found in deployments repos."
|
537
|
-
route :biff, :biff
|
119
|
+
opts.on("-v", "--verbose", "Show additional output") do
|
120
|
+
@options[:verbose] = true
|
538
121
|
end
|
539
|
-
|
540
|
-
|
541
|
-
usage "tasks"
|
542
|
-
desc "Show the list of running tasks"
|
543
|
-
route :task, :list_running
|
122
|
+
opts.on("-q", "--quiet", "Suppress all output") do
|
123
|
+
Config.output = nil
|
544
124
|
end
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
desc "Show <number> recent tasks"
|
549
|
-
route :task, :list_recent
|
125
|
+
opts.on("-n", "--non-interactive", "Don't ask for user input") do
|
126
|
+
@options[:non_interactive] = true
|
127
|
+
Config.colorize = false
|
550
128
|
end
|
551
|
-
|
552
|
-
|
553
|
-
usage "vms [<deployment>]"
|
554
|
-
desc "List all VMs that supposed to be in a deployment"
|
555
|
-
route :vms, :list
|
129
|
+
opts.on("-t", "--target URL", "Override target") do |target|
|
130
|
+
@options[:target] = target
|
556
131
|
end
|
557
|
-
|
558
|
-
|
559
|
-
usage "cleanup"
|
560
|
-
desc "Remove all but several recent stemcells and releases " +
|
561
|
-
"from current director " +
|
562
|
-
"(stemcells and releases currently in use are NOT deleted)"
|
563
|
-
route :maintenance, :cleanup
|
132
|
+
opts.on("-u", "--user USER", "Override username") do |user|
|
133
|
+
@options[:username] = user
|
564
134
|
end
|
565
|
-
|
566
|
-
|
567
|
-
usage "cloudcheck [<deployment>]"
|
568
|
-
desc "Cloud consistency check and interactive repair"
|
569
|
-
option "--auto", "resolve problems automatically " +
|
570
|
-
"(not recommended for production)"
|
571
|
-
option "--report", "generate report only, " +
|
572
|
-
"don't attempt to resolve problems"
|
573
|
-
route :cloud_check, :perform
|
135
|
+
opts.on("-p", "--password PASSWORD", "Override password") do |pass|
|
136
|
+
@options[:password] = pass
|
574
137
|
end
|
575
|
-
|
576
|
-
|
577
|
-
usage "add blob <local_path> [<blob_dir>]"
|
578
|
-
desc "Add a local file as BOSH blob"
|
579
|
-
route :blob_management, :add
|
138
|
+
opts.on("-d", "--deployment FILE", "Override deployment") do |file|
|
139
|
+
@options[:deployment] = file
|
580
140
|
end
|
581
141
|
|
582
|
-
|
583
|
-
|
584
|
-
desc "Upload new and updated blobs to the blobstore"
|
585
|
-
route :blob_management, :upload
|
586
|
-
end
|
142
|
+
@args = @option_parser.order!(@args)
|
143
|
+
end
|
587
144
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
end
|
145
|
+
# Discover and load CLI plugins from all available gems
|
146
|
+
# @return [void]
|
147
|
+
def load_plugins
|
148
|
+
plugins_glob = "bosh/cli/commands/*.rb"
|
593
149
|
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
150
|
+
unless Gem.respond_to?(:find_files)
|
151
|
+
say("Cannot load plugins, ".yellow +
|
152
|
+
"please run `gem update --system' to ".yellow +
|
153
|
+
"update your RubyGems".yellow)
|
154
|
+
return
|
598
155
|
end
|
599
156
|
|
600
|
-
|
601
|
-
plugins_glob
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
"please run `gem update --system' to ".yellow +
|
606
|
-
"update your RubyGems".yellow)
|
607
|
-
return
|
608
|
-
end
|
609
|
-
|
610
|
-
plugins = begin
|
611
|
-
Gem.find_files(plugins_glob, true)
|
612
|
-
rescue ArgumentError
|
613
|
-
# Handling rubygems compatibility issue
|
614
|
-
Gem.find_files(plugins_glob)
|
615
|
-
end
|
616
|
-
|
617
|
-
plugins.each do |file|
|
618
|
-
class_name = File.basename(file, ".rb").capitalize
|
619
|
-
|
620
|
-
next if Bosh::Cli::Command.const_defined?(class_name)
|
621
|
-
|
622
|
-
load file
|
623
|
-
|
624
|
-
plugin = Bosh::Cli::Command.const_get(class_name)
|
625
|
-
|
626
|
-
plugin.commands.each do |name, block|
|
627
|
-
command(name, &block)
|
628
|
-
end
|
629
|
-
|
630
|
-
@plugins ||= {}
|
631
|
-
@plugins[class_name] = plugin
|
632
|
-
end
|
157
|
+
plugins = begin
|
158
|
+
Gem.find_files(plugins_glob, true)
|
159
|
+
rescue ArgumentError
|
160
|
+
# Handling rubygems compatibility issue
|
161
|
+
Gem.find_files(plugins_glob)
|
633
162
|
end
|
634
163
|
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
opts.on("-q", "--quiet") { @options[:quiet] = true }
|
644
|
-
opts.on("-s", "--skip-director-checks") do
|
645
|
-
@options[:director_checks] = false
|
164
|
+
plugins.each do |plugin|
|
165
|
+
n_commands = Config.commands.size
|
166
|
+
gem_dir = Pathname.new(Gem.dir)
|
167
|
+
plugin_name = Pathname.new(plugin).relative_path_from(gem_dir)
|
168
|
+
begin
|
169
|
+
require plugin
|
170
|
+
rescue Exception => e
|
171
|
+
say("Failed to load plugin #{plugin_name}: #{e.message}".red)
|
646
172
|
end
|
647
|
-
|
648
|
-
|
649
|
-
|
173
|
+
if Config.commands.size == n_commands
|
174
|
+
say(("File #{plugin_name} has been loaded as plugin but it didn't " +
|
175
|
+
"contain any commands.\nMake sure this plugin is updated to be " +
|
176
|
+
"compatible with BOSH CLI 1.0.").columnize(80).yellow)
|
650
177
|
end
|
651
|
-
opts.on("-d", "--debug") { @options[:debug] = true }
|
652
|
-
opts.on("--target URL") { |target| @options[:target] = target }
|
653
|
-
opts.on("--user USER") { |user| @options[:username] = user }
|
654
|
-
opts.on("--password PASSWORD") { |pass| @options[:password] = pass }
|
655
|
-
opts.on("--deployment FILE") { |file| @options[:deployment] = file }
|
656
|
-
opts.on("-v", "--version") { dispatch(find_command(:version)) }
|
657
178
|
end
|
658
|
-
|
659
|
-
@args = opts_parser.order!(@args)
|
660
179
|
end
|
661
180
|
|
662
181
|
def build_parse_tree
|
663
182
|
@parse_tree = ParseTreeNode.new
|
664
183
|
|
665
|
-
|
184
|
+
Config.commands.each_value do |command|
|
666
185
|
p = @parse_tree
|
667
186
|
n_kw = command.keywords.size
|
668
187
|
|
669
|
-
|
188
|
+
command.keywords.each_with_index do |kw, i|
|
670
189
|
p[kw] ||= ParseTreeNode.new
|
671
190
|
p = p[kw]
|
672
191
|
p.command = command if i == n_kw - 1
|
@@ -675,81 +194,17 @@ module Bosh::Cli
|
|
675
194
|
end
|
676
195
|
|
677
196
|
def add_shortcuts
|
678
|
-
{
|
197
|
+
{
|
198
|
+
"st" => "status",
|
679
199
|
"props" => "properties",
|
680
|
-
"cck" => "cloudcheck"
|
200
|
+
"cck" => "cloudcheck"
|
201
|
+
}.each do |short, long|
|
681
202
|
@parse_tree[short] = @parse_tree[long]
|
682
203
|
end
|
683
204
|
end
|
684
205
|
|
685
|
-
def
|
686
|
-
|
687
|
-
usage: bosh [--verbose] [--config|-c <FILE>] [--cache-dir <DIR]
|
688
|
-
[--force] [--no-color] [--skip-director-checks] [--quiet]
|
689
|
-
[--non-interactive]
|
690
|
-
command [<args>]
|
691
|
-
OUT
|
692
|
-
end
|
693
|
-
|
694
|
-
def command_usage(cmd, margin = nil)
|
695
|
-
command = cmd.is_a?(Symbol) ? find_command(cmd) : cmd
|
696
|
-
usage = command.usage
|
697
|
-
|
698
|
-
margin ||= 2
|
699
|
-
usage_width = 25
|
700
|
-
desc_width = 43
|
701
|
-
option_width = 10
|
702
|
-
|
703
|
-
output = " " * margin
|
704
|
-
output << usage.ljust(usage_width) + " "
|
705
|
-
char_count = usage.size > usage_width ? 100 : 0
|
706
|
-
|
707
|
-
command.description.to_s.split(/\s+/).each do |word|
|
708
|
-
if char_count + word.size + 1 > desc_width # +1 accounts for space
|
709
|
-
char_count = 0
|
710
|
-
output << "\n" + " " * (margin + usage_width + 1)
|
711
|
-
end
|
712
|
-
char_count += word.size
|
713
|
-
output << word << " "
|
714
|
-
end
|
715
|
-
|
716
|
-
command.options.each do |name, value|
|
717
|
-
output << "\n" + " " * (margin + usage_width + 1)
|
718
|
-
output << name.ljust(option_width) + " "
|
719
|
-
# Long option name eats the whole line,
|
720
|
-
# short one gives space to description
|
721
|
-
char_count = name.size > option_width ? 100 : 0
|
722
|
-
|
723
|
-
value.to_s.split(/\s+/).each do |word|
|
724
|
-
if char_count + word.size + 1 > desc_width - option_width
|
725
|
-
char_count = 0
|
726
|
-
output << "\n" + " " * (margin + usage_width + option_width + 2)
|
727
|
-
end
|
728
|
-
char_count += word.size
|
729
|
-
output << word << " "
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
output
|
734
|
-
end
|
735
|
-
|
736
|
-
def help_message
|
737
|
-
template = File.join(File.dirname(__FILE__),
|
738
|
-
"templates", "help_message.erb")
|
739
|
-
ERB.new(File.read(template), 4).result(binding.taint)
|
740
|
-
end
|
741
|
-
|
742
|
-
def plugin_help_message
|
743
|
-
help = ['']
|
744
|
-
|
745
|
-
@plugins.each do |class_name, plugin|
|
746
|
-
help << class_name
|
747
|
-
plugin.commands.keys.each do |name|
|
748
|
-
help << command_usage(name)
|
749
|
-
end
|
750
|
-
end
|
751
|
-
|
752
|
-
help.join("\n")
|
206
|
+
def usage
|
207
|
+
@option_parser.to_s
|
753
208
|
end
|
754
209
|
|
755
210
|
def search_parse_tree(node)
|
@@ -770,13 +225,12 @@ module Bosh::Cli
|
|
770
225
|
# Tries to find best match among aliases (possibly multiple words),
|
771
226
|
# then unwinds it onto the remaining args and searches parse tree again.
|
772
227
|
# Not the most effective algorithm but does the job.
|
773
|
-
config = Bosh::Cli::Config.new(
|
774
|
-
@options[:config] || Bosh::Cli::DEFAULT_CONFIG_PATH)
|
228
|
+
config = Bosh::Cli::Config.new(@options[:config])
|
775
229
|
candidate = []
|
776
230
|
best_match = nil
|
777
231
|
save_args = @args.dup
|
778
232
|
|
779
|
-
while arg = @args.shift
|
233
|
+
while (arg = @args.shift)
|
780
234
|
candidate << arg
|
781
235
|
resolved = config.resolve_alias(:cli, candidate.join(" "))
|
782
236
|
if best_match && resolved.nil?
|
@@ -791,41 +245,11 @@ module Bosh::Cli
|
|
791
245
|
return
|
792
246
|
end
|
793
247
|
|
794
|
-
best_match.split(/\s+/).reverse.each do |
|
795
|
-
@args.unshift(
|
248
|
+
best_match.split(/\s+/).reverse.each do |keyword|
|
249
|
+
@args.unshift(keyword)
|
796
250
|
end
|
797
251
|
|
798
252
|
search_parse_tree(@parse_tree)
|
799
253
|
end
|
800
|
-
|
801
|
-
def command_suggestions(args)
|
802
|
-
non_keywords = args - ALL_KEYWORDS
|
803
|
-
|
804
|
-
COMMANDS.values.select do |cmd|
|
805
|
-
(args & cmd.keywords).size > 0 && args - cmd.keywords == non_keywords
|
806
|
-
end
|
807
|
-
end
|
808
|
-
|
809
|
-
def unknown_command(cmd)
|
810
|
-
say("Command `#{cmd}' not found.")
|
811
|
-
say("Please use `bosh help' to get the list of bosh commands.")
|
812
|
-
end
|
813
|
-
|
814
|
-
def save_exception(e)
|
815
|
-
say("BOSH CLI Error: #{e.message}".red)
|
816
|
-
begin
|
817
|
-
errfile = File.expand_path("~/.bosh_error")
|
818
|
-
File.open(errfile, "w") do |f|
|
819
|
-
f.write(e.message)
|
820
|
-
f.write("\n")
|
821
|
-
f.write(e.backtrace.join("\n"))
|
822
|
-
end
|
823
|
-
say("Error information saved in #{errfile}")
|
824
|
-
rescue => e
|
825
|
-
say("Error information couldn't be saved: #{e.message}")
|
826
|
-
end
|
827
|
-
end
|
828
|
-
|
829
254
|
end
|
830
|
-
|
831
255
|
end
|