morpheus-cli 2.10.3 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/morpheus +5 -96
- data/lib/morpheus/api/api_client.rb +23 -1
- data/lib/morpheus/api/checks_interface.rb +106 -0
- data/lib/morpheus/api/incidents_interface.rb +102 -0
- data/lib/morpheus/api/monitoring_apps_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_contacts_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_groups_interface.rb +47 -0
- data/lib/morpheus/api/monitoring_interface.rb +36 -0
- data/lib/morpheus/api/option_type_lists_interface.rb +47 -0
- data/lib/morpheus/api/option_types_interface.rb +47 -0
- data/lib/morpheus/api/roles_interface.rb +0 -1
- data/lib/morpheus/api/setup_interface.rb +19 -9
- data/lib/morpheus/cli.rb +20 -1
- data/lib/morpheus/cli/accounts.rb +8 -3
- data/lib/morpheus/cli/app_templates.rb +19 -11
- data/lib/morpheus/cli/apps.rb +52 -37
- data/lib/morpheus/cli/cli_command.rb +229 -53
- data/lib/morpheus/cli/cli_registry.rb +48 -40
- data/lib/morpheus/cli/clouds.rb +55 -26
- data/lib/morpheus/cli/command_error.rb +12 -0
- data/lib/morpheus/cli/credentials.rb +68 -26
- data/lib/morpheus/cli/curl_command.rb +98 -0
- data/lib/morpheus/cli/dashboard_command.rb +2 -7
- data/lib/morpheus/cli/deployments.rb +4 -4
- data/lib/morpheus/cli/deploys.rb +1 -2
- data/lib/morpheus/cli/dot_file.rb +5 -8
- data/lib/morpheus/cli/error_handler.rb +179 -15
- data/lib/morpheus/cli/groups.rb +21 -13
- data/lib/morpheus/cli/hosts.rb +220 -110
- data/lib/morpheus/cli/instance_types.rb +2 -2
- data/lib/morpheus/cli/instances.rb +257 -167
- data/lib/morpheus/cli/key_pairs.rb +15 -9
- data/lib/morpheus/cli/library.rb +673 -27
- data/lib/morpheus/cli/license.rb +2 -2
- data/lib/morpheus/cli/load_balancers.rb +4 -4
- data/lib/morpheus/cli/log_level_command.rb +6 -4
- data/lib/morpheus/cli/login.rb +17 -3
- data/lib/morpheus/cli/logout.rb +25 -11
- data/lib/morpheus/cli/man_command.rb +388 -0
- data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/monitoring_helper.rb +434 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +620 -112
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -1
- data/lib/morpheus/cli/monitoring_apps_command.rb +29 -0
- data/lib/morpheus/cli/monitoring_checks_command.rb +427 -0
- data/lib/morpheus/cli/monitoring_contacts_command.rb +373 -0
- data/lib/morpheus/cli/monitoring_groups_command.rb +29 -0
- data/lib/morpheus/cli/monitoring_incidents_command.rb +711 -0
- data/lib/morpheus/cli/option_types.rb +10 -1
- data/lib/morpheus/cli/recent_activity_command.rb +2 -5
- data/lib/morpheus/cli/remote.rb +874 -134
- data/lib/morpheus/cli/roles.rb +54 -27
- data/lib/morpheus/cli/security_group_rules.rb +2 -2
- data/lib/morpheus/cli/security_groups.rb +23 -19
- data/lib/morpheus/cli/set_prompt_command.rb +50 -0
- data/lib/morpheus/cli/shell.rb +222 -157
- data/lib/morpheus/cli/tasks.rb +19 -15
- data/lib/morpheus/cli/users.rb +27 -17
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +28 -13
- data/lib/morpheus/cli/whoami.rb +131 -52
- data/lib/morpheus/cli/workflows.rb +24 -9
- data/lib/morpheus/formatters.rb +195 -3
- data/lib/morpheus/logging.rb +86 -0
- data/lib/morpheus/terminal.rb +371 -0
- data/scripts/generate_morpheus_commands_help.morpheus +60 -0
- metadata +21 -2
data/lib/morpheus/cli/roles.rb
CHANGED
@@ -60,9 +60,14 @@ class Morpheus::Cli::Roles
|
|
60
60
|
print JSON.pretty_generate(json_response)
|
61
61
|
print "\n"
|
62
62
|
else
|
63
|
-
|
63
|
+
title = "Morpheus Roles"
|
64
|
+
subtitles = []
|
65
|
+
if params[:phrase]
|
66
|
+
subtitles << "Search: #{params[:phrase]}".strip
|
67
|
+
end
|
68
|
+
print_h1 title, subtitles
|
64
69
|
if roles.empty?
|
65
|
-
|
70
|
+
print cyan,"No roles found.",reset,"\n"
|
66
71
|
else
|
67
72
|
print_roles_table(roles, {is_master_account: @is_master_account})
|
68
73
|
print_results_pagination(json_response)
|
@@ -105,46 +110,68 @@ class Morpheus::Cli::Roles
|
|
105
110
|
puts optparse
|
106
111
|
exit 1
|
107
112
|
end
|
108
|
-
name = args[0]
|
109
113
|
|
110
114
|
connect(options)
|
111
115
|
begin
|
112
116
|
account = find_account_from_options(options)
|
113
117
|
account_id = account ? account['id'] : nil
|
114
118
|
if options[:dry_run]
|
115
|
-
|
119
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
120
|
+
print_dry_run @roles_interface.dry.get(account_id, args[0].to_i)
|
121
|
+
else
|
122
|
+
print_dry_run @roles_interface.dry.list(account_id, {name: args[0]})
|
123
|
+
end
|
116
124
|
return
|
117
125
|
end
|
118
126
|
|
119
|
-
role = find_role_by_name_or_id(account_id,
|
120
|
-
exit 1 if role.nil?
|
127
|
+
# role = find_role_by_name_or_id(account_id, args[0])
|
128
|
+
# exit 1 if role.nil?
|
129
|
+
# refetch from show action, argh
|
130
|
+
# json_response = @roles_interface.get(account_id, role['id'])
|
131
|
+
# role = json_response['role']
|
121
132
|
|
122
|
-
json_response =
|
123
|
-
|
133
|
+
json_response = nil
|
134
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
135
|
+
json_response = @roles_interface.get(account_id, args[0].to_i)
|
136
|
+
role = json_response['role']
|
137
|
+
else
|
138
|
+
role = find_role_by_name_or_id(account_id, args[0])
|
139
|
+
exit 1 if role.nil?
|
140
|
+
# refetch from show action, argh
|
141
|
+
json_response = @roles_interface.get(account_id, role['id'])
|
142
|
+
role = json_response['role']
|
143
|
+
end
|
124
144
|
|
125
145
|
if options[:json]
|
126
146
|
print JSON.pretty_generate(json_response)
|
127
147
|
print "\n"
|
128
148
|
else
|
129
|
-
print "\n" ,cyan, bold, "Role Details\n","==================", reset, "\n\n"
|
130
149
|
print cyan
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
150
|
+
print_h1 "Role Details"
|
151
|
+
print cyan
|
152
|
+
description_cols = {
|
153
|
+
"ID" => 'id',
|
154
|
+
"Name" => 'name',
|
155
|
+
"Description" => 'description',
|
156
|
+
"Scope" => lambda {|it| it['scope'] },
|
157
|
+
"Type" => lambda {|it| format_role_type(it) },
|
158
|
+
"Multitenant" => lambda {|it| format_boolean(it['multitenant']) },
|
159
|
+
"Owner" => lambda {|it| role['owner'] ? role['owner']['name'] : '' },
|
160
|
+
#"Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
161
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
162
|
+
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
163
|
+
}
|
164
|
+
print_description_list(description_cols, role)
|
165
|
+
|
166
|
+
print_h2 "Role Instance Limits"
|
142
167
|
print cyan
|
143
|
-
|
144
|
-
|
145
|
-
|
168
|
+
print_description_list({
|
169
|
+
"Max Storage" => lambda {|it| (it && it['maxStorage'].to_i != 0) ? Filesize.from("#{it['maxStorage']} B").pretty : "no limit" },
|
170
|
+
"Max Memory" => lambda {|it| (it && it['maxMemory'].to_i != 0) ? Filesize.from("#{it['maxMemory']} B").pretty : "no limit" },
|
171
|
+
"CPU Count" => lambda {|it| (it && it['maxCpu'].to_i != 0) ? it['maxCpu'] : "no limit" }
|
172
|
+
}, role['instanceLimits'])
|
146
173
|
|
147
|
-
|
174
|
+
print_h2 "Feature Access"
|
148
175
|
print cyan
|
149
176
|
|
150
177
|
if options[:include_feature_access]
|
@@ -160,7 +187,7 @@ class Morpheus::Cli::Roles
|
|
160
187
|
puts "Use --feature-access to list feature access"
|
161
188
|
end
|
162
189
|
|
163
|
-
|
190
|
+
print_h2 "Group Access"
|
164
191
|
print cyan
|
165
192
|
puts "Global Group Access: #{get_access_string(json_response['globalSiteAccess'])}\n\n"
|
166
193
|
if json_response['globalSiteAccess'] == 'custom'
|
@@ -177,7 +204,7 @@ class Morpheus::Cli::Roles
|
|
177
204
|
end
|
178
205
|
end
|
179
206
|
|
180
|
-
|
207
|
+
print_h2 "Cloud Access"
|
181
208
|
print cyan
|
182
209
|
puts "Global Cloud Access: #{get_access_string(json_response['globalZoneAccess'])}\n\n"
|
183
210
|
if json_response['globalZoneAccess'] == 'custom'
|
@@ -194,7 +221,7 @@ class Morpheus::Cli::Roles
|
|
194
221
|
end
|
195
222
|
end
|
196
223
|
|
197
|
-
|
224
|
+
print_h2 "Instance Type Access"
|
198
225
|
print cyan
|
199
226
|
puts "Global Instance Type Access: #{get_access_string(json_response['globalInstanceTypeAccess'])}\n\n"
|
200
227
|
if json_response['globalInstanceTypeAccess'] == 'custom'
|
@@ -167,9 +167,9 @@ EOT
|
|
167
167
|
return
|
168
168
|
end
|
169
169
|
rules = json_response['rules']
|
170
|
-
|
170
|
+
print_h1 "Morpheus Security Group Rules for Security Group ID: #{security_group_id}"
|
171
171
|
if rules.empty?
|
172
|
-
|
172
|
+
print yellow,"No Security Group Rules currently configured.",reset,"\n"
|
173
173
|
else
|
174
174
|
rules = rules.sort {|x,y| x["id"] <=> y["id"] }
|
175
175
|
rules.each do |rule|
|
@@ -44,9 +44,9 @@ class Morpheus::Cli::SecurityGroups
|
|
44
44
|
return
|
45
45
|
end
|
46
46
|
security_groups = json_response['securityGroups']
|
47
|
-
|
47
|
+
print_h1 "Morpheus Security Groups"
|
48
48
|
if security_groups.empty?
|
49
|
-
|
49
|
+
print yellow,"No Security Groups currently configured.",reset,"\n"
|
50
50
|
else
|
51
51
|
active_id = @active_security_group[@appliance_name.to_sym]
|
52
52
|
security_groups.each do |security_group|
|
@@ -57,7 +57,7 @@ class Morpheus::Cli::SecurityGroups
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
if active_id
|
60
|
-
print cyan, "\n
|
60
|
+
print cyan, "\n# => - current", reset, "\n"
|
61
61
|
end
|
62
62
|
end
|
63
63
|
print reset,"\n"
|
@@ -91,14 +91,17 @@ class Morpheus::Cli::SecurityGroups
|
|
91
91
|
return
|
92
92
|
end
|
93
93
|
security_group = json_response['securityGroup']
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
94
|
+
print_h1 "Morpheus Security Group"
|
95
|
+
print cyan
|
96
|
+
description_cols = {
|
97
|
+
"ID" => 'id',
|
98
|
+
"Name" => 'name',
|
99
|
+
"Description" => 'description',
|
100
|
+
#"Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
101
|
+
}
|
102
|
+
print_description_list(description_cols, security_group)
|
103
|
+
print reset,"\n"
|
104
|
+
rescue RestClient::Exception => e
|
102
105
|
print_rest_exception(e, options)
|
103
106
|
exit 1
|
104
107
|
end
|
@@ -235,16 +238,17 @@ class Morpheus::Cli::SecurityGroups
|
|
235
238
|
end
|
236
239
|
|
237
240
|
def self.security_group_file_path
|
238
|
-
|
239
|
-
morpheus_dir = File.join(home_dir,".morpheus")
|
240
|
-
if !Dir.exist?(morpheus_dir)
|
241
|
-
Dir.mkdir(morpheus_dir)
|
242
|
-
end
|
243
|
-
return File.join(morpheus_dir,"securitygroup")
|
241
|
+
File.join(Morpheus::Cli.home_directory,"securitygroup")
|
244
242
|
end
|
245
243
|
|
246
|
-
def self.save_security_group(
|
247
|
-
|
244
|
+
def self.save_security_group(new_config)
|
245
|
+
fn = security_group_file_path
|
246
|
+
if !Dir.exists?(File.dirname(fn))
|
247
|
+
FileUtils.mkdir_p(File.dirname(fn))
|
248
|
+
end
|
249
|
+
File.open(fn, 'w') {|f| f.write new_config.to_yaml } #Store
|
250
|
+
FileUtils.chmod(0600, fn)
|
251
|
+
new_config
|
248
252
|
end
|
249
253
|
|
250
254
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'json'
|
3
|
+
require 'morpheus/logging'
|
4
|
+
require 'morpheus/cli/cli_command'
|
5
|
+
|
6
|
+
class Morpheus::Cli::SetPromptCommand
|
7
|
+
include Morpheus::Cli::CliCommand
|
8
|
+
set_command_name :'set-prompt'
|
9
|
+
set_command_hidden
|
10
|
+
|
11
|
+
def handle(args)
|
12
|
+
options = {}
|
13
|
+
optparse = Morpheus::Cli::OptionParser.new do|opts|
|
14
|
+
opts.banner = "Usage: morpheus #{command_name} [shell-prompt]"
|
15
|
+
#build_common_options(opts, options, [])
|
16
|
+
opts.on('-h', '--help', "Prints this help" ) do
|
17
|
+
puts opts
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
opts.footer = <<-EOT
|
21
|
+
This is intended for use in your morpheus scripts.
|
22
|
+
It allows you to set the shell prompt.
|
23
|
+
This can be used as alternative to setting the MORPHEUS_PS1 environment variable
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
set-prompt "morpheus $ "
|
27
|
+
set-prompt "%cyanmorpheus> "
|
28
|
+
set-prompt "[%magenta%remote%reset] %cyan%username morpheus> "
|
29
|
+
|
30
|
+
The default prompt is: "%cyanmorpheus> "
|
31
|
+
|
32
|
+
EOT
|
33
|
+
end
|
34
|
+
optparse.parse!(args)
|
35
|
+
|
36
|
+
if args.count != 1
|
37
|
+
print_error Morpheus::Terminal.angry_prompt
|
38
|
+
puts_error "too many arguments"
|
39
|
+
puts_error optparse
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
self.my_terminal.prompt = args[0]
|
44
|
+
# Morpheus::Terminal.instance.prompt = args[0]
|
45
|
+
Morpheus::Cli::Shell.instance.recalculate_prompt()
|
46
|
+
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/morpheus/cli/shell.rb
CHANGED
@@ -8,22 +8,43 @@ require 'logger'
|
|
8
8
|
require 'fileutils'
|
9
9
|
require 'morpheus/cli/cli_command'
|
10
10
|
require 'morpheus/cli/error_handler'
|
11
|
+
require 'morpheus/terminal'
|
11
12
|
|
12
|
-
|
13
|
+
#class Morpheus::Cli::Shell < Morpheus::Terminal
|
13
14
|
class Morpheus::Cli::Shell
|
14
15
|
include Morpheus::Cli::CliCommand
|
15
16
|
|
16
17
|
@@instance = nil
|
17
18
|
|
18
19
|
def self.instance
|
19
|
-
@@instance ||=
|
20
|
+
@@instance ||= reload_instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.reload_instance
|
24
|
+
@@instance = self.new
|
20
25
|
end
|
21
26
|
|
27
|
+
attr_accessor :prompt #, :angry_prompt
|
28
|
+
|
22
29
|
def initialize()
|
23
|
-
|
30
|
+
@@instance = self
|
31
|
+
reinitialize()
|
32
|
+
end
|
33
|
+
|
34
|
+
def reinitialize()
|
35
|
+
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
36
|
+
@current_remote = ::Morpheus::Cli::Remote.load_active_remote()
|
37
|
+
if @current_remote
|
38
|
+
@appliance_name, @appliance_url = @current_remote[:name], @current_remote[:host]
|
39
|
+
@current_username = @current_remote[:username] || '(anonymous)'
|
40
|
+
else
|
41
|
+
@appliance_name, @appliance_url = nil, nil
|
42
|
+
@current_username = nil
|
43
|
+
end
|
24
44
|
#connect()
|
25
45
|
#raise "one shell only" if @@instance
|
26
46
|
@@instance = self
|
47
|
+
recalculate_prompt()
|
27
48
|
recalculate_auto_complete_commands()
|
28
49
|
end
|
29
50
|
|
@@ -31,6 +52,29 @@ class Morpheus::Cli::Shell
|
|
31
52
|
# @api_client = establish_remote_appliance_connection(opts)
|
32
53
|
# end
|
33
54
|
|
55
|
+
def recalculate_prompt()
|
56
|
+
# custom prompts.. this is overkill and perhaps a silly thing..
|
57
|
+
# Example usage:
|
58
|
+
# MORPHEUS_PS1="[%remote] %cyan %username morph> " morpheus shell --debug
|
59
|
+
#@prompt = Morpheus::Terminal.instance.prompt.to_s #.dup
|
60
|
+
@prompt = my_terminal.prompt
|
61
|
+
|
62
|
+
var_map = {
|
63
|
+
'%cyan' => cyan, '%magenta' => magenta, '%reset' => reset, '%dark' => dark,
|
64
|
+
'%remote' => @appliance_name.to_s, '%username' => @current_username.to_s,
|
65
|
+
'%remote_url' => @appliance_url.to_s
|
66
|
+
}
|
67
|
+
@calculated_prompt = @prompt.to_s.dup
|
68
|
+
var_map.each do |var_key, var_value|
|
69
|
+
@calculated_prompt.gsub!(var_key.to_s, var_value.to_s)
|
70
|
+
end
|
71
|
+
# cleanup empty brackets caused by var value
|
72
|
+
@calculated_prompt = @calculated_prompt.gsub("[]", "").gsub("<>", "").gsub("{}", "")
|
73
|
+
@calculated_prompt = @calculated_prompt.strip
|
74
|
+
@calculated_prompt = "#{@calculated_prompt}#{reset} "
|
75
|
+
@calculated_prompt
|
76
|
+
end
|
77
|
+
|
34
78
|
def recalculate_auto_complete_commands
|
35
79
|
@morpheus_commands = Morpheus::Cli::CliRegistry.all.keys.reject {|k| [:shell].include?(k) }
|
36
80
|
@shell_commands = [:clear, :history, :'flush-history', :reload!, :help, :exit]
|
@@ -57,7 +101,6 @@ class Morpheus::Cli::Shell
|
|
57
101
|
|
58
102
|
def handle(args)
|
59
103
|
usage = "Usage: morpheus #{command_name}"
|
60
|
-
@command_options = {} # this is a way to curry options to all commands.. but meh
|
61
104
|
optparse = OptionParser.new do|opts|
|
62
105
|
opts.banner = usage
|
63
106
|
opts.on('--norc','--norc', "Do not read and execute the personal initialization script .morpheusrc") do
|
@@ -67,13 +110,11 @@ class Morpheus::Cli::Shell
|
|
67
110
|
Morpheus::RestClient.enable_ssl_verification = false
|
68
111
|
end
|
69
112
|
opts.on('-C','--nocolor', "Disable ANSI coloring") do
|
70
|
-
@command_options[:nocolor] = true
|
71
113
|
Term::ANSIColor::coloring = false
|
72
114
|
end
|
73
115
|
opts.on('-V','--debug', "Print extra output for debugging. ") do |json|
|
74
116
|
Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
|
75
|
-
::RestClient.log = Morpheus::Logging.debug? ?
|
76
|
-
@command_options[:debug] = true
|
117
|
+
::RestClient.log = Morpheus::Logging.debug? ? Morpheus::Logging::DarkPrinter.instance : nil
|
77
118
|
end
|
78
119
|
opts.on( '-h', '--help', "Prints this help" ) do
|
79
120
|
puts opts
|
@@ -95,13 +136,17 @@ class Morpheus::Cli::Shell
|
|
95
136
|
end
|
96
137
|
end
|
97
138
|
|
139
|
+
reinitialize()
|
140
|
+
# recalculate_prompt()
|
141
|
+
# recalculate_auto_complete_commands()
|
142
|
+
|
98
143
|
exit = false
|
99
144
|
while !exit do
|
100
145
|
Readline.completion_append_character = " "
|
101
146
|
Readline.completion_proc = @auto_complete
|
102
147
|
Readline.basic_word_break_characters = ""
|
103
148
|
#Readline.basic_word_break_characters = "\t\n\"\‘`@$><=;|&{( "
|
104
|
-
input = Readline.readline(
|
149
|
+
input = Readline.readline(@calculated_prompt, true).to_s
|
105
150
|
input = input.strip
|
106
151
|
|
107
152
|
execute_commands(input)
|
@@ -109,7 +154,15 @@ class Morpheus::Cli::Shell
|
|
109
154
|
|
110
155
|
end
|
111
156
|
|
157
|
+
# same as Terminal instance
|
158
|
+
def execute(input)
|
159
|
+
# args = Shellwords.shellsplit(input)
|
160
|
+
#cmd = args.shift
|
161
|
+
execute_commands(input)
|
162
|
+
end
|
163
|
+
|
112
164
|
def execute_commands(input)
|
165
|
+
# input = input.to_s.sub(/^morpheus\s+/, "") # meh
|
113
166
|
# split the command on unquoted semicolons.
|
114
167
|
# so you can run multiple commands at once! eg hosts list; instances list
|
115
168
|
# all_commands = input.gsub(/(\;)(?=(?:[^"]|"[^"]*")*$)/, '__CMDDELIM__').split('__CMDDELIM__').collect {|it| it.to_s.strip }.select {|it| !it.empty? }.compact
|
@@ -126,12 +179,8 @@ class Morpheus::Cli::Shell
|
|
126
179
|
|
127
180
|
def execute_command(input)
|
128
181
|
#puts "shell execute_command(#{input})"
|
129
|
-
@command_options ||= {}
|
130
|
-
|
131
182
|
input = input.to_s.strip
|
132
183
|
|
133
|
-
# print cyan,"morpheus > ",reset
|
134
|
-
# input = $stdin.gets.chomp!
|
135
184
|
if !input.empty?
|
136
185
|
|
137
186
|
if input == 'exit'
|
@@ -140,6 +189,9 @@ class Morpheus::Cli::Shell
|
|
140
189
|
exit 0
|
141
190
|
elsif input == 'help'
|
142
191
|
|
192
|
+
#print_h1 "Morpheus Shell Help", [], white
|
193
|
+
#print "\n"
|
194
|
+
|
143
195
|
puts "You are in a morpheus client shell."
|
144
196
|
puts "See the available commands below."
|
145
197
|
|
@@ -149,15 +201,29 @@ class Morpheus::Cli::Shell
|
|
149
201
|
@morpheus_commands.sort.each {|cmd|
|
150
202
|
puts "\t#{cmd.to_s}"
|
151
203
|
}
|
152
|
-
puts "\n"
|
204
|
+
#puts "\n"
|
153
205
|
puts "\nShell Commands:"
|
154
206
|
@shell_commands.each {|cmd|
|
155
207
|
puts "\t#{cmd.to_s}"
|
156
208
|
}
|
157
209
|
puts "\n"
|
158
|
-
puts "For more information."
|
159
|
-
|
160
|
-
|
210
|
+
puts "For more information, see https://github.com/gomorpheus/morpheus-cli/wiki"
|
211
|
+
#print "\n"
|
212
|
+
return 0
|
213
|
+
elsif input =~ /^sleep/
|
214
|
+
sleep_sec = input.sub("sleep ", "").to_f
|
215
|
+
if (!(sleep_sec > 0))
|
216
|
+
# raise_command_error "sleep requires the argument [seconds]. eg. sleep 3.14"
|
217
|
+
puts_error "sleep requires argument [seconds]. eg. sleep 3.14"
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
log_history_command(input)
|
221
|
+
Morpheus::Logging::DarkPrinter.puts "sleeping for #{sleep_sec}s ... zzzZzzzZ" if Morpheus::Logging.debug?
|
222
|
+
begin
|
223
|
+
sleep(sleep_sec)
|
224
|
+
rescue Interrupt
|
225
|
+
Morpheus::Logging::DarkPrinter.puts "\nInterrupt. waking up from sleep early"
|
226
|
+
end
|
161
227
|
return 0
|
162
228
|
elsif input =~ /^history/
|
163
229
|
n_commands = input.sub(/^history\s?/, '').sub(/\-n\s?/, '')
|
@@ -182,32 +248,74 @@ class Morpheus::Cli::Shell
|
|
182
248
|
@history_logger = load_history_logger
|
183
249
|
puts "history cleared!"
|
184
250
|
return 0
|
251
|
+
elsif input == "edit rc"
|
252
|
+
fn = Morpheus::Cli::DotFile.morpheusrc_filename
|
253
|
+
editor = ENV['EDITOR'] # || 'nano'
|
254
|
+
if !editor
|
255
|
+
puts "You have no EDITOR defined. Use 'export EDITOR=emacs'"
|
256
|
+
#puts "Trying nano..."
|
257
|
+
#editor = "nano"
|
258
|
+
end
|
259
|
+
system("which #{editor} > /dev/null 2>&1")
|
260
|
+
has_editor = $?.success?
|
261
|
+
if has_editor
|
262
|
+
puts "opening #{fn} for editing with #{editor} ..."
|
263
|
+
system("#{editor} #{fn}")
|
264
|
+
puts "Use 'reload' to re-execute your startup script #{File.basename(fn)}"
|
265
|
+
else
|
266
|
+
puts_error2 Morpheus::Terminal.angry_prompt
|
267
|
+
puts_error "The defined EDITOR '#{editor}' was not found on your system."
|
268
|
+
end
|
269
|
+
return 0 # $?
|
270
|
+
elsif input == "edit profile"
|
271
|
+
fn = Morpheus::Cli::DotFile.morpheus_profile_filename
|
272
|
+
editor = ENV['EDITOR'] # || 'nano'
|
273
|
+
if !editor
|
274
|
+
puts "You have no EDITOR defined. Use 'export EDITOR=emacs'."
|
275
|
+
#puts "Trying nano..."
|
276
|
+
#editor = "nano"
|
277
|
+
end
|
278
|
+
system("which #{editor} > /dev/null 2>&1")
|
279
|
+
has_editor = $?.success?
|
280
|
+
if has_editor
|
281
|
+
puts "opening #{fn} for editing with #{editor} ..."
|
282
|
+
`#{editor} #{fn}`
|
283
|
+
puts "Use 'reload' to re-execute your startup script #{File.basename(fn)}"
|
284
|
+
else
|
285
|
+
puts_error Morpheus::Terminal.angry_prompt
|
286
|
+
puts_error "The defined EDITOR '#{editor}' was not found on your system."
|
287
|
+
end
|
288
|
+
return 0 # $?
|
185
289
|
elsif input == 'reload' || input == 'reload!'
|
290
|
+
# raise RestartShellPlease
|
186
291
|
#log_history_command(input)
|
187
292
|
# could just fork instead?
|
293
|
+
# clear registry
|
294
|
+
Morpheus::Cli::CliRegistry.instance.flush
|
295
|
+
# reload code
|
188
296
|
Morpheus::Cli.load!
|
189
|
-
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
load __FILE__
|
199
|
-
rescue => err
|
200
|
-
print "failed to reload #{__FILE__}. oh well"
|
201
|
-
# print err
|
297
|
+
|
298
|
+
# raise RestartShellPlease
|
299
|
+
|
300
|
+
# execute startup scripts
|
301
|
+
if File.exists?(Morpheus::Cli::DotFile.morpheus_profile_filename)
|
302
|
+
Morpheus::Cli::DotFile.new(Morpheus::Cli::DotFile.morpheus_profile_filename).execute()
|
303
|
+
end
|
304
|
+
if File.exists?(Morpheus::Cli::DotFile.morpheusrc_filename)
|
305
|
+
Morpheus::Cli::DotFile.new(Morpheus::Cli::DotFile.morpheusrc_filename).execute()
|
202
306
|
end
|
203
|
-
|
307
|
+
|
308
|
+
# recalculate shell environment
|
309
|
+
reinitialize()
|
310
|
+
|
311
|
+
Morpheus::Logging::DarkPrinter.puts "shell has been reloaded" if Morpheus::Logging.debug?
|
204
312
|
return 0
|
205
313
|
elsif input == '!!'
|
206
314
|
cmd_number = @history.keys[-1]
|
207
315
|
input = @history[cmd_number]
|
208
316
|
if !input
|
209
317
|
puts "There is no previous command"
|
210
|
-
return
|
318
|
+
return false
|
211
319
|
end
|
212
320
|
execute_commands(input)
|
213
321
|
elsif input =~ /^\!.+/
|
@@ -237,76 +345,29 @@ class Morpheus::Cli::Shell
|
|
237
345
|
# puts "#{Morpheus::Logging.log_level}"
|
238
346
|
elsif input == "debug"
|
239
347
|
log_history_command(input)
|
240
|
-
Morpheus::Cli::LogLevelCommand.new.handle(["debug"])
|
241
|
-
@command_options[:debug] = true
|
242
|
-
return 0
|
243
|
-
# Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
|
244
|
-
# ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
|
245
|
-
# elsif log_level == "info"
|
246
|
-
# #log_history_command(input)
|
247
|
-
# @command_options.delete(:debug)
|
248
|
-
# Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::INFO)
|
249
|
-
# ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
|
250
|
-
# elsif log_level.to_s == "0" || (log_level.to_i > 0 && log_level.to_i < 7)
|
251
|
-
# # other log levels are pointless right now..
|
252
|
-
# @command_options.delete(:debug)
|
253
|
-
# Morpheus::Logging.set_log_level(log_level.to_i)
|
254
|
-
# ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
|
255
|
-
# else
|
256
|
-
# print_red_alert "unknown log level: #{log_level}"
|
257
|
-
# end
|
258
|
-
# return 0
|
259
|
-
# # lots of hidden commands
|
260
|
-
# elsif input == "debug"
|
261
|
-
# @command_options[:debug] = true
|
262
|
-
# Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
|
263
|
-
# ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
|
264
|
-
# return 0
|
348
|
+
return Morpheus::Cli::LogLevelCommand.new.handle(["debug"])
|
265
349
|
elsif ["hello","hi","hey","hola"].include?(input.strip.downcase)
|
266
350
|
print "#{input.capitalize}, how may I #{cyan}help#{reset} you?\n"
|
267
351
|
return 0
|
268
|
-
# use morpheus coloring [on|off]
|
269
|
-
# elsif input == "colorize"
|
270
|
-
# Term::ANSIColor::coloring = true
|
271
|
-
# @command_options[:nocolor] = false
|
272
|
-
# return 0
|
273
|
-
# elsif input == "uncolorize"
|
274
|
-
# Term::ANSIColor::coloring = false
|
275
|
-
# @command_options[:nocolor] = true
|
276
|
-
# return 0
|
277
352
|
elsif input == "shell"
|
278
353
|
print "#{cyan}You are already in a shell.#{reset}\n"
|
279
354
|
return false
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
# if File.exists?(source_file)
|
285
|
-
# cmd_results = Morpheus::Cli::DotFile.new(source_file).execute()
|
286
|
-
# # return !cmd_results.include?(false)
|
287
|
-
# return true
|
288
|
-
# else
|
289
|
-
# print_red_alert "file not found: '#{source_file}'"
|
290
|
-
# return false
|
291
|
-
# end
|
292
|
-
# there is actually a Cli::EchoCommand !
|
293
|
-
# elsif input =~ /^echo\s*/i
|
294
|
-
# your_words = input.sub(/^echo\s*/i, '')
|
295
|
-
# print your_words.to_s + "\n"
|
296
|
-
# return true
|
355
|
+
elsif input =~ /^\.\s/
|
356
|
+
# dot alias for source <file>
|
357
|
+
log_history_command(input)
|
358
|
+
return Morpheus::Cli::SourceCommand.new.handle(input.split[1..-1])
|
297
359
|
end
|
298
360
|
|
299
361
|
begin
|
300
362
|
argv = Shellwords.shellsplit(input)
|
301
|
-
|
302
|
-
|
303
|
-
if Morpheus::Cli::CliRegistry.has_command?(
|
363
|
+
cmd_name = argv[0]
|
364
|
+
cmd_args = argv[1..-1]
|
365
|
+
if Morpheus::Cli::CliRegistry.has_command?(cmd_name) || Morpheus::Cli::CliRegistry.has_alias?(cmd_name)
|
304
366
|
#log_history_command(input)
|
305
|
-
Morpheus::Cli::CliRegistry.exec(
|
367
|
+
Morpheus::Cli::CliRegistry.exec(cmd_name, cmd_args)
|
306
368
|
else
|
307
|
-
|
308
|
-
@history_logger.warn "Unrecognized Command #{
|
309
|
-
#puts optparse
|
369
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}'#{cmd_name}' is not a morpheus command. Use 'help' to see the list of available commands."
|
370
|
+
@history_logger.warn "Unrecognized Command #{cmd_name}" if @history_logger
|
310
371
|
end
|
311
372
|
# rescue ArgumentError
|
312
373
|
# puts "Argument Syntax Error..."
|
@@ -319,13 +380,13 @@ class Morpheus::Cli::Shell
|
|
319
380
|
# print "\n"
|
320
381
|
rescue => e
|
321
382
|
@history_logger.error "#{e.message}" if @history_logger
|
322
|
-
Morpheus::Cli::ErrorHandler.new.handle_error(e
|
383
|
+
Morpheus::Cli::ErrorHandler.new(my_terminal.stderr).handle_error(e) # lol
|
323
384
|
# exit 1
|
324
385
|
end
|
325
386
|
|
326
387
|
if @return_to_log_level
|
327
388
|
Morpheus::Logging.set_log_level(@return_to_log_level)
|
328
|
-
::RestClient.log = Morpheus::Logging.debug? ?
|
389
|
+
::RestClient.log = Morpheus::Logging.debug? ? Morpheus::Logging::DarkPrinter.instance : nil
|
329
390
|
@return_to_log_level = nil
|
330
391
|
end
|
331
392
|
|
@@ -334,84 +395,88 @@ class Morpheus::Cli::Shell
|
|
334
395
|
end
|
335
396
|
|
336
397
|
def get_prompt
|
398
|
+
|
399
|
+
# print cyan,"morpheus > ",reset
|
400
|
+
# input = $stdin.gets.chomp!
|
401
|
+
|
337
402
|
input = ''
|
338
403
|
while char=$stdin.getch do
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
end
|
344
|
-
print char
|
345
|
-
input << char
|
404
|
+
if char == '\n'
|
405
|
+
print "\r\n"
|
406
|
+
puts "executing..."
|
407
|
+
break
|
346
408
|
end
|
347
|
-
|
409
|
+
print char
|
410
|
+
input << char
|
348
411
|
end
|
412
|
+
return input
|
413
|
+
end
|
349
414
|
|
350
|
-
|
351
|
-
|
352
|
-
|
415
|
+
def history_file_path
|
416
|
+
File.join(Morpheus::Cli.home_directory, "shell_history")
|
417
|
+
end
|
353
418
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
end
|
359
|
-
if !File.exists?(file_path)
|
360
|
-
FileUtils.touch(file_path)
|
361
|
-
FileUtils.chmod(0600, file_path)
|
362
|
-
end
|
363
|
-
logger = Logger.new(file_path)
|
364
|
-
# logger.formatter = proc do |severity, datetime, progname, msg|
|
365
|
-
# "#{msg}\n"
|
366
|
-
# end
|
367
|
-
return logger
|
419
|
+
def load_history_logger
|
420
|
+
file_path = history_file_path
|
421
|
+
if !Dir.exists?(File.dirname(file_path))
|
422
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
368
423
|
end
|
424
|
+
if !File.exists?(file_path)
|
425
|
+
FileUtils.touch(file_path)
|
426
|
+
FileUtils.chmod(0600, file_path)
|
427
|
+
end
|
428
|
+
logger = Logger.new(file_path)
|
429
|
+
# logger.formatter = proc do |severity, datetime, progname, msg|
|
430
|
+
# "#{msg}\n"
|
431
|
+
# end
|
432
|
+
return logger
|
433
|
+
end
|
369
434
|
|
370
|
-
|
371
|
-
|
372
|
-
|
435
|
+
def load_history_from_log_file(n_commands=1000)
|
436
|
+
@history ||= {}
|
437
|
+
@last_command_number ||= 0
|
373
438
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
end
|
439
|
+
begin
|
440
|
+
if Gem.win_platform?
|
441
|
+
return @history
|
442
|
+
end
|
443
|
+
file_path = history_file_path
|
444
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
445
|
+
# grab extra lines because not all log entries are commands
|
446
|
+
n_lines = n_commands + 500
|
447
|
+
history_lines = `tail -n #{n_lines} #{file_path}`.split(/\n/)
|
448
|
+
command_lines = history_lines.select do |line|
|
449
|
+
line.match(/\(cmd (\d+)\) (.+)/)
|
450
|
+
end
|
451
|
+
command_lines = command_lines.last(n_commands)
|
452
|
+
command_lines.each do |line|
|
453
|
+
matches = line.match(/\(cmd (\d+)\) (.+)/)
|
454
|
+
if matches && matches.size == 3
|
455
|
+
cmd_number = matches[1].to_i
|
456
|
+
cmd = matches[2]
|
457
|
+
|
458
|
+
@last_command_number = cmd_number
|
459
|
+
@history[@last_command_number] = cmd
|
460
|
+
|
461
|
+
# for Ctrl+R history searching
|
462
|
+
Readline::HISTORY << cmd
|
399
463
|
end
|
400
|
-
rescue => e
|
401
|
-
# raise e
|
402
|
-
# puts "failed to load history from log"
|
403
|
-
@history = {}
|
404
464
|
end
|
405
|
-
|
465
|
+
rescue => e
|
466
|
+
# raise e
|
467
|
+
# puts "failed to load history from log"
|
468
|
+
@history = {}
|
406
469
|
end
|
470
|
+
return @history
|
471
|
+
end
|
407
472
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
end
|
473
|
+
def log_history_command(cmd)
|
474
|
+
@history ||= {}
|
475
|
+
@last_command_number ||= 0
|
476
|
+
@last_command_number += 1
|
477
|
+
@history[@last_command_number] = cmd
|
478
|
+
if @history_logger
|
479
|
+
@history_logger.info "(cmd #{@last_command_number}) #{cmd}"
|
416
480
|
end
|
417
481
|
end
|
482
|
+
end
|