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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/bin/morpheus +5 -96
  3. data/lib/morpheus/api/api_client.rb +23 -1
  4. data/lib/morpheus/api/checks_interface.rb +106 -0
  5. data/lib/morpheus/api/incidents_interface.rb +102 -0
  6. data/lib/morpheus/api/monitoring_apps_interface.rb +47 -0
  7. data/lib/morpheus/api/monitoring_contacts_interface.rb +47 -0
  8. data/lib/morpheus/api/monitoring_groups_interface.rb +47 -0
  9. data/lib/morpheus/api/monitoring_interface.rb +36 -0
  10. data/lib/morpheus/api/option_type_lists_interface.rb +47 -0
  11. data/lib/morpheus/api/option_types_interface.rb +47 -0
  12. data/lib/morpheus/api/roles_interface.rb +0 -1
  13. data/lib/morpheus/api/setup_interface.rb +19 -9
  14. data/lib/morpheus/cli.rb +20 -1
  15. data/lib/morpheus/cli/accounts.rb +8 -3
  16. data/lib/morpheus/cli/app_templates.rb +19 -11
  17. data/lib/morpheus/cli/apps.rb +52 -37
  18. data/lib/morpheus/cli/cli_command.rb +229 -53
  19. data/lib/morpheus/cli/cli_registry.rb +48 -40
  20. data/lib/morpheus/cli/clouds.rb +55 -26
  21. data/lib/morpheus/cli/command_error.rb +12 -0
  22. data/lib/morpheus/cli/credentials.rb +68 -26
  23. data/lib/morpheus/cli/curl_command.rb +98 -0
  24. data/lib/morpheus/cli/dashboard_command.rb +2 -7
  25. data/lib/morpheus/cli/deployments.rb +4 -4
  26. data/lib/morpheus/cli/deploys.rb +1 -2
  27. data/lib/morpheus/cli/dot_file.rb +5 -8
  28. data/lib/morpheus/cli/error_handler.rb +179 -15
  29. data/lib/morpheus/cli/groups.rb +21 -13
  30. data/lib/morpheus/cli/hosts.rb +220 -110
  31. data/lib/morpheus/cli/instance_types.rb +2 -2
  32. data/lib/morpheus/cli/instances.rb +257 -167
  33. data/lib/morpheus/cli/key_pairs.rb +15 -9
  34. data/lib/morpheus/cli/library.rb +673 -27
  35. data/lib/morpheus/cli/license.rb +2 -2
  36. data/lib/morpheus/cli/load_balancers.rb +4 -4
  37. data/lib/morpheus/cli/log_level_command.rb +6 -4
  38. data/lib/morpheus/cli/login.rb +17 -3
  39. data/lib/morpheus/cli/logout.rb +25 -11
  40. data/lib/morpheus/cli/man_command.rb +388 -0
  41. data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
  42. data/lib/morpheus/cli/mixins/monitoring_helper.rb +434 -0
  43. data/lib/morpheus/cli/mixins/print_helper.rb +620 -112
  44. data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -1
  45. data/lib/morpheus/cli/monitoring_apps_command.rb +29 -0
  46. data/lib/morpheus/cli/monitoring_checks_command.rb +427 -0
  47. data/lib/morpheus/cli/monitoring_contacts_command.rb +373 -0
  48. data/lib/morpheus/cli/monitoring_groups_command.rb +29 -0
  49. data/lib/morpheus/cli/monitoring_incidents_command.rb +711 -0
  50. data/lib/morpheus/cli/option_types.rb +10 -1
  51. data/lib/morpheus/cli/recent_activity_command.rb +2 -5
  52. data/lib/morpheus/cli/remote.rb +874 -134
  53. data/lib/morpheus/cli/roles.rb +54 -27
  54. data/lib/morpheus/cli/security_group_rules.rb +2 -2
  55. data/lib/morpheus/cli/security_groups.rb +23 -19
  56. data/lib/morpheus/cli/set_prompt_command.rb +50 -0
  57. data/lib/morpheus/cli/shell.rb +222 -157
  58. data/lib/morpheus/cli/tasks.rb +19 -15
  59. data/lib/morpheus/cli/users.rb +27 -17
  60. data/lib/morpheus/cli/version.rb +1 -1
  61. data/lib/morpheus/cli/virtual_images.rb +28 -13
  62. data/lib/morpheus/cli/whoami.rb +131 -52
  63. data/lib/morpheus/cli/workflows.rb +24 -9
  64. data/lib/morpheus/formatters.rb +195 -3
  65. data/lib/morpheus/logging.rb +86 -0
  66. data/lib/morpheus/terminal.rb +371 -0
  67. data/scripts/generate_morpheus_commands_help.morpheus +60 -0
  68. metadata +21 -2
@@ -49,9 +49,14 @@ class Morpheus::Cli::Workflows
49
49
  print JSON.pretty_generate(json_response)
50
50
  else
51
51
  task_sets = json_response['taskSets']
52
- print "\n" ,cyan, bold, "Morpheus Workflows\n","==================", reset, "\n\n"
52
+ title = "Morpheus Workflows"
53
+ subtitles = []
54
+ if params[:phrase]
55
+ subtitles << "Search: #{params[:phrase]}".strip
56
+ end
57
+ print_h1 title, subtitles
53
58
  if task_sets.empty?
54
- puts yellow,"No workflows currently configured.",reset
59
+ print cyan,"No workflows found.",reset,"\n"
55
60
  else
56
61
  print cyan
57
62
  print_workflows_table(task_sets)
@@ -142,15 +147,25 @@ class Morpheus::Cli::Workflows
142
147
  # tasks << find_task_by_name_or_id(task_name)['id']
143
148
  # end
144
149
  tasks = workflow['taskSetTasks'].sort { |x,y| x['taskOrder'].to_i <=> y['taskOrder'].to_i }
145
- print "\n" ,cyan, bold, "Workflow Details\n","==================", reset, "\n\n"
150
+ print_h1 "Workflow Details"
151
+
146
152
  print cyan
147
- puts "ID: #{workflow['id']}"
148
- puts "Name: #{workflow['name']}"
149
- #puts "Description: #{workflow['description']}"
153
+ description_cols = {
154
+ "ID" => 'id',
155
+ "Name" => 'name',
156
+ #"Description" => 'description',
157
+ }
158
+ print_description_list(description_cols, workflow)
159
+
150
160
  #task_names = tasks.collect {|it| it['name'] }
151
- print "\n", cyan, "Tasks:\n"
152
- tasks.each_with_index do |taskSetTask, index|
153
- puts "#{(index+1).to_s.rjust(3, ' ')}. #{taskSetTask['task']['name']}"
161
+ print_h2 "Tasks"
162
+ if tasks.empty?
163
+ print yellow,"No tasks in this workflow.",reset,"\n"
164
+ else
165
+ print cyan
166
+ tasks.each_with_index do |taskSetTask, index|
167
+ puts "#{(index+1).to_s.rjust(3, ' ')}. #{taskSetTask['task']['name']}"
168
+ end
154
169
  end
155
170
  print reset,"\n"
156
171
  end
@@ -1,13 +1,37 @@
1
1
  require 'time'
2
2
 
3
+ DEFAULT_TIME_FORMAT = "%x %I:%M %p %Z"
4
+
3
5
  # returns an instance of Time
4
- def parse_time(dt)
6
+ def parse_time(dt, format=nil)
5
7
  if dt.nil? || dt == '' || dt.to_i == 0
6
8
  return nil
7
9
  elsif dt.is_a?(Time)
8
10
  return dt
9
11
  elsif dt.is_a?(String)
10
- return Time.parse(dt)
12
+ result = nil
13
+ err = nil
14
+ begin
15
+ result = Time.parse(dt)
16
+ rescue => e
17
+ err = e
18
+ end
19
+ if !result
20
+ format ||= DEFAULT_TIME_FORMAT
21
+ if format
22
+ begin
23
+ result = Time.strptime(dt, format)
24
+ rescue => e
25
+ err = e
26
+ end
27
+ end
28
+ end
29
+ if result
30
+ return result
31
+ else
32
+ raise "unable to parse time '#{dt}'. #{err}"
33
+ end
34
+
11
35
  elsif dt.is_a?(Numeric)
12
36
  return Time.at(dt)
13
37
  else
@@ -21,7 +45,7 @@ def format_dt(dt, options={})
21
45
  if options[:local]
22
46
  dt = dt.getlocal
23
47
  end
24
- format = options[:format] || "%x %I:%M %p %Z"
48
+ format = options[:format] || DEFAULT_TIME_FORMAT
25
49
  return dt.strftime(format)
26
50
  end
27
51
 
@@ -42,6 +66,77 @@ def format_dt_as_param(dt)
42
66
  format_dt(dt, {format: "%Y-%m-%d %X"})
43
67
  end
44
68
 
69
+ def format_duration(start_time, end_time=nil, format="human")
70
+ if !start_time
71
+ return ""
72
+ end
73
+ start_time = parse_time(start_time)
74
+ if end_time
75
+ end_time = parse_time(end_time)
76
+ else
77
+ end_time = Time.now
78
+ end
79
+ seconds = (end_time - start_time).abs
80
+ format_duration_seconds(seconds, format)
81
+ end
82
+
83
+ def format_duration_seconds(seconds, format="human")
84
+ seconds = seconds.abs
85
+ out = ""
86
+ # interval = Math.abs(interval)
87
+ if format == "human"
88
+ out = format_human_duration(seconds)
89
+ elsif format
90
+ interval_time = Time.mktime(0) + seconds
91
+ out = interval_time.strftime(format)
92
+ else
93
+ interval_time = Time.mktime(0) + seconds
94
+ out = interval_time.strftime("%H:%M:%S")
95
+ end
96
+ out
97
+ end
98
+
99
+ # returns a human readable time duration
100
+ # @param seconds - duration in seconds
101
+ def format_human_duration(seconds)
102
+ out = ""
103
+ #seconds = seconds.round
104
+ days, hours, minutes = (seconds / (60*60*24)).floor, (seconds / (60*60)).floor, (seconds / (60)).floor
105
+ if days > 365
106
+ out << "#{days.floor} days (more than a year!!)"
107
+ elsif days > 61
108
+ out << "#{days.floor} days (months!)"
109
+ elsif days > 31
110
+ out << "#{days.floor} days (over a month)"
111
+ elsif days > 0
112
+ if days.floor == 1
113
+ out << "1 day"
114
+ else
115
+ out << "#{days.floor} days"
116
+ end
117
+ elsif hours > 1
118
+ if hours == 1
119
+ out << "1 hour"
120
+ else
121
+ out << "#{hours.floor} hours"
122
+ end
123
+ elsif minutes > 1
124
+ if minutes == 1
125
+ out << "1 minute"
126
+ else
127
+ out << "#{minutes.floor} minutes"
128
+ end
129
+ else
130
+ seconds = seconds.floor
131
+ if seconds == 1
132
+ out << "1 second"
133
+ else
134
+ out << "#{seconds} seconds"
135
+ end
136
+ end
137
+ out
138
+ end
139
+
45
140
  def display_appliance(name, url)
46
141
  "#{name} - #{url}"
47
142
  end
@@ -49,3 +144,100 @@ end
49
144
  def iso8601(dt)
50
145
  dt.instance_of(Time) ? dt.iso8601 : "#{dt}"
51
146
  end
147
+
148
+ # get_object_value returns a value within a Hash like object
149
+ # Usage: get_object_value(host, "plan.name")
150
+ def get_object_value(data, key)
151
+ value = nil
152
+ if key.is_a?(Proc)
153
+ return key.call(data)
154
+ end
155
+ key = key.to_s
156
+ if key.include?(".")
157
+ namespaces = key.split(".")
158
+ value = data
159
+ namespaces.each do |ns|
160
+ if value.respond_to?("key?")
161
+ if value.key?(ns.to_s)
162
+ value = value[ns]
163
+ elsif value.key?(ns.to_sym)
164
+ value = value[ns.to_sym]
165
+ else
166
+ value = nil
167
+ end
168
+ else
169
+ value = nil
170
+ end
171
+ end
172
+ else
173
+ value = data[key] || data[key.to_sym]
174
+ end
175
+ return value
176
+ end
177
+
178
+ # filter_data filters Hash-like data to only the specified fields
179
+ # To specify fields of child objects, use a "."
180
+ # Usage: filter_data(instance, ["id", "name", "plan.name"])
181
+ def filter_data(data, include_fields=nil, exclude_fields=nil)
182
+ if !data
183
+ return data
184
+ elsif data.is_a?(Array)
185
+ new_data = data.collect { |it| filter_data(it, include_fields, exclude_fields) }
186
+ return new_data
187
+ elsif data.is_a?(Hash)
188
+ if include_fields
189
+ #new_data = data.select {|k, v| include_fields.include?(k.to_s) || include_fields.include?(k.to_sym) }
190
+ # allow extracting dot pathed fields, just like get_object_value
191
+ my_data = {}
192
+ include_fields.each do |field|
193
+ if field.nil?
194
+ next
195
+ end
196
+ field = field.to_s
197
+ if field.empty?
198
+ next
199
+ end
200
+ if field.include?(".")
201
+ # could do this instead...
202
+ # namespaces = field.split(".")
203
+ # cur_data = data
204
+ # namespaces.each
205
+ # if index != namespaces.length - 1
206
+ # cur_data[ns] ||= {}
207
+ # else
208
+ # cur_data[ns] = get_object_value(new_data, field)
209
+ # end
210
+ # end
211
+ namespaces = field.split(".")
212
+ cur_data = data
213
+ cur_filtered_data = my_data
214
+ namespaces.each_with_index do |ns, index|
215
+ if index != namespaces.length - 1
216
+ if cur_data
217
+ cur_data = cur_data[ns] || cur_data[ns.to_sym]
218
+ else
219
+ cur_data = nil
220
+ end
221
+ cur_filtered_data[ns] ||= {}
222
+ cur_filtered_data = cur_filtered_data[ns]
223
+ else
224
+ if cur_data.respond_to?("[]")
225
+ cur_filtered_data[ns] = cur_data[ns] || cur_data[ns.to_sym]
226
+ else
227
+ cur_filtered_data[ns] = nil
228
+ end
229
+ end
230
+ end
231
+ else
232
+ my_data[field] = data[field] || data[field.to_sym]
233
+ end
234
+ end
235
+ return my_data
236
+ elsif exclude_fields
237
+ new_data = data.reject {|k, v| exclude_fields.include?(k.to_s) || exclude_fields.include?(k.to_sym) }
238
+ return new_data
239
+ end
240
+ else
241
+ return data # .clone
242
+ end
243
+ end
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'term/ansicolor'
2
3
 
3
4
  # Provides global Logging behavior
4
5
  # By default, Morpheus::Logging.logger is set to STDOUT with level INFO
@@ -75,4 +76,89 @@ module Morpheus::Logging
75
76
  self.debug?
76
77
  end
77
78
 
79
+ # An IO class for printing debugging info
80
+ # This is used as a proxy for ::RestClient.log printing right now.
81
+ class DarkPrinter
82
+ include Term::ANSIColor
83
+
84
+ # [IO] to write to
85
+ attr_accessor :io
86
+
87
+ # [String] ansi color code for output. Default is dark
88
+ attr_accessor :color
89
+
90
+ # DarkPrinter with io STDOUT
91
+ def self.instance
92
+ @instance ||= self.new(STDOUT, nil, true)
93
+ end
94
+
95
+ def self.print(*messages)
96
+ instance.print(*messages)
97
+ end
98
+
99
+ def self.puts(*messages)
100
+ instance.puts(*messages)
101
+ end
102
+
103
+ def self.<<(*messages)
104
+ instance.<<(*messages)
105
+ end
106
+
107
+ def initialize(io, color=nil, is_dark=true)
108
+ @io = io # || $stdout
109
+ @color = color # || cyan
110
+ @is_dark = is_dark
111
+ end
112
+
113
+ # mask well known secrets
114
+ def scrub_message(msg)
115
+ if msg.is_a?(String)
116
+ msg.gsub!(/Authorization\"\s?\=\>\s?\"Bearer [^"]+/, 'Authorization"=>"Bearer ************')
117
+ msg.gsub!(/Authorization\:\s?Bearer [^"]+/, 'Authorization"=>"Bearer ************')
118
+ msg.gsub!(/password\"\s?\=\>\s?\"[^"]+/, 'password"=>"************')
119
+ msg.gsub!(/password\=\"[^"]+/, 'password="************')
120
+ end
121
+ msg
122
+ end
123
+
124
+ def print_with_color(&block)
125
+ if Term::ANSIColor.coloring?
126
+ @io.print Term::ANSIColor.reset
127
+ @io.print @color if @color
128
+ @io.print Term::ANSIColor.dark if @is_dark
129
+ end
130
+ yield
131
+ if Term::ANSIColor.coloring?
132
+ @io.print Term::ANSIColor.reset
133
+ end
134
+ end
135
+
136
+ def print(*messages)
137
+ if @io
138
+ messages = messages.flatten.collect {|it| scrub_message(it) }
139
+ print_with_color do
140
+ messages.each do |msg|
141
+ @io.print msg
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def puts(*messages)
148
+ if @io
149
+ messages = messages.flatten.collect {|it| scrub_message(it) }
150
+ print_with_color do
151
+ messages.each do |msg|
152
+ @io.puts msg
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def <<(*messages)
159
+ print(*messages)
160
+ end
161
+
162
+ end
163
+
78
164
  end
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env ruby
2
+ require 'term/ansicolor'
3
+ require 'optparse'
4
+ require 'morpheus/cli'
5
+ require 'morpheus/rest_client'
6
+ require 'morpheus/cli/cli_registry'
7
+ require 'morpheus/cli/dot_file'
8
+ require 'morpheus/cli/error_handler'
9
+ require 'morpheus/logging'
10
+ require 'morpheus/cli'
11
+
12
+ module Morpheus
13
+
14
+ # Terminal is a class for executing morpheus commands
15
+ # The default IO is STDIN, STDOUT, STDERR
16
+ # The default home directory is $HOME/.morpheus
17
+ #
18
+ # ==== Example Usage
19
+ #
20
+ # morph = Morpheus::Terminal.new
21
+ # exit_code, err = morph.execute("instances list -m 10")
22
+ # assert exit_code == 0
23
+ # assert err == nil
24
+ #
25
+ # morph = Morpheus::Terminal.new(STDIN, File.new("/tmp/morph.log", "w+"))
26
+ # morph.execute("hosts get 23")
27
+ #
28
+ # morph = Morpheus::Terminal.new(STDIN, File.new("/tmp/host23.json", "w"))
29
+ # morph.execute("hosts get 23 --json")
30
+ # puts File.read("/tmp/host23.json")
31
+ #
32
+ class Terminal
33
+
34
+ # todo: this can be combined with Cli::Shell
35
+
36
+ class Blackhole # < IO
37
+ def accrete_data(*mgs)
38
+ # Singularity.push(*msgs)
39
+ return nil
40
+ end
41
+ alias :print :accrete_data
42
+ alias :puts :accrete_data
43
+ alias :'<<' :accrete_data
44
+ alias :write :accrete_data
45
+ alias :write :accrete_data
46
+ # alias :gets :do_nothing
47
+ end
48
+
49
+ # DEFAULT_TERMINAL_WIDTH = 80
50
+
51
+ def self.default_color
52
+ Term::ANSIColor.cyan
53
+ end
54
+
55
+ def self.prompt
56
+ if @prompt.nil?
57
+ if ENV['MORPHEUS_PS1']
58
+ @prompt = ENV['MORPHEUS_PS1'].dup
59
+ else
60
+ #ENV['MORPHEUS_PS1'] = "#{Term::ANSIColor.cyan}morpheus> #{Term::ANSIColor.reset}"
61
+ @prompt = "#{Term::ANSIColor.cyan}morpheus>#{Term::ANSIColor.reset} "
62
+ end
63
+ end
64
+ @prompt
65
+ end
66
+
67
+ def self.prompt=(v)
68
+ @prompt = v
69
+ end
70
+
71
+ def self.angry_prompt
72
+ "#{Term::ANSIColor.red}morpheus: #{Term::ANSIColor.reset}"
73
+ end
74
+
75
+ def self.custom_prompt
76
+ #export MORPHEUS_PS1='\[\e[1;32m\]\u@\h:\w${text}$\[\e[m\] '
77
+ end
78
+
79
+ # the global Morpheus::Terminal instance
80
+ # This should go away, but it needed for now...
81
+ def self.instance
82
+ @morphterm
83
+ end
84
+
85
+ # hack alert! This should go away, but it needed for now...
86
+ def self.new(*args)
87
+ obj = super(*args)
88
+ @morphterm = obj
89
+ obj
90
+ end
91
+
92
+ attr_accessor :prompt #, :angry_prompt
93
+ attr_reader :stdin, :stdout, :stderr, :homedir
94
+
95
+
96
+ # @param stdin [IO] Default is STDIN
97
+ # @param stdout [IO] Default is STDOUT
98
+ # @param stderr [IO] Default is STDERR
99
+ # @param [IO] stderr
100
+ # @stderr = stderr
101
+ # @homedir = homedir
102
+ # instead, setup global stuff for now...
103
+ def initialize(stdin=STDIN,stdout=STDOUT, stderr=STDERR, homedir=nil)
104
+ # todo: establish config context for executing commands,
105
+ # so you can run them in parallel within the same process
106
+ # that means not using globals and class instance vars
107
+ # just return an exit code / err, instead of raising SystemExit
108
+
109
+
110
+ # establish IO
111
+ # @stdin, @stdout, @stderr = stdin, stdout, stderr
112
+ set_stdin(stdin)
113
+ set_stdout(stdout)
114
+ set_stderr(stderr)
115
+
116
+ # establish home directory
117
+ @homedir = homedir || ENV['MORPHEUS_CLI_HOME'] || File.join(Dir.home, ".morpheus")
118
+ Morpheus::Cli.home_directory = @homedir
119
+
120
+ # use colors by default
121
+ set_coloring(STDOUT.isatty)
122
+ # Term::ANSIColor::coloring = STDOUT.isatty
123
+ # @coloring = Term::ANSIColor::coloring?
124
+
125
+ # startup script
126
+ if File.exists? Morpheus::Cli::DotFile.morpheus_profile_filename
127
+ @profile_dot_file = Morpheus::Cli::DotFile.new(Morpheus::Cli::DotFile.morpheus_profile_filename)
128
+ else
129
+ @profile_dot_file = nil
130
+ end
131
+
132
+ # the string to prompt for input with
133
+ @prompt ||= Morpheus::Terminal.prompt
134
+ @angry_prompt ||= Morpheus::Terminal.angry_prompt
135
+ end
136
+
137
+ # execute .morpheus_profile startup script
138
+ def execute_profile_script(rerun=false)
139
+ if @profile_dot_file
140
+ if rerun || !@profile_dot_file_has_run
141
+ @profile_dot_file_has_run = true
142
+ return @profile_dot_file.execute() # todo: pass io in here
143
+ else
144
+ return false # already run
145
+ end
146
+ else
147
+ return nil
148
+ end
149
+ end
150
+
151
+ def set_stdin(io)
152
+ # if io.nil? || io == 'blackhole' || io == '/dev/null'
153
+ # @stdout = Morpheus::Terminal::Blackhole.new
154
+ # else
155
+ # @stdout = io
156
+ # end
157
+ @stdin = io
158
+ end
159
+
160
+ def set_stdout(io)
161
+ if io.nil? || io == 'blackhole' || io == '/dev/null'
162
+ @stdout = Morpheus::Terminal::Blackhole.new
163
+ else
164
+ @stdout = io
165
+ end
166
+ @stdout
167
+ end
168
+
169
+ def set_stderr(io)
170
+ if io.nil? || io == 'blackhole' || io == '/dev/null'
171
+ @stderr = Morpheus::Terminal::Blackhole.new
172
+ else
173
+ @stderr = io
174
+ end
175
+ @stderr
176
+ end
177
+
178
+ # def coloring=(v)
179
+ # set_coloring(enabled)
180
+ # end
181
+
182
+ def set_coloring(enabled)
183
+ @coloring = !!enabled
184
+ Term::ANSIColor::coloring = @coloring
185
+ coloring?
186
+ end
187
+
188
+ def coloring?
189
+ @coloring == true
190
+ end
191
+
192
+ # alias :'coloring=' :set_coloring
193
+
194
+ def usage
195
+ out = "Usage: morpheus [command] [options]\n"
196
+ # just for printing help. todo: start using this. maybe in class Cli::MainCommand
197
+ # maybe OptionParser's recover() instance method will do the trick
198
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
199
+ opts.banner = "Options:" # hack alert
200
+ opts.on('-v','--version', "Print the version.") do
201
+ @stdout.puts Morpheus::Cli::VERSION
202
+ # exit
203
+ end
204
+ opts.on('--noprofile','--noprofile', "Do not read and execute the personal initialization script .morpheus_profile") do
205
+ @noprofile = true
206
+ end
207
+ opts.on('-C','--nocolor', "Disable ANSI coloring") do
208
+ @coloring = false
209
+ Term::ANSIColor::coloring = false
210
+ end
211
+ opts.on('-V','--debug', "Print extra output for debugging. ") do |json|
212
+ @terminal_log_level = Morpheus::Logging::Logger::DEBUG
213
+ Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
214
+ ::RestClient.log = Morpheus::Logging.debug? ? Morpheus::Logging::DarkPrinter.instance : nil
215
+ end
216
+ opts.on( '-h', '--help', "Prints this help" ) do
217
+ @stdout.puts opts
218
+ # exit
219
+ end
220
+ end
221
+ out << "Commands:\n"
222
+ Morpheus::Cli::CliRegistry.all.keys.sort.each {|cmd|
223
+ out << "\t#{cmd.to_s}\n"
224
+ }
225
+ # out << "Options:\n"
226
+ out << optparse.to_s
227
+ out << "\n"
228
+ out << "For more information, see https://github.com/gomorpheus/morpheus-cli/wiki"
229
+ out << "\n"
230
+ out
231
+ end
232
+
233
+ def prompt
234
+ @prompt # ||= Morpheus::Terminal.default_prompt
235
+ end
236
+
237
+ def prompt=(str)
238
+ @prompt = str
239
+ end
240
+
241
+ def angry_prompt
242
+ @angry_prompt ||= Morpheus::Terminal.angry_prompt
243
+ end
244
+
245
+ # def gets
246
+ # Readline.completion_append_character = " "
247
+ # Readline.completion_proc = @auto_complete
248
+ # Readline.basic_word_break_characters = ""
249
+ # #Readline.basic_word_break_characters = "\t\n\"\‘`@$><=;|&{( "
250
+ # input = Readline.readline("#{@prompt}", true).to_s
251
+ # input = input.strip
252
+ # execute(input)
253
+ # end
254
+
255
+ # def puts(*cmds)
256
+ # cmds.each do |cmd|
257
+ # self.execute(cmd) # exec
258
+ # end
259
+ # end
260
+
261
+ # def gets(*args)
262
+ # $stdin.gets(*args)
263
+ # end
264
+
265
+ # protected
266
+
267
+ def execute(input)
268
+ exit_code = 0
269
+ err = nil
270
+ args = nil
271
+ if input.is_a? String
272
+ args = Shellwords.shellsplit(input)
273
+ elsif input.is_a?(Array)
274
+ args = input.dup
275
+ else
276
+ raise "terminal execute() expects a String to be split or an Array of String arguments and instead got (#{args.class}) #{args}"
277
+ end
278
+
279
+ # include Term::ANSIColor # tempting
280
+
281
+ # short circuit version switch
282
+ if args.length == 1
283
+ if args[0] == '-v' || args[0] == '--version'
284
+ @stdout.puts Morpheus::Cli::VERSION
285
+ return 0, nil
286
+ end
287
+ end
288
+
289
+ # looking for global help?
290
+ if args.length == 1
291
+ if args[0] == '-h' || args[0] == '--help' || args[0] == 'help'
292
+ @stdout.puts usage
293
+ return 0, nil
294
+ end
295
+ end
296
+
297
+
298
+ # process global options
299
+
300
+ # raise log level right away
301
+ if args.find {|it| it == '-V' || it == '--debug'}
302
+ @terminal_log_level = Morpheus::Logging::Logger::DEBUG
303
+ Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
304
+ ::RestClient.log = Morpheus::Logging.debug? ? Morpheus::Logging::DarkPrinter.instance : nil
305
+ end
306
+
307
+ # execute startup script .morpheus_profile unless --noprofile is passed
308
+ # todo: this should happen in initialize..
309
+ noprofile = false
310
+ if args.find {|it| it == '--noprofile' }
311
+ noprofile = true
312
+ args.delete_if {|it| it == '--noprofile' }
313
+ end
314
+
315
+ if @profile_dot_file && !@profile_dot_file_has_run
316
+ if !noprofile && File.exists?(@profile_dot_file.filename)
317
+ execute_profile_script()
318
+ end
319
+ end
320
+
321
+ # not enough arguments?
322
+ if args.count == 0
323
+ @stderr.puts "#{@angry_prompt}[command] argument is required."
324
+ #@stderr.puts "No command given, here's some help:"
325
+ @stderr.print usage
326
+ return 2, nil # CommandError.new("morpheus requires a command")
327
+ end
328
+
329
+ cmd_name = args[0]
330
+ cmd_args = args[1..-1]
331
+
332
+ # unknown command?
333
+ # all commands should be registered commands or aliases
334
+ if !(Morpheus::Cli::CliRegistry.has_command?(cmd_name) || Morpheus::Cli::CliRegistry.has_alias?(cmd_name))
335
+ @stderr.puts "#{@angry_prompt}'#{cmd_name}' is not a morpheus command. See 'morpheus --help'."
336
+ #@stderr.puts usage
337
+ return 127, nil
338
+ end
339
+
340
+ # ok, execute the command (or alias)
341
+ result = nil
342
+ begin
343
+ # shell is a Singleton command class
344
+ if args[0] == "shell"
345
+ result = Morpheus::Cli::Shell.instance.handle(args[1..-1])
346
+ else
347
+ result = Morpheus::Cli::CliRegistry.exec(args[0], args[1..-1])
348
+ end
349
+ # todo: clean up CliCommand return values, handle a few diff types for now
350
+ if result == nil || result == true || result == 0
351
+ exit_code = 0
352
+ elsif result == false
353
+ exit_code = 1
354
+ elsif result.is_a?(Array) # exit_code, err
355
+ exit_code = result[0] #.to_i
356
+ err = result[1]
357
+ else
358
+ exit_code = result #.to_i
359
+ end
360
+ rescue => e
361
+ exit_code = Morpheus::Cli::ErrorHandler.new(@stderr).handle_error(e)
362
+ err = e
363
+ end
364
+
365
+ return exit_code, err
366
+
367
+ end
368
+
369
+ end
370
+ end
371
+