morpheus-cli 3.5.2 → 3.5.3

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +16 -0
  3. data/lib/morpheus/api/blueprints_interface.rb +84 -0
  4. data/lib/morpheus/api/execution_request_interface.rb +33 -0
  5. data/lib/morpheus/api/instances_interface.rb +21 -0
  6. data/lib/morpheus/api/packages_interface.rb +25 -5
  7. data/lib/morpheus/api/processes_interface.rb +34 -0
  8. data/lib/morpheus/api/roles_interface.rb +7 -0
  9. data/lib/morpheus/api/servers_interface.rb +8 -0
  10. data/lib/morpheus/api/user_settings_interface.rb +76 -0
  11. data/lib/morpheus/cli.rb +5 -1
  12. data/lib/morpheus/cli/alias_command.rb +1 -1
  13. data/lib/morpheus/cli/app_templates.rb +2 -1
  14. data/lib/morpheus/cli/apps.rb +173 -19
  15. data/lib/morpheus/cli/blueprints_command.rb +2134 -0
  16. data/lib/morpheus/cli/cli_command.rb +3 -1
  17. data/lib/morpheus/cli/clouds.rb +4 -10
  18. data/lib/morpheus/cli/coloring_command.rb +14 -8
  19. data/lib/morpheus/cli/containers_command.rb +92 -5
  20. data/lib/morpheus/cli/execution_request_command.rb +313 -0
  21. data/lib/morpheus/cli/hosts.rb +188 -7
  22. data/lib/morpheus/cli/instances.rb +472 -9
  23. data/lib/morpheus/cli/login.rb +1 -1
  24. data/lib/morpheus/cli/mixins/print_helper.rb +8 -0
  25. data/lib/morpheus/cli/mixins/processes_helper.rb +134 -0
  26. data/lib/morpheus/cli/option_types.rb +21 -16
  27. data/lib/morpheus/cli/packages_command.rb +469 -17
  28. data/lib/morpheus/cli/processes_command.rb +313 -0
  29. data/lib/morpheus/cli/remote.rb +20 -9
  30. data/lib/morpheus/cli/roles.rb +186 -6
  31. data/lib/morpheus/cli/shell.rb +10 -1
  32. data/lib/morpheus/cli/tasks.rb +4 -1
  33. data/lib/morpheus/cli/user_settings_command.rb +431 -0
  34. data/lib/morpheus/cli/version.rb +1 -1
  35. data/lib/morpheus/cli/whoami.rb +1 -1
  36. data/lib/morpheus/formatters.rb +14 -0
  37. data/lib/morpheus/morpkg.rb +119 -0
  38. data/morpheus-cli.gemspec +1 -0
  39. metadata +26 -2
@@ -3,14 +3,17 @@ require 'io/console'
3
3
  require 'rest_client'
4
4
  require 'optparse'
5
5
  require 'morpheus/cli/cli_command'
6
+ require 'morpheus/cli/mixins/accounts_helper'
6
7
  require 'morpheus/cli/mixins/provisioning_helper'
7
8
  require 'morpheus/cli/option_types'
8
9
  require 'json'
9
10
 
10
11
  class Morpheus::Cli::Hosts
11
12
  include Morpheus::Cli::CliCommand
13
+ include Morpheus::Cli::AccountsHelper
12
14
  include Morpheus::Cli::ProvisioningHelper
13
- register_subcommands :list, :count, :get, :stats, :add, :remove, :logs, :start, :stop, :resize, :run_workflow, {:'make-managed' => :install_agent}, :upgrade_agent, :server_types
15
+ register_subcommands :list, :count, :get, :stats, :add, :update, :remove, :logs, :start, :stop, :resize, :run_workflow, {:'make-managed' => :install_agent}, :upgrade_agent, :server_types
16
+ register_subcommands :exec => :execution_request
14
17
  alias_subcommand :details, :get
15
18
  set_default_subcommand :list
16
19
 
@@ -26,7 +29,9 @@ class Morpheus::Cli::Hosts
26
29
  @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
27
30
  @servers_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).servers
28
31
  @logs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).logs
32
+ @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
29
33
  @active_group_id = Morpheus::Cli::Groups.active_group
34
+ @execution_request_interface = @api_client.execution_request
30
35
  end
31
36
 
32
37
  def handle(args)
@@ -38,6 +43,9 @@ class Morpheus::Cli::Hosts
38
43
  params = {}
39
44
  optparse = Morpheus::Cli::OptionParser.new do |opts|
40
45
  opts.banner = subcommand_usage()
46
+ opts.on( '-a', '--account ACCOUNT', "Account Name or ID" ) do |val|
47
+ options[:account] = val
48
+ end
41
49
  opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
42
50
  options[:group] = val
43
51
  end
@@ -87,6 +95,15 @@ class Morpheus::Cli::Hosts
87
95
  optparse.parse!(args)
88
96
  connect(options)
89
97
  begin
98
+ account = nil
99
+ if options[:account]
100
+ account = find_account_by_name_or_id(options[:account])
101
+ if account.nil?
102
+ return 1
103
+ else
104
+ params['accountId'] = account['id']
105
+ end
106
+ end
90
107
  group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
91
108
  if group
92
109
  params['siteId'] = group['id']
@@ -127,6 +144,7 @@ class Morpheus::Cli::Hosts
127
144
  return 0
128
145
  else
129
146
  servers = json_response['servers']
147
+ multi_tenant = json_response['multiTenant'] == true
130
148
  title = "Morpheus Hosts"
131
149
  subtitles = []
132
150
  if group
@@ -164,6 +182,7 @@ class Morpheus::Cli::Hosts
164
182
  storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
165
183
  row = {
166
184
  id: server['id'],
185
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
167
186
  name: server['name'],
168
187
  platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
169
188
  cloud: server['zone'] ? server['zone']['name'] : '',
@@ -178,6 +197,9 @@ class Morpheus::Cli::Hosts
178
197
  row
179
198
  }
180
199
  columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
200
+ if multi_tenant
201
+ columns.insert(4, :tenant)
202
+ end
181
203
  term_width = current_terminal_width()
182
204
  if term_width > 170
183
205
  columns += [:cpu, :memory, :storage]
@@ -232,9 +254,9 @@ class Morpheus::Cli::Hosts
232
254
  options = {}
233
255
  optparse = Morpheus::Cli::OptionParser.new do |opts|
234
256
  opts.banner = subcommand_usage("[name]")
235
- opts.on('--refresh-until [status]', String, "Refresh until status is reached. Default status is provisioned.") do |val|
257
+ opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is provisioned.") do |val|
236
258
  if val.to_s.empty?
237
- options[:refresh_until_status] = "provisioned"
259
+ options[:refresh_until_status] = "provisioned,failed"
238
260
  else
239
261
  options[:refresh_until_status] = val.to_s.downcase
240
262
  end
@@ -312,10 +334,13 @@ class Morpheus::Cli::Hosts
312
334
  if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
313
335
  options[:refresh_interval] = 5
314
336
  end
315
- while server['status'].to_s.downcase != options[:refresh_until_status].to_s.downcase
337
+ statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
338
+ if !statuses.include?(server['status'])
316
339
  print cyan
317
- print "Refreshing until status #{options[:refresh_until_status]} ..."
318
- sleep(options[:refresh_interval])
340
+ print "Status is #{server['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
341
+ #sleep(options[:refresh_interval])
342
+ sleep_with_dots(options[:refresh_interval])
343
+ print "\n"
319
344
  _get(arg, options)
320
345
  end
321
346
  end
@@ -438,7 +463,7 @@ class Morpheus::Cli::Hosts
438
463
  when 'FATAL'
439
464
  log_level = "#{red}#{bold}FATAL#{reset}"
440
465
  end
441
- output << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message']}\n"
466
+ output << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message'].to_s.strip}\n"
442
467
  end
443
468
  end
444
469
  end
@@ -655,6 +680,80 @@ class Morpheus::Cli::Hosts
655
680
  end
656
681
  end
657
682
 
683
+ def update(args)
684
+ options = {}
685
+ params = {}
686
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
687
+ opts.banner = subcommand_usage("[name]")
688
+ opts.on('--name VALUE', String, "Name") do |val|
689
+ params['name'] = val == "null" ? nil : val
690
+ end
691
+ opts.on('--description VALUE', String, "Description") do |val|
692
+ params['description'] = val == "null" ? nil : val
693
+ end
694
+ opts.on('--ssh-username VALUE', String, "SSH Username") do |val|
695
+ params['sshUsername'] = val == "null" ? nil : val
696
+ end
697
+ opts.on('--ssh-password VALUE', String, "SSH Password") do |val|
698
+ params['sshPassword'] = val == "null" ? nil : val
699
+ end
700
+ opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
701
+ params['powerScheduleType'] = val == "null" ? nil : val
702
+ end
703
+ # opts.on('--created-by ID', String, "Created By User ID") do |val|
704
+ # params['createdById'] = val
705
+ # end
706
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
707
+ end
708
+ optparse.parse!(args)
709
+ if args.count != 1
710
+ puts optparse
711
+ return 1
712
+ end
713
+ connect(options)
714
+
715
+ begin
716
+ server = find_host_by_name_or_id(args[0])
717
+ return 1 if server.nil?
718
+ new_group = nil
719
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
720
+ payload = nil
721
+ if options[:payload]
722
+ payload = options[:payload]
723
+ # support args and option parameters on top of payload
724
+ if !params.empty?
725
+ payload['server'] ||= {}
726
+ payload['server'].deep_merge!(params)
727
+ end
728
+ else
729
+ if params.empty?
730
+ print_red_alert "Specify atleast one option to update"
731
+ puts optparse
732
+ return 1
733
+ end
734
+ payload = {}
735
+ payload['server'] = params
736
+ end
737
+
738
+ if options[:dry_run]
739
+ print_dry_run @servers_interface.dry.update(server["id"], payload)
740
+ return
741
+ end
742
+ json_response = @servers_interface.update(server["id"], payload)
743
+
744
+ if options[:json]
745
+ puts as_json(json_response, options)
746
+ else
747
+ print_green_success "Updated host #{server['name']}"
748
+ get([server['id']])
749
+ end
750
+ return 0
751
+ rescue RestClient::Exception => e
752
+ print_rest_exception(e, options)
753
+ exit 1
754
+ end
755
+ end
756
+
658
757
  def remove(args)
659
758
  options = {}
660
759
  query_params = {}
@@ -1033,6 +1132,88 @@ class Morpheus::Cli::Hosts
1033
1132
  end
1034
1133
  end
1035
1134
 
1135
+ def execution_request(args)
1136
+ options = {}
1137
+ params = {}
1138
+ script_content = nil
1139
+ do_refresh = true
1140
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1141
+ opts.banner = subcommand_usage("[id] [options]")
1142
+ opts.on('--script SCRIPT', "Script to be executed" ) do |val|
1143
+ script_content = val
1144
+ end
1145
+ opts.on('--file FILE', "File containing the script. This can be used instead of --script" ) do |filename|
1146
+ full_filename = File.expand_path(filename)
1147
+ if File.exists?(full_filename)
1148
+ script_content = File.read(full_filename)
1149
+ else
1150
+ print_red_alert "File not found: #{full_filename}"
1151
+ exit 1
1152
+ end
1153
+ end
1154
+ opts.on(nil, '--no-refresh', "Do not refresh until finished" ) do
1155
+ do_refresh = false
1156
+ end
1157
+ #build_option_type_options(opts, options, add_user_source_option_types())
1158
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
1159
+ opts.footer = "Execute an arbitrary command or script on a host." + "\n" +
1160
+ "[id] is required. This is the id a host." + "\n" +
1161
+ "[script] is required. This is the script that is to be executed."
1162
+ end
1163
+ optparse.parse!(args)
1164
+ connect(options)
1165
+ if args.count != 1
1166
+ print_error Morpheus::Terminal.angry_prompt
1167
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
1168
+ return 1
1169
+ end
1170
+
1171
+
1172
+ begin
1173
+ host = find_host_by_name_or_id(args[0])
1174
+ return 1 if host.nil?
1175
+ params['serverId'] = host['id']
1176
+ # construct payload
1177
+ payload = {}
1178
+ if options[:payload]
1179
+ payload = options[:payload]
1180
+ else
1181
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
1182
+ # prompt for Script
1183
+ if script_content.nil?
1184
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'script', 'type' => 'code-editor', 'fieldLabel' => 'Script', 'required' => true, 'description' => 'The script content'}], options[:options])
1185
+ script_content = v_prompt['script']
1186
+ end
1187
+ payload['script'] = script_content
1188
+ end
1189
+ # dry run?
1190
+ if options[:dry_run]
1191
+ print_dry_run @execution_request_interface.dry.create(params, payload)
1192
+ return 0
1193
+ end
1194
+ # do it
1195
+ json_response = @execution_request_interface.create(params, payload)
1196
+ # print and return result
1197
+ if options[:quiet]
1198
+ return 0
1199
+ elsif options[:json]
1200
+ puts as_json(json_response, options)
1201
+ return 0
1202
+ end
1203
+ execution_request = json_response['executionRequest']
1204
+ print_green_success "Executing request #{execution_request['uniqueId']}"
1205
+ if do_refresh
1206
+ Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_request['uniqueId'], "--refresh"])
1207
+ else
1208
+ Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_request['uniqueId']])
1209
+ end
1210
+ return 0
1211
+ rescue RestClient::Exception => e
1212
+ print_rest_exception(e, options)
1213
+ exit 1
1214
+ end
1215
+ end
1216
+
1036
1217
  private
1037
1218
 
1038
1219
  def find_host_by_id(id)
@@ -5,13 +5,16 @@ require 'filesize'
5
5
  require 'table_print'
6
6
  require 'morpheus/cli/cli_command'
7
7
  require 'morpheus/cli/mixins/provisioning_helper'
8
+ require 'morpheus/cli/mixins/processes_helper'
8
9
  require 'morpheus/cli/option_types'
9
10
 
10
11
  class Morpheus::Cli::Instances
11
12
  include Morpheus::Cli::CliCommand
12
13
  include Morpheus::Cli::ProvisioningHelper
14
+ include Morpheus::Cli::ProcessesHelper
13
15
 
14
- register_subcommands :list, :count, :get, :add, :update, :update_notes, :remove, :logs, :stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :backup, :backups, :stop_service, :start_service, :restart_service, :resize, :clone, :envs, :setenv, :delenv, :security_groups, :apply_security_groups, :firewall_enable, :firewall_disable, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
16
+ register_subcommands :list, :count, :get, :add, :update, :update_notes, :remove, :logs, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}, :stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :backup, :backups, :stop_service, :start_service, :restart_service, :resize, :clone, :envs, :setenv, :delenv, :security_groups, :apply_security_groups, :firewall_enable, :firewall_disable, :run_workflow, :import_snapshot, :console, :status_check, {:containers => :list_containers}, :scaling, {:'scaling-update' => :scaling_update}
17
+ register_subcommands :exec => :execution_request
15
18
  # register_subcommands {:'lb-update' => :load_balancer_update}
16
19
  alias_subcommand :details, :get
17
20
  set_default_subcommand :list
@@ -32,6 +35,7 @@ class Morpheus::Cli::Instances
32
35
  @provision_types_interface = @api_client.provision_types
33
36
  @options_interface = @api_client.options
34
37
  @active_group_id = Morpheus::Cli::Groups.active_group
38
+ @execution_request_interface = @api_client.execution_request
35
39
  end
36
40
 
37
41
  def handle(args)
@@ -229,7 +233,7 @@ class Morpheus::Cli::Instances
229
233
  options[:layout_size] = val.to_i
230
234
  end
231
235
  opts.on("--workflow ID", String, "Automation: Workflow ID") do |val|
232
- options[:workflow_id] = val.to_i
236
+ options[:workflow_id] = val
233
237
  end
234
238
  # opts.on('-L', "--lb", "Enable Load Balancer") do
235
239
  # options[:enable_load_balancer] = true
@@ -314,7 +318,11 @@ class Morpheus::Cli::Instances
314
318
  payload['instance']['userGroup'] = {'id' => options[:user_group_id] }
315
319
  end
316
320
  if options[:workflow_id]
317
- payload['taskSetId'] = options[:workflow_id]
321
+ if options[:workflow_id].to_s =~ /\A\d{1,}\Z/
322
+ payload['taskSetId'] = options[:workflow_id].to_i
323
+ else
324
+ payload['taskSetName'] = options[:workflow_id]
325
+ end
318
326
  end
319
327
  if options[:enable_load_balancer]
320
328
  lb_payload = prompt_instance_load_balancer(payload['instance'], nil, options)
@@ -722,7 +730,7 @@ class Morpheus::Cli::Instances
722
730
  def get(args)
723
731
  options = {}
724
732
  optparse = Morpheus::Cli::OptionParser.new do |opts|
725
- opts.banner = subcommand_usage("[name]")
733
+ opts.banner = subcommand_usage("[instance]")
726
734
  opts.on( nil, '--containers', "Display Instance Containers" ) do
727
735
  options[:include_containers] = true
728
736
  end
@@ -735,9 +743,9 @@ class Morpheus::Cli::Instances
735
743
  opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
736
744
  options[:include_scaling] = true
737
745
  end
738
- opts.on('--refresh-until [status]', String, "Refresh until status is reached. Default status is running.") do |val|
746
+ opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is running.") do |val|
739
747
  if val.to_s.empty?
740
- options[:refresh_until_status] = "running"
748
+ options[:refresh_until_status] = "running,failed"
741
749
  else
742
750
  options[:refresh_until_status] = val.to_s.downcase
743
751
  end
@@ -752,6 +760,8 @@ class Morpheus::Cli::Instances
752
760
  # options[:include_lb] = true
753
761
  # end
754
762
  build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
763
+ opts.footer = "Get details about an instance.\n" +
764
+ "[instance] is required. This is the name or id of an instance. Supports 1-N [instance] arguments."
755
765
  end
756
766
  optparse.parse!(args)
757
767
  if args.count < 1
@@ -945,10 +955,13 @@ class Morpheus::Cli::Instances
945
955
  if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
946
956
  options[:refresh_interval] = 5
947
957
  end
948
- while instance['status'].to_s.downcase != options[:refresh_until_status].to_s.downcase
958
+ statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
959
+ if !statuses.include?(instance['status'])
949
960
  print cyan
950
- print "Refreshing until status #{options[:refresh_until_status]} ..."
951
- sleep(options[:refresh_interval])
961
+ print "Status is #{instance['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
962
+ #sleep(options[:refresh_interval])
963
+ sleep_with_dots(options[:refresh_interval])
964
+ print "\n"
952
965
  _get(arg, options)
953
966
  end
954
967
  end
@@ -2434,6 +2447,396 @@ class Morpheus::Cli::Instances
2434
2447
  end
2435
2448
  end
2436
2449
 
2450
+ def history(args)
2451
+ raw_args = args.dup
2452
+ options = {}
2453
+ #options[:show_output] = true
2454
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2455
+ opts.banner = subcommand_usage("[instance]")
2456
+ # opts.on( '-n', '--node NODE_ID', "Scope history to specific Container or VM" ) do |node_id|
2457
+ # options[:node_id] = node_id.to_i
2458
+ # end
2459
+ opts.on( nil, '--events', "Display sub processes (events)." ) do
2460
+ options[:show_events] = true
2461
+ end
2462
+ opts.on( nil, '--output', "Display process output." ) do
2463
+ options[:show_output] = true
2464
+ end
2465
+ opts.on('--process-id ID', String, "Display details about a specfic process only." ) do |val|
2466
+ options[:process_id] = val
2467
+ end
2468
+ opts.on('--event-id ID', String, "Display details about a specfic process event only." ) do |val|
2469
+ options[:event_id] = val
2470
+ end
2471
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2472
+ opts.footer = "List historical processes for a specific instance.\n" +
2473
+ "[instance] is required. This is the name or id of an instance."
2474
+ end
2475
+ optparse.parse!(args)
2476
+
2477
+ # shortcut to other actions
2478
+ if options[:process_id]
2479
+ return history_details(raw_args)
2480
+ elsif options[:event_id]
2481
+ return history_event_details(raw_args)
2482
+ end
2483
+
2484
+ if args.count != 1
2485
+ puts optparse
2486
+ return 1
2487
+ end
2488
+ connect(options)
2489
+ begin
2490
+ instance = find_instance_by_name_or_id(args[0])
2491
+ # container_ids = instance['containers']
2492
+ # if options[:node_id] && container_ids.include?(options[:node_id])
2493
+ # container_ids = [options[:node_id]]
2494
+ # end
2495
+ params = {}
2496
+ params.merge!(parse_list_options(options))
2497
+ # params[:query] = params.delete(:phrase) unless params[:phrase].nil?
2498
+ if options[:dry_run]
2499
+ print_dry_run @instances_interface.dry.history(instance['id'], params)
2500
+ return
2501
+ end
2502
+ json_response = @instances_interface.history(instance['id'], params)
2503
+ if options[:json]
2504
+ puts as_json(json_response, options, "processes")
2505
+ return 0
2506
+ elsif options[:yaml]
2507
+ puts as_yaml(json_response, options, "processes")
2508
+ return 0
2509
+ elsif options[:csv]
2510
+ puts records_as_csv(json_response['processes'], options)
2511
+ return 0
2512
+ else
2513
+
2514
+ title = "Instance History: #{instance['name']}"
2515
+ subtitles = []
2516
+ if params[:query]
2517
+ subtitles << "Search: #{params[:query]}".strip
2518
+ end
2519
+ subtitles += parse_list_subtitles(options)
2520
+ print_h1 title, subtitles
2521
+ if json_response['processes'].empty?
2522
+ print "#{cyan}No process history found.#{reset}\n\n"
2523
+ else
2524
+ history_records = []
2525
+ json_response["processes"].each do |process|
2526
+ row = {
2527
+ id: process['id'],
2528
+ eventId: nil,
2529
+ uniqueId: process['uniqueId'],
2530
+ name: process['displayName'],
2531
+ description: process['description'],
2532
+ processType: process['processType'] ? (process['processType']['name'] || process['processType']['code']) : process['processTypeName'],
2533
+ createdBy: process['createdBy'] ? (process['createdBy']['displayName'] || process['createdBy']['username']) : '',
2534
+ startDate: format_local_dt(process['startDate']),
2535
+ duration: format_process_duration(process),
2536
+ status: format_process_status(process),
2537
+ error: format_process_error(process),
2538
+ output: format_process_output(process)
2539
+ }
2540
+ history_records << row
2541
+ process_events = process['events'] || process['processEvents']
2542
+ if options[:show_events]
2543
+ if process_events
2544
+ process_events.each do |process_event|
2545
+ event_row = {
2546
+ id: process['id'],
2547
+ eventId: process_event['id'],
2548
+ uniqueId: process_event['uniqueId'],
2549
+ name: process_event['displayName'], # blank like the UI
2550
+ description: process_event['description'],
2551
+ processType: process_event['processType'] ? (process_event['processType']['name'] || process_event['processType']['code']) : process['processTypeName'],
2552
+ createdBy: process_event['createdBy'] ? (process_event['createdBy']['displayName'] || process_event['createdBy']['username']) : '',
2553
+ startDate: format_local_dt(process_event['startDate']),
2554
+ duration: format_process_duration(process_event),
2555
+ status: format_process_status(process_event),
2556
+ error: format_process_error(process_event),
2557
+ output: format_process_output(process_event)
2558
+ }
2559
+ history_records << event_row
2560
+ end
2561
+ else
2562
+
2563
+ end
2564
+ end
2565
+ end
2566
+ columns = [
2567
+ {:id => {:display_name => "PROCESS ID"} },
2568
+ :name,
2569
+ :description,
2570
+ {:processType => {:display_name => "PROCESS TYPE"} },
2571
+ {:createdBy => {:display_name => "CREATED BY"} },
2572
+ {:startDate => {:display_name => "START DATE"} },
2573
+ {:duration => {:display_name => "ETA/DURATION"} },
2574
+ :status,
2575
+ :error
2576
+ ]
2577
+ if options[:show_events]
2578
+ columns.insert(1, {:eventId => {:display_name => "EVENT ID"} })
2579
+ end
2580
+ if options[:show_output]
2581
+ columns << :output
2582
+ end
2583
+ # custom pretty table columns ...
2584
+ if options[:include_fields]
2585
+ columns = options[:include_fields]
2586
+ end
2587
+ print cyan
2588
+ print as_pretty_table(history_records, columns, options)
2589
+ #print_results_pagination(json_response)
2590
+ print_results_pagination(json_response, {:label => "process", :n_label => "processes"})
2591
+ print reset, "\n"
2592
+ return 0
2593
+ end
2594
+ end
2595
+ rescue RestClient::Exception => e
2596
+ print_rest_exception(e, options)
2597
+ exit 1
2598
+ end
2599
+ end
2600
+
2601
+ def history_details(args)
2602
+ options = {}
2603
+ process_id = nil
2604
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2605
+ opts.banner = subcommand_usage("[instance] [process-id]")
2606
+ opts.on('--process-id ID', String, "Display details about a specfic event." ) do |val|
2607
+ options[:process_id] = val
2608
+ end
2609
+ opts.add_hidden_option('process-id')
2610
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2611
+ opts.footer = "Display history details for a specific process.\n" +
2612
+ "[instance] is required. This is the name or id of an instance.\n" +
2613
+ "[process-id] is required. This is the id of the process."
2614
+ end
2615
+ optparse.parse!(args)
2616
+ if args.count == 2
2617
+ process_id = args[1]
2618
+ elsif args.count == 1 && options[:process_id]
2619
+ process_id = options[:process_id]
2620
+ else
2621
+ puts_error optparse
2622
+ return 1
2623
+ end
2624
+ connect(options)
2625
+ begin
2626
+ instance = find_instance_by_name_or_id(args[0])
2627
+ params = {}
2628
+ params.merge!(parse_list_options(options))
2629
+ params[:query] = params.delete(:phrase) unless params[:phrase].nil?
2630
+ if options[:dry_run]
2631
+ print_dry_run @instances_interface.dry.history_details(instance['id'], process_id, params)
2632
+ return
2633
+ end
2634
+ json_response = @instances_interface.history_details(instance['id'], process_id, params)
2635
+ if options[:json]
2636
+ puts as_json(json_response, options, "process")
2637
+ return 0
2638
+ elsif options[:yaml]
2639
+ puts as_yaml(json_response, options, "process")
2640
+ return 0
2641
+ elsif options[:csv]
2642
+ puts records_as_csv(json_response['process'], options)
2643
+ return 0
2644
+ else
2645
+ process = json_response["process"]
2646
+ title = "Instance History Details"
2647
+ subtitles = []
2648
+ subtitles << " Process ID: #{process_id}"
2649
+ subtitles += parse_list_subtitles(options)
2650
+ print_h1 title, subtitles
2651
+ print_process_details(process)
2652
+
2653
+ print_h2 "Process Events"
2654
+ process_events = process['events'] || process['processEvents'] || []
2655
+ history_records = []
2656
+ if process_events.empty?
2657
+ puts "#{cyan}No events found.#{reset}"
2658
+ else
2659
+ process_events.each do |process_event|
2660
+ event_row = {
2661
+ id: process_event['id'],
2662
+ eventId: process_event['id'],
2663
+ uniqueId: process_event['uniqueId'],
2664
+ name: process_event['displayName'], # blank like the UI
2665
+ description: process_event['description'],
2666
+ processType: process_event['processType'] ? (process_event['processType']['name'] || process_event['processType']['code']) : process['processTypeName'],
2667
+ createdBy: process_event['createdBy'] ? (process_event['createdBy']['displayName'] || process_event['createdBy']['username']) : '',
2668
+ startDate: format_local_dt(process_event['startDate']),
2669
+ duration: format_process_duration(process_event),
2670
+ status: format_process_status(process_event),
2671
+ error: format_process_error(process_event),
2672
+ output: format_process_output(process_event)
2673
+ }
2674
+ history_records << event_row
2675
+ end
2676
+ columns = [
2677
+ {:id => {:display_name => "EVENT ID"} },
2678
+ :name,
2679
+ :description,
2680
+ {:processType => {:display_name => "PROCESS TYPE"} },
2681
+ {:createdBy => {:display_name => "CREATED BY"} },
2682
+ {:startDate => {:display_name => "START DATE"} },
2683
+ {:duration => {:display_name => "ETA/DURATION"} },
2684
+ :status,
2685
+ :error,
2686
+ :output
2687
+ ]
2688
+ print cyan
2689
+ print as_pretty_table(history_records, columns, options)
2690
+ print_results_pagination({size: process_events.size, total: process_events.size})
2691
+ print reset, "\n"
2692
+ return 0
2693
+ end
2694
+ end
2695
+ rescue RestClient::Exception => e
2696
+ print_rest_exception(e, options)
2697
+ exit 1
2698
+ end
2699
+ end
2700
+
2701
+ def history_event_details(args)
2702
+ options = {}
2703
+ process_event_id = nil
2704
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2705
+ opts.banner = subcommand_usage("[instance] [event-id]")
2706
+ opts.on('--event-id ID', String, "Display details about a specfic event." ) do |val|
2707
+ options[:event_id] = val
2708
+ end
2709
+ opts.add_hidden_option('event-id')
2710
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2711
+ opts.footer = "Display history details for a specific process event.\n" +
2712
+ "[instance] is required. This is the name or id of an instance.\n" +
2713
+ "[event-id] is required. This is the id of the process event."
2714
+ end
2715
+ optparse.parse!(args)
2716
+ if args.count == 2
2717
+ process_event_id = args[1]
2718
+ elsif args.count == 1 && options[:event_id]
2719
+ process_event_id = options[:event_id]
2720
+ else
2721
+ puts_error optparse
2722
+ return 1
2723
+ end
2724
+ connect(options)
2725
+ begin
2726
+ instance = find_instance_by_name_or_id(args[0])
2727
+ params = {}
2728
+ params.merge!(parse_list_options(options))
2729
+ if options[:dry_run]
2730
+ print_dry_run @instances_interface.dry.history_event_details(instance['id'], process_event_id, params)
2731
+ return
2732
+ end
2733
+ json_response = @instances_interface.history_event_details(instance['id'], process_event_id, params)
2734
+ if options[:json]
2735
+ puts as_json(json_response, options, "processEvent")
2736
+ return 0
2737
+ elsif options[:yaml]
2738
+ puts as_yaml(json_response, options, "processEvent")
2739
+ return 0
2740
+ elsif options[:csv]
2741
+ puts records_as_csv(json_response['processEvent'], options)
2742
+ return 0
2743
+ else
2744
+ process_event = json_response['processEvent'] || json_response['event']
2745
+ title = "Instance History Event"
2746
+ subtitles = []
2747
+ subtitles += parse_list_subtitles(options)
2748
+ print_h1 title, subtitles
2749
+ print_process_event_details(process_event)
2750
+ print reset, "\n"
2751
+ return 0
2752
+ end
2753
+ rescue RestClient::Exception => e
2754
+ print_rest_exception(e, options)
2755
+ exit 1
2756
+ end
2757
+ end
2758
+
2759
+ def execution_request(args)
2760
+ options = {}
2761
+ params = {}
2762
+ script_content = nil
2763
+ do_refresh = true
2764
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
2765
+ opts.banner = subcommand_usage("[id] [options]")
2766
+ opts.on('--script SCRIPT', "Script to be executed" ) do |val|
2767
+ script_content = val
2768
+ end
2769
+ opts.on('--file FILE', "File containing the script. This can be used instead of --script" ) do |filename|
2770
+ full_filename = File.expand_path(filename)
2771
+ if File.exists?(full_filename)
2772
+ script_content = File.read(full_filename)
2773
+ else
2774
+ print_red_alert "File not found: #{full_filename}"
2775
+ exit 1
2776
+ end
2777
+ end
2778
+ opts.on(nil, '--no-refresh', "Do not refresh until finished" ) do
2779
+ do_refresh = false
2780
+ end
2781
+ #build_option_type_options(opts, options, add_user_source_option_types())
2782
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
2783
+ opts.footer = "Execute an arbitrary script or command on an instance." + "\n" +
2784
+ "[id] is required. This is the id or name of an instance." + "\n" +
2785
+ "[script] is required. This is the script that is to be executed."
2786
+ end
2787
+ optparse.parse!(args)
2788
+ connect(options)
2789
+ if args.count != 1
2790
+ print_error Morpheus::Terminal.angry_prompt
2791
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
2792
+ return 1
2793
+ end
2794
+
2795
+ begin
2796
+ instance = find_instance_by_name_or_id(args[0])
2797
+ return 1 if instance.nil?
2798
+ params['instanceId'] = instance['id']
2799
+ # construct payload
2800
+ payload = {}
2801
+ if options[:payload]
2802
+ payload = options[:payload]
2803
+ else
2804
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
2805
+ # prompt for Script
2806
+ if script_content.nil?
2807
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'script', 'type' => 'code-editor', 'fieldLabel' => 'Script', 'required' => true, 'description' => 'The script content'}], options[:options])
2808
+ script_content = v_prompt['script']
2809
+ end
2810
+ payload['script'] = script_content
2811
+ end
2812
+ # dry run?
2813
+ if options[:dry_run]
2814
+ print_dry_run @execution_request_interface.dry.create(params, payload)
2815
+ return 0
2816
+ end
2817
+ # do it
2818
+ json_response = @execution_request_interface.create(params, payload)
2819
+ # print and return result
2820
+ if options[:quiet]
2821
+ return 0
2822
+ elsif options[:json]
2823
+ puts as_json(json_response, options)
2824
+ return 0
2825
+ end
2826
+ execution_request = json_response['executionRequest']
2827
+ print_green_success "Executing request #{execution_request['uniqueId']}"
2828
+ if do_refresh
2829
+ Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_request['uniqueId'], "--refresh"])
2830
+ else
2831
+ Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_request['uniqueId']])
2832
+ end
2833
+ return 0
2834
+ rescue RestClient::Exception => e
2835
+ print_rest_exception(e, options)
2836
+ exit 1
2837
+ end
2838
+ end
2839
+
2437
2840
  private
2438
2841
 
2439
2842
  def find_zone_by_name_or_id(group_id, val)
@@ -2661,6 +3064,66 @@ private
2661
3064
  print_description_list(description_cols, instance_threshold)
2662
3065
  end
2663
3066
 
3067
+ def print_process_details(process)
3068
+ description_cols = {
3069
+ "Process ID" => lambda {|it| it['id'] },
3070
+ "Name" => lambda {|it| it['displayName'] },
3071
+ "Description" => lambda {|it| it['description'] },
3072
+ "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
3073
+ "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
3074
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
3075
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
3076
+ "Duration" => lambda {|it| format_process_duration(it) },
3077
+ "Status" => lambda {|it| format_process_status(it) },
3078
+ # "# Events" => lambda {|it| (it['events'] || []).size() },
3079
+ }
3080
+ print_description_list(description_cols, process)
3081
+
3082
+ if process['error']
3083
+ print_h2 "Error"
3084
+ print reset
3085
+ #puts format_process_error(process_event)
3086
+ puts process['error'].to_s.strip
3087
+ end
3088
+
3089
+ if process['output']
3090
+ print_h2 "Output"
3091
+ print reset
3092
+ #puts format_process_error(process_event)
3093
+ puts process['output'].to_s.strip
3094
+ end
3095
+ end
3096
+
3097
+ def print_process_event_details(process_event)
3098
+ # process_event =~ process
3099
+ description_cols = {
3100
+ "Process ID" => lambda {|it| it['processId'] },
3101
+ "Event ID" => lambda {|it| it['id'] },
3102
+ "Name" => lambda {|it| it['displayName'] },
3103
+ "Description" => lambda {|it| it['description'] },
3104
+ "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
3105
+ "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
3106
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
3107
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
3108
+ "Duration" => lambda {|it| format_process_duration(it) },
3109
+ "Status" => lambda {|it| format_process_status(it) },
3110
+ }
3111
+ print_description_list(description_cols, process_event)
3112
+
3113
+ if process_event['error']
3114
+ print_h2 "Error"
3115
+ print reset
3116
+ #puts format_process_error(process_event)
3117
+ puts process_event['error'].to_s.strip
3118
+ end
3119
+
3120
+ if process_event['output']
3121
+ print_h2 "Output"
3122
+ print reset
3123
+ #puts format_process_error(process_event)
3124
+ puts process_event['output'].to_s.strip
3125
+ end
3126
+ end
2664
3127
 
2665
3128
 
2666
3129
  end