morpheus-cli 2.10.3 → 2.11.0

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