morpheus-cli 3.5.2 → 3.5.3

Sign up to get free protection for your applications and to get access to all the features.
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