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.
- 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
|