morpheus-cli 4.1.7 → 4.1.8

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.
@@ -3,9 +3,9 @@ require 'morpheus/cli/cli_command'
3
3
  class Morpheus::Cli::BackupSettingsCommand
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::AccountsHelper
6
- set_command_hidden
7
- set_command_name :'backup-settings'
8
6
 
7
+ set_command_name :'backup-settings'
8
+ set_command_hidden
9
9
  register_subcommands :get, :update
10
10
  set_default_subcommand :get
11
11
 
@@ -58,7 +58,7 @@ class Morpheus::Cli::BackupSettingsCommand
58
58
  print_h1 "Backup Settings"
59
59
  print cyan
60
60
  description_cols = {
61
- "Backups Enabled" => lambda {|it| format_boolean(it['enabled']) },
61
+ "Scheduled Backups" => lambda {|it| format_boolean(it['backupsEnabled']) },
62
62
  "Create Backups" => lambda {|it| format_boolean(it['createBackups']) },
63
63
  "Backup Appliance" => lambda {|it| format_boolean(it['backupAppliance']) },
64
64
  "Default Backup Bucket" => lambda {|it| it['defaultStorageBucket'] ? it['defaultStorageBucket']['name'] : '' },
@@ -80,8 +80,8 @@ class Morpheus::Cli::BackupSettingsCommand
80
80
 
81
81
  optparse = Morpheus::Cli::OptionParser.new do |opts|
82
82
  opts.banner = opts.banner = subcommand_usage()
83
- opts.on('-a', '--active [on|off]', String, "Can be used to enable / disable the backups. Default is on") do |val|
84
- params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
83
+ opts.on('-a', '--active [on|off]', String, "Can be used to enable / disable the scheduled backups. Default is on") do |val|
84
+ params['backupsEnabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
85
85
  end
86
86
  opts.on("--create-backups [on|off]", String, "Can be used to enable / disable create backups. Default is on") do |val|
87
87
  params['createBackups'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
@@ -137,9 +137,7 @@ class Morpheus::Cli::BackupSettingsCommand
137
137
  end
138
138
 
139
139
  if !options[:storageBucket].nil?
140
- storage_bucket = @storage_providers.list['storageBuckets'].find do |it|
141
- it['name'] == options[:storageBucket].to_s || it['bucketName'] == options[:storageBucket].to_s || it['id'] == options[:storageBucket].to_i
142
- end
140
+ storage_bucket = find_storage_bucket_by_name_or_id(options[:storageBucket])
143
141
  if storage_bucket.nil?
144
142
  print_red_alert "Storage bucket not found for #{options[:storageBucket]}"
145
143
  return 1
@@ -178,4 +176,9 @@ class Morpheus::Cli::BackupSettingsCommand
178
176
  end
179
177
  end
180
178
 
179
+ private
180
+
181
+ def find_storage_bucket_by_name_or_id(val)
182
+ (val.to_s =~ /\A\d{1,}\Z/) ? @storage_providers.get(val)['storageBucket'] : @storage_providers.list({'name' => val})['storageBuckets'].first
183
+ end
181
184
  end
@@ -435,7 +435,7 @@ module Morpheus
435
435
  opts.on( '-r', '--remote REMOTE', "Remote name. The current remote is used by default." ) do |val|
436
436
  options[:remote] = val
437
437
  end
438
- opts.on( nil, '--remote-url URL', "Remote url. The current remote url is used by default." ) do |val|
438
+ opts.on( '--remote-url URL', '--remote-url URL', "Remote url. This allows adhoc requests instead of using a configured remote." ) do |val|
439
439
  options[:remote_url] = val
440
440
  end
441
441
  opts.on( '-T', '--token TOKEN', "Access token for authentication with --remote. Saved credentials are used by default." ) do |val|
@@ -423,7 +423,7 @@ class Morpheus::Cli::Clouds
423
423
  opts.on( '-f', '--force', "Force Remove" ) do
424
424
  query_params[:force] = 'on'
425
425
  end
426
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
426
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
427
427
  end
428
428
  optparse.parse!(args)
429
429
  if args.count < 1
@@ -437,6 +437,10 @@ class Morpheus::Cli::Clouds
437
437
  exit
438
438
  end
439
439
  @clouds_interface.setopts(options)
440
+ if options[:dry_run]
441
+ print_dry_run @clouds_interface.dry.destroy(cloud['id'], query_params)
442
+ return 0
443
+ end
440
444
  json_response = @clouds_interface.destroy(cloud['id'], query_params)
441
445
  if options[:json]
442
446
  print JSON.pretty_generate(json_response)
@@ -445,6 +449,7 @@ class Morpheus::Cli::Clouds
445
449
  print_green_success "Removed cloud #{cloud['name']}"
446
450
  #list([])
447
451
  end
452
+ return 0
448
453
  rescue RestClient::Exception => e
449
454
  print_rest_exception(e, options)
450
455
  exit 1
@@ -2540,13 +2540,14 @@ class Morpheus::Cli::Clusters
2540
2540
  name: ds['name'],
2541
2541
  type: ds['type'],
2542
2542
  capacity: format_bytes_short(ds['freeSpace']).strip,
2543
- online: format_boolean(ds['online']),
2543
+ online: (ds['online'] == false ? red : '') + format_boolean(ds['online']) + cyan,
2544
+ active: format_boolean(ds['active']),
2544
2545
  visibility: ds['visibility'].nil? ? '' : ds['visibility'].to_s.capitalize,
2545
2546
  tenants: ds['tenants'].nil? ? '' : ds['tenants'].collect {|it| it['name']}.join(', ')
2546
2547
  }
2547
2548
  end
2548
2549
  columns = [
2549
- :id, :name, :type, :capacity, :online, :visibility, :tenants
2550
+ :id, :name, :type, :capacity, :online, :active, :visibility, :tenants
2550
2551
  ]
2551
2552
  print as_pretty_table(rows, columns, options)
2552
2553
  end
@@ -2562,7 +2563,7 @@ class Morpheus::Cli::Clusters
2562
2563
  options = {}
2563
2564
  optparse = Morpheus::Cli::OptionParser.new do |opts|
2564
2565
  opts.banner = subcommand_usage( "[cluster] [datastore]")
2565
- build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2566
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
2566
2567
  opts.footer = "Get details about a cluster datastore.\n" +
2567
2568
  "[cluster] is required. This is the name or id of an existing cluster.\n" +
2568
2569
  "[datastore] is required. This is the name or id of an existing datastore."
@@ -2600,7 +2601,8 @@ class Morpheus::Cli::Clusters
2600
2601
  "Name" => 'name',
2601
2602
  "Type" => 'type',
2602
2603
  "Capacity" => lambda { |it| format_bytes_short(it['freeSpace']).strip },
2603
- "Online" => lambda { |it| format_boolean(it['online']) },
2604
+ "Online" => lambda { |it| (it['online'] == false ? red : '') + format_boolean(it['online']) + cyan },
2605
+ "Active" => lambda { |it| format_boolean(it['active']) },
2604
2606
  "Visibility" => lambda { |it| it['visibility'].nil? ? '' : it['visibility'].to_s.capitalize },
2605
2607
  "Tenants" => lambda { |it| it['tenants'].nil? ? '' : it['tenants'].collect {|it| it['name']}.join(', ') },
2606
2608
  "Cluster" => lambda { |it| cluster['name'] }
@@ -3695,7 +3697,7 @@ class Morpheus::Cli::Clusters
3695
3697
  service_plan = available_service_plans.find {|sp| sp['id'] == options[:servicePlan].to_i || sp['name'] == options[:servicePlan] || sp['code'] == options[:servicePlan] }
3696
3698
  else
3697
3699
  if available_service_plans.count > 1 && !options[:no_prompt]
3698
- service_plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => available_service_plans, 'required' => true, 'description' => 'Select Plan.'}],options[:options],@api_client,{})['servicePlan'].to_i
3700
+ service_plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => available_service_plans, 'required' => true, 'description' => 'Select Plan.'}],options[:options],@api_client,{},options[:no_prompt],true)['servicePlan'].to_i
3699
3701
  else
3700
3702
  service_plan_id = available_service_plans.first['id']
3701
3703
  end
@@ -3977,8 +3979,8 @@ class Morpheus::Cli::Clusters
3977
3979
 
3978
3980
  # Group Access
3979
3981
  if !excludes.include?('groupAccess')
3980
- if options[:groupAccessAll]
3981
- all_groups = true
3982
+ if !options[:groupAccessAll].nil?
3983
+ all_groups = options[:groupAccessAll]
3982
3984
  end
3983
3985
 
3984
3986
  if !options[:groupAccessList].empty?
@@ -4023,8 +4025,8 @@ class Morpheus::Cli::Clusters
4023
4025
 
4024
4026
  # Plan Access
4025
4027
  if !excludes.include?('planAccess')
4026
- if options[:planAccessAll]
4027
- all_plans = true
4028
+ if !options[:planAccessAll].nil?
4029
+ all_plans = options[:planAccessAll]
4028
4030
  end
4029
4031
 
4030
4032
  if !options[:planAccessList].empty?
@@ -55,9 +55,9 @@ class Morpheus::Cli::Groups
55
55
  groups = json_response['groups']
56
56
  subtitles = []
57
57
  subtitles += parse_list_subtitles(options)
58
- print_h1 "Morpheus Groups"
58
+ print_h1 "Morpheus Groups", subtitles, options
59
59
  if groups.empty?
60
- print yellow,"No groups currently configured.",reset,"\n"
60
+ print cyan,"No groups found.",reset,"\n"
61
61
  else
62
62
  print_groups_table(groups, options)
63
63
  print_results_pagination(json_response)
@@ -74,6 +74,7 @@ class Morpheus::Cli::Groups
74
74
  end
75
75
  end
76
76
  print reset,"\n"
77
+ return 0
77
78
  rescue RestClient::Exception => e
78
79
  print_rest_exception(e, options)
79
80
  exit 1
@@ -0,0 +1,97 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::IntegrationsCommand
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::AccountsHelper
6
+
7
+ set_command_name :'integrations'
8
+
9
+ register_subcommands :list
10
+ set_default_subcommand :list
11
+
12
+ def connect(opts)
13
+ @api_client = establish_remote_appliance_connection(opts)
14
+ @integrations_interface = @api_client.integrations
15
+ end
16
+
17
+ def handle(args)
18
+ handle_subcommand(args)
19
+ end
20
+
21
+ def list(args)
22
+ options = {}
23
+ params = {}
24
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
25
+ opts.banner = subcommand_usage()
26
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
27
+ opts.footer = "List integrations."
28
+ end
29
+ optparse.parse!(args)
30
+ connect(options)
31
+ if args.count != 0
32
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
33
+ return 1
34
+ end
35
+
36
+ begin
37
+ params.merge!(parse_list_options(options))
38
+ @integrations_interface.setopts(options)
39
+ if options[:dry_run]
40
+ print_dry_run @integrations_interface.dry.list(params)
41
+ return
42
+ end
43
+ json_response = @integrations_interface.list(params)
44
+
45
+ render_result = render_with_format(json_response, options, 'integrations')
46
+ return 0 if render_result
47
+
48
+ title = "Morpheus Integrations"
49
+ subtitles = []
50
+ subtitles += parse_list_subtitles(options)
51
+ print_h1 title, subtitles
52
+
53
+ integrations = json_response['integrations']
54
+
55
+ if integrations.empty?
56
+ print yellow,"No integrations found.",reset,"\n"
57
+ else
58
+ rows = integrations.collect do |it|
59
+ {
60
+ id: it['id'],
61
+ name: it['name'],
62
+ status: format_integration_status(it),
63
+ last_updated: format_local_dt(it['statusDate']),
64
+ type: it['integrationType'] ? it['integrationType']['name'] : ''
65
+ }
66
+ end
67
+ columns = [ :id, :name, :status, :last_updated, :type]
68
+ print as_pretty_table(rows, columns)
69
+ end
70
+ print reset,"\n"
71
+ return 0
72
+ rescue RestClient::Exception => e
73
+ print_rest_exception(e, options)
74
+ exit 1
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def format_integration_status(integration, return_color=cyan)
81
+ out = ""
82
+ status_string = integration['status']
83
+ if integration['enabled'] == false
84
+ out << "#{red}DISABLED#{integration['statusMessage'] ? "#{return_color} - #{integration['statusMessage']}" : ''}#{return_color}"
85
+ elsif status_string.nil? || status_string.empty? || status_string == "unknown"
86
+ out << "#{white}UNKNOWN#{integration['statusMessage'] ? "#{return_color} - #{integration['statusMessage']}" : ''}#{return_color}"
87
+ elsif status_string == 'ok'
88
+ out << "#{green}#{status_string.upcase}#{return_color}"
89
+ elsif status_string == 'error' || status_string == 'offline'
90
+ out << "#{red}#{status_string ? status_string.upcase : 'N/A'}#{integration['statusMessage'] ? "#{return_color} - #{integration['statusMessage']}" : ''}#{return_color}"
91
+ else
92
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
93
+ end
94
+ out
95
+ end
96
+
97
+ end
@@ -0,0 +1,930 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::JobsCommand
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::AccountsHelper
6
+
7
+ set_command_name :'jobs'
8
+ set_command_hidden
9
+ register_subcommands :list, :get, :add, :update, :execute, :remove
10
+ register_subcommands :list_executions, :get_execution, :get_execution_event
11
+ set_default_subcommand :list
12
+
13
+ def connect(opts)
14
+ @api_client = establish_remote_appliance_connection(opts)
15
+ @jobs_interface = @api_client.jobs
16
+ @options_interface = @api_client.options
17
+ @tasks_interface = @api_client.tasks
18
+ @task_sets_interface = @api_client.task_sets
19
+ @instances_interface = @api_client.instances
20
+ @servers_interface = @api_client.servers
21
+ @containers_interface = @api_client.containers
22
+ @execute_schedules_interface = @api_client.execute_schedules
23
+ end
24
+
25
+ def handle(args)
26
+ handle_subcommand(args)
27
+ end
28
+
29
+ def list(args)
30
+ options = {}
31
+ params = {}
32
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
33
+ opts.banner = subcommand_usage()
34
+ opts.on("--source [all|user|discovered]", String, "Filters job based upon specified source. Default is all") do |val|
35
+ options[:source] = val.to_s
36
+ end
37
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
38
+ opts.footer = "List jobs."
39
+ end
40
+ optparse.parse!(args)
41
+ connect(options)
42
+ if args.count != 0
43
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
44
+ return 1
45
+ end
46
+
47
+ begin
48
+ params.merge!(parse_list_options(options))
49
+
50
+ if !options[:source].nil?
51
+ if !['all', 'user', 'discovered', 'sync'].include?(options[:source])
52
+ print_red_alert "Invalid source filter #{options[:source]}"
53
+ exit 1
54
+ end
55
+ params['itemSource'] = options[:source] == 'discovered' ? 'sync' : options[:source]
56
+ end
57
+
58
+ @jobs_interface.setopts(options)
59
+ if options[:dry_run]
60
+ print_dry_run @jobs_interface.dry.list(params)
61
+ return
62
+ end
63
+ json_response = @jobs_interface.list(params)
64
+
65
+ render_result = render_with_format(json_response, options, 'jobs')
66
+ return 0 if render_result
67
+
68
+ title = "Morpheus Jobs"
69
+ subtitles = []
70
+ subtitles += parse_list_subtitles(options)
71
+ print_h1 title, subtitles
72
+
73
+ jobs = json_response['jobs']
74
+
75
+ if jobs.empty?
76
+ print yellow,"No jobs found.",reset,"\n"
77
+ else
78
+ rows = jobs.collect do |job|
79
+ {
80
+ id: job['id'],
81
+ type: job['type'] ? job['type']['name'] : '',
82
+ name: job['name'],
83
+ details: job['jobSummary'],
84
+ enabled: "#{job['enabled'] ? '' : yellow}#{format_boolean(job['enabled'])}#{cyan}",
85
+ lastRun: format_local_dt(job['lastRun']),
86
+ nextRun: job['enabled'] && job['scheduleMode'] && job['scheduleMode'] != 'manual' ? format_local_dt(job['nextFire']) : '',
87
+ lastResult: format_status(job['lastResult'])
88
+ }
89
+ end
90
+ columns = [
91
+ :id, :type, :name, :details, :enabled, :lastRun, :nextRun, :lastResult
92
+ ]
93
+ print as_pretty_table(rows, columns, options)
94
+ print_results_pagination(json_response)
95
+
96
+ if stats = json_response['stats']
97
+ label_width = 17
98
+
99
+ print_h2 "Executions Stats - Last 7 Days"
100
+ print cyan
101
+
102
+ print "Jobs".rjust(label_width, ' ') + ": #{stats['jobCount']}\n"
103
+ print "Executions Today".rjust(label_width, ' ') + ": #{stats['todayCount']}\n"
104
+ print "Daily Executions".rjust(label_width, ' ') + ": " + stats['executionsPerDay'].join(' | ') + "\n"
105
+ print "Total Executions".rjust(label_width, ' ') + ": #{stats['execCount']}\n"
106
+ print "Completed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execSuccessRate'].to_f, 100) + "#{stats['execSuccess']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
107
+ print "Failed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execFailedRate'].to_f, 100) + "#{stats['execFailed']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
108
+ end
109
+ print reset,"\n"
110
+ end
111
+ print reset,"\n"
112
+ return 0
113
+ rescue RestClient::Exception => e
114
+ print_rest_exception(e, options)
115
+ exit 1
116
+ end
117
+ end
118
+
119
+ def get(args)
120
+ options = {}
121
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
122
+ opts.banner = subcommand_usage("[job] [max-exec-count]")
123
+ build_common_options(opts, options, [:json, :dry_run, :remote])
124
+ opts.footer = "Get details about a job.\n" +
125
+ "[job] is required. Job ID or name.\n" +
126
+ "[max-exec-count] is optional. Specified max # of most recent executions. Defaults is 3"
127
+ end
128
+ optparse.parse!(args)
129
+ if args.count < 1
130
+ raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
131
+ end
132
+ connect(options)
133
+ return _get(args[0], args.count > 1 ? args[1].to_i : nil, options)
134
+ end
135
+
136
+ def _get(job_id, max_execs = 3, options = {})
137
+ begin
138
+ @jobs_interface.setopts(options)
139
+
140
+ if !(job_id.to_s =~ /\A\d{1,}\Z/)
141
+ job = find_by_name_or_id('job', job_id)
142
+
143
+ if !job
144
+ print_red_alert "Job #{job_id} not found"
145
+ exit 1
146
+ end
147
+ job_id = job['id']
148
+ end
149
+
150
+ max_execs = 3 if max_execs.nil?
151
+
152
+ params = {'includeExecCount' => max_execs}
153
+
154
+ if options[:dry_run]
155
+ print_dry_run @jobs_interface.dry.get(job_id, params)
156
+ return
157
+ end
158
+ json_response = @jobs_interface.get(job_id, params)
159
+
160
+ render_result = render_with_format(json_response, options, 'job')
161
+ return 0 if render_result
162
+
163
+ title = "Morpheus Job"
164
+ subtitles = []
165
+ subtitles += parse_list_subtitles(options)
166
+ print_h1 title, subtitles
167
+
168
+ job = json_response['job']
169
+ scheduleName = ''
170
+ if !job['scheduleMode'].nil?
171
+ if job['scheduleMode'] == 'manual'
172
+ scheduleName = 'Manual'
173
+ else !job['scheduleMode']
174
+ schedule = @execute_schedules_interface.get(job['scheduleMode'])['schedule']
175
+ scheduleName = schedule ? schedule['name'] : ''
176
+ end
177
+ end
178
+
179
+ print cyan
180
+ description_cols = {
181
+ "Name" => lambda {|it| it['name']},
182
+ "Job Type" => lambda {|it| it['type']['name']},
183
+ "Enabled" => lambda {|it| format_boolean(it['enabled'])},
184
+ (job['workflow'] ? 'Workflow' : 'Task') => lambda {|it| it['jobSummary']},
185
+ "Schedule" => lambda {|it| scheduleName}
186
+ }
187
+
188
+ if job['targetType']
189
+ description_cols["Context Type"] = lambda {|it| it['targetType'] == 'appliance' ? 'None' : it['targetType'] }
190
+
191
+ if job['targetType'] != 'appliance'
192
+ description_cols["Context #{job['targetType'].capitalize}#{job['targets'].count > 1 ? 's' : ''}"] = lambda {|it| it['targets'].collect {|it| it['name']}.join(', ')}
193
+ end
194
+ end
195
+
196
+ print_description_list(description_cols, job)
197
+
198
+ if max_execs != 0
199
+ print_h2 "Recent Executions"
200
+ print_job_executions(json_response['executions']['jobExecutions'], options)
201
+
202
+ if json_response['executions']['meta'] && json_response['executions']['meta']['total'] > max_execs
203
+ print_results_pagination(json_response['executions'])
204
+ end
205
+ end
206
+ print reset,"\n"
207
+ return 0
208
+ rescue RestClient::Exception => e
209
+ print_rest_exception(e, options)
210
+ exit 1
211
+ end
212
+ end
213
+
214
+ def add(args)
215
+ options = {}
216
+ params = {}
217
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
218
+ opts.banner = subcommand_usage( "[name]")
219
+ opts.on("--name NAME", String, "Updates job name") do |val|
220
+ params['name'] = val.to_s
221
+ end
222
+ opts.on('-a', '--active [on|off]', String, "Can be used to enable / disable the job. Default is on") do |val|
223
+ params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
224
+ end
225
+ opts.on('-t', '--task [TASK]', String, "Task ID or code, assigns task to job. Incompatible with --workflow option.") do |val|
226
+ if options[:workflow].nil?
227
+ options[:task] = val
228
+ else
229
+ raise_command_error "Options --task and --workflow are incompatible"
230
+ end
231
+ end
232
+ opts.on('-w', '--workflow [WORKFLOW]', String, "Workflow ID or code, assigns workflow to job. Incompatible with --task option.") do |val|
233
+ if options[:task].nil?
234
+ options[:workflow] = val
235
+ else
236
+ raise_command_error "Options --task and --workflow are incompatible"
237
+ end
238
+ end
239
+ opts.on('--context-type [TYPE]', String, "Context type (instance|server|none). Default is none") do |val|
240
+ params['targetType'] = (val == 'none' ? 'appliance' : val)
241
+ end
242
+ opts.on('--instances [LIST]', Array, "Context instances(s), comma separated list of instance IDs. Incompatible with --servers") do |list|
243
+ params['targetType'] = 'instance'
244
+ params['targets'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq.collect {|it| {'refId' => it.to_i}}
245
+ end
246
+ opts.on('--servers [LIST]', Array, "Context server(s), comma separated list of server IDs. Incompatible with --instances") do |list|
247
+ params['targetType'] = 'server'
248
+ params['targets'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq.collect {|it| {'refId' => it.to_i}}
249
+ end
250
+ opts.on('-S', '--schedule [SCHEDULE]', String, "Job execution schedule type name or ID") do |val|
251
+ options[:schedule] = val
252
+ end
253
+ opts.on('--config [TEXT]', String, "Custom config") do |val|
254
+ params['customConfig'] = val.to_s
255
+ end
256
+ opts.on('-R', '--run [on|off]', String, "Can be used to run the job now.") do |val|
257
+ params['run'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
258
+ end
259
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
260
+ opts.footer = "Create job."
261
+ end
262
+ optparse.parse!(args)
263
+ connect(options)
264
+ if args.count > 1
265
+ raise_command_error "wrong number of arguments, expected 0 or 1 and got (#{args.count}) #{args}\n#{optparse}"
266
+ return 1
267
+ end
268
+
269
+ begin
270
+ payload = parse_payload(options)
271
+
272
+ if !payload
273
+ # name
274
+ params['name'] = params['name'] || args[0] || name = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Job Name', 'required' => true, 'description' => 'Job Name.'}],options[:options],@api_client,{})['name']
275
+
276
+ if options[:task].nil? && options[:workflow].nil?
277
+ # prompt job type
278
+ job_types = @options_interface.options_for_source('jobTypes', {})['data']
279
+ job_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobType', 'type' => 'select', 'fieldLabel' => 'Job Type', 'selectOptions' => job_types, 'required' => true, 'description' => 'Select Job Type.'}],options[:options],@api_client,{})['jobType']
280
+ job_type = job_types.find {|it| it['value'] == job_type_id}
281
+
282
+ job_options = @jobs_interface.options(job_type_id)
283
+
284
+ # prompt task / workflow
285
+ if job_type['code'] == 'morpheus.task'
286
+ task_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'task', 'fieldLabel' => 'Task', 'type' => 'select', 'required' => true, 'optionSource' => 'tasks'}], {'optionTypeId' => job_options['optionTypes'][0]['id']}, @api_client, {})['task']
287
+ params['task'] = {'id' => task_id}
288
+ else
289
+ workflow_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'workflow', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => true, 'optionSource' => 'operationTaskSets'}], {'optionTypeId' => job_options['optionTypes'][0]['id']}, @api_client, {})['workflow']
290
+ params['workflow'] = {'id' => workflow_id}
291
+ end
292
+ end
293
+
294
+ # task
295
+ if !options[:task].nil?
296
+ task = find_by_name_or_id('task', options[:task])
297
+
298
+ if task.nil?
299
+ print_red_alert "Task #{options[:task]} not found"
300
+ exit 1
301
+ end
302
+ params['task'] = {'id' => task['id']}
303
+ job_type_id = load_job_type_id_by_code('morpheus.task')
304
+ end
305
+
306
+ # workflow
307
+ if !options[:workflow].nil?
308
+ task_set = find_by_name_or_id('task_set', options[:workflow])
309
+
310
+ if task_set.nil?
311
+ print_red_alert "Workflow #{options[:workflow]} not found"
312
+ exit 1
313
+ end
314
+ params['workflow'] = {'id' => task_set['id']}
315
+ job_type_id = load_job_type_id_by_code('morpheus.workflow')
316
+ end
317
+
318
+ # load options based upon job type + task / taskset
319
+ job_options = @jobs_interface.options(job_type_id, {'taskId' => params['task'] ? params['task']['id'] : nil, 'workflowId' => params['workflow'] ? params['workflow']['id'] : nil})
320
+ option_type_id = job_options['optionTypes'][0]['id']
321
+
322
+ # context type
323
+ if params['targetType'].nil?
324
+ params['targetType'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'contextType', 'fieldLabel' => 'Context Type', 'type' => 'select', 'required' => true, 'selectOptions' => job_options['targetTypes'], 'defaultValue' => job_options['targetTypes'].first['name']}], {'optionTypeId' => option_type_id}, @api_client, {})['contextType']
325
+ end
326
+
327
+ # contexts
328
+ if ['instance', 'server'].include?(params['targetType']) && (params['targets'].nil? || params['targets'].empty?)
329
+ targets = []
330
+ if params['targetType'] == 'instance'
331
+ avail_targets = @instances_interface.list({max:10000})['instances'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
332
+ else
333
+ avail_targets = @servers_interface.list({max:10000, 'vmHypervisor' => nil, 'containerHypervisor' => nil})['servers'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
334
+ end
335
+ target_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'target', 'fieldLabel' => "Context #{params['targetType'].capitalize}", 'type' => 'select', 'required' => true, 'selectOptions' => avail_targets}], options[:options], @api_client, {}, options[:no_prompt], true)['target']
336
+ targets << target_id
337
+ avail_targets.reject! {|it| it['value'] == target_id}
338
+
339
+ while !target_id.nil? && !avail_targets.empty? && Morpheus::Cli::OptionTypes.confirm("Add another context #{params['targetType']}?", {:default => false})
340
+ target_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'target', 'fieldLabel' => "Context #{params['targetType'].capitalize}", 'type' => 'select', 'required' => false, 'selectOptions' => avail_targets}], options[:options], @api_client, {}, options[:no_prompt], true)['target']
341
+
342
+ if !target_id.nil?
343
+ targets << target_id
344
+ avail_targets.reject! {|it| it['value'] == target_id}
345
+ end
346
+ end
347
+ params['targets'] = targets.collect {|it| {'refId' => it}}
348
+ end
349
+
350
+ # schedule
351
+ if options[:schedule].nil?
352
+ params['scheduleMode'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'schedule', 'fieldLabel' => "Schedule", 'type' => 'select', 'required' => true, 'selectOptions' => job_options['schedules'], 'defaultValue' => job_options['schedules'].first['name']}], options[:options], @api_client, {})['schedule']
353
+ else
354
+ if options[:schedule] != 'manual'
355
+ schedule = job_options['schedules'].find {|it| it['name'] == options[:schedule] || it['value'] == options[:schedule].to_i}
356
+
357
+ if schedule.nil?
358
+ print_red_alert "Schedule #{options[:schedule]} not found"
359
+ exit 1
360
+ end
361
+ options[:schedule] = schedule['value']
362
+ end
363
+ params['scheduleMode'] = options[:schedule]
364
+ end
365
+
366
+ # custom config
367
+ if params['customConfig'].nil? && job_options['allowCustomConfig']
368
+ params['customConfig'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'config', 'fieldLabel' => "Custom Config", 'type' => 'text', 'required' => false}], options[:options], @api_client, {})['config']
369
+ end
370
+ payload = {'job' => params}
371
+ end
372
+
373
+ @jobs_interface.setopts(options)
374
+ if options[:dry_run]
375
+ print_dry_run @jobs_interface.dry.create(payload)
376
+ return
377
+ end
378
+ json_response = @jobs_interface.create(payload)
379
+
380
+ if options[:json]
381
+ puts as_json(json_response, options)
382
+ elsif !options[:quiet]
383
+ if json_response['success']
384
+ print_green_success "Job created"
385
+ _get(json_response['id'], 0, options)
386
+ else
387
+ print_red_alert "Error creating job: #{json_response['msg'] || json_response['errors']}"
388
+ end
389
+ end
390
+ return 0
391
+ rescue RestClient::Exception => e
392
+ print_rest_exception(e, options)
393
+ exit 1
394
+ end
395
+ end
396
+
397
+ def update(args)
398
+ options = {}
399
+ params = {}
400
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
401
+ opts.banner = subcommand_usage( "[job]")
402
+ opts.on("--name NAME", String, "Updates job name") do |val|
403
+ params['name'] = val.to_s
404
+ end
405
+ opts.on('-a', '--active [on|off]', String, "Can be used to enable / disable the job. Default is on") do |val|
406
+ params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
407
+ end
408
+ opts.on('-t', '--task [TASK]', String, "Task ID or code, assigns task to job. Incompatible with --workflow option.") do |val|
409
+ if options[:workflow].nil?
410
+ options[:task] = val
411
+ else
412
+ raise_command_error "Options --task and --workflow are incompatible"
413
+ end
414
+ end
415
+ opts.on('-w', '--workflow [WORKFLOW]', String, "Workflow ID or code, assigns workflow to job. Incompatible with --task option.") do |val|
416
+ if options[:task].nil?
417
+ options[:workflow] = val
418
+ else
419
+ raise_command_error "Options --task and --workflow are incompatible"
420
+ end
421
+ end
422
+ opts.on('--context-type [TYPE]', String, "Context type (instance|server|none). Default is none") do |val|
423
+ params['targetType'] = (val == 'none' ? 'appliance' : val)
424
+ end
425
+ opts.on('--instances [LIST]', Array, "Context instances(s), comma separated list of instance IDs. Incompatible with --servers") do |list|
426
+ params['targetType'] = 'instance'
427
+ options[:targets] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip.to_i }.compact.uniq.collect {|it| {'refId' => it.to_i}}
428
+ end
429
+ opts.on('--servers [LIST]', Array, "Context server(s), comma separated list of server IDs. Incompatible with --instances") do |list|
430
+ params['targetType'] = 'server'
431
+ options[:targets] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip.to_i }.compact.uniq.collect {|it| {'refId' => it.to_i}}
432
+ end
433
+ opts.on('--schedule [SCHEDULE]', String, "Job execution schedule type name or ID") do |val|
434
+ options[:schedule] = val
435
+ end
436
+ opts.on('--config [TEXT]', String, "Custom config") do |val|
437
+ params['customConfig'] = val.to_s
438
+ end
439
+ opts.on('-R', '--run [on|off]', String, "Can be used to run the job now.") do |val|
440
+ params['run'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
441
+ end
442
+ build_common_options(opts, options, [:payload, :list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
443
+ opts.footer = "Update job.\n" +
444
+ "[job] is required. Job ID or name"
445
+ end
446
+ optparse.parse!(args)
447
+ connect(options)
448
+ if args.count != 1
449
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
450
+ return 1
451
+ end
452
+
453
+ begin
454
+ job = find_by_name_or_id('job', args[0])
455
+
456
+ if job.nil?
457
+ print_red_alert "Job #{args[0]} not found"
458
+ exit 1
459
+ end
460
+
461
+ payload = parse_payload(options)
462
+
463
+ if !payload
464
+ job_type_id = job['type']['id']
465
+
466
+ if !options[:task].nil?
467
+ task = find_by_name_or_id('task', options[:task])
468
+
469
+ if task.nil?
470
+ print_red_alert "Task #{options[:task]} not found"
471
+ exit 1
472
+ end
473
+ params['task'] = {'id': task['id']}
474
+ job_type_id = load_job_type_id_by_code('morpheus.task')
475
+ end
476
+
477
+ if !options[:workflow].nil?
478
+ task_set = find_by_name_or_id('task_set', options[:workflow])
479
+
480
+ if task_set.nil?
481
+ print_red_alert "Workflow #{options[:workflow]} not found"
482
+ exit 1
483
+ end
484
+ params['workflow'] = {'id': task_set['id']}
485
+ job_type_id = load_job_type_id_by_code('morpheus.workflow')
486
+ end
487
+
488
+ if !options[:targets].nil? && ['instance', 'server'].include?(params['targetType'])
489
+ params['targets'] = []
490
+ target_type = params['targetType'] || job['targetType']
491
+ options[:targets].collect do |it|
492
+ target = find_by_name_or_id(target_type, it['refId'])
493
+
494
+ if target.nil?
495
+ print_red_alert "Context #{target_type} #{it['refId']} not found"
496
+ exit 1
497
+ end
498
+ params['targets'] << it
499
+ end
500
+ end
501
+
502
+ if !options[:schedule].nil?
503
+ if options[:schedule] != 'manual'
504
+ job_options = @jobs_interface.options(job_type_id)
505
+ schedule = job_options['schedules'].find {|it| it['name'] == options[:schedule] || it['value'] == options[:schedule].to_i}
506
+
507
+ if schedule.nil?
508
+ print_red_alert "Schedule #{options[:schedule]} not found"
509
+ exit 1
510
+ end
511
+ options[:schedule] = schedule['value']
512
+ end
513
+ params['scheduleMode'] = options[:schedule]
514
+ end
515
+ payload = {'job' => params}
516
+ end
517
+
518
+ if payload['job'].nil? || payload['job'].empty?
519
+ print_green_success "Nothing to update"
520
+ exit 1
521
+ end
522
+
523
+ @jobs_interface.setopts(options)
524
+ if options[:dry_run]
525
+ print_dry_run @jobs_interface.dry.update(job['id'], payload)
526
+ return
527
+ end
528
+ json_response = @jobs_interface.update(job['id'], payload)
529
+
530
+ if options[:json]
531
+ puts as_json(json_response, options)
532
+ elsif !options[:quiet]
533
+ if json_response['success']
534
+ print_green_success "Job updated"
535
+ _get(job['id'], nil, options)
536
+ else
537
+ print_red_alert "Error updating job: #{json_response['msg'] || json_response['errors']}"
538
+ end
539
+ end
540
+ return 0
541
+ rescue RestClient::Exception => e
542
+ print_rest_exception(e, options)
543
+ exit 1
544
+ end
545
+ end
546
+
547
+ def execute(args)
548
+ options = {}
549
+ params = {}
550
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
551
+ opts.banner = subcommand_usage( "[job]")
552
+ opts.on('--config [TEXT]', String, "Custom config") do |val|
553
+ params['customConfig'] = val.to_s
554
+ end
555
+ build_common_options(opts, options, [:json, :dry_run, :remote])
556
+ opts.footer = "Run job.\n" +
557
+ "[job] is required. Job ID or name"
558
+ end
559
+ optparse.parse!(args)
560
+ connect(options)
561
+ if args.count != 1
562
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
563
+ return 1
564
+ end
565
+
566
+ begin
567
+ job = find_by_name_or_id('job', args[0])
568
+
569
+ if job.nil?
570
+ print_red_alert "Job #{args[0]} not found"
571
+ exit 1
572
+ end
573
+
574
+ @jobs_interface.setopts(options)
575
+ if options[:dry_run]
576
+ print_dry_run @jobs_interface.dry.execute_job(job['id'], params)
577
+ return
578
+ end
579
+
580
+ json_response = @jobs_interface.execute_job(job['id'], params)
581
+
582
+ if options[:json]
583
+ puts as_json(json_response, options)
584
+ elsif !options[:quiet]
585
+ if json_response['success']
586
+ print_green_success "Job queued for execution"
587
+ _get(job['id'], nil, options)
588
+ else
589
+ print_red_alert "Error executing job: #{json_response['msg'] || json_response['errors']}"
590
+ end
591
+ end
592
+ return 0
593
+ rescue RestClient::Exception => e
594
+ print_rest_exception(e, options)
595
+ exit 1
596
+ end
597
+ end
598
+
599
+ def remove(args)
600
+ options = {}
601
+ params = {}
602
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
603
+ opts.banner = subcommand_usage( "[job]")
604
+ build_common_options(opts, options, [:json, :dry_run, :remote])
605
+ opts.footer = "Remove job.\n" +
606
+ "[job] is required. Job ID or name"
607
+ end
608
+ optparse.parse!(args)
609
+ connect(options)
610
+ if args.count != 1
611
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
612
+ return 1
613
+ end
614
+
615
+ begin
616
+ job = find_by_name_or_id('job', args[0])
617
+
618
+ if job.nil?
619
+ print_red_alert "Job #{args[0]} not found"
620
+ exit 1
621
+ end
622
+
623
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the job '#{job['name']}'?", options)
624
+ return 9, "aborted command"
625
+ end
626
+
627
+ @jobs_interface.setopts(options)
628
+ if options[:dry_run]
629
+ print_dry_run @jobs_interface.dry.destroy(job['id'], params)
630
+ return
631
+ end
632
+
633
+ json_response = @jobs_interface.destroy(job['id'], params)
634
+
635
+ if options[:json]
636
+ print JSON.pretty_generate(json_response)
637
+ print "\n"
638
+ elsif !options[:quiet]
639
+ print_green_success "Job #{job['name']} removed"
640
+ end
641
+ return 0
642
+ rescue RestClient::Exception => e
643
+ print_rest_exception(e, options)
644
+ exit 1
645
+ end
646
+ end
647
+
648
+ def list_executions(args)
649
+ options = {}
650
+ params = {}
651
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
652
+ opts.banner = subcommand_usage("[job]")
653
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
654
+ opts.footer = "List job executions.\n" +
655
+ "[job] is optional. Job ID or name to filter executions."
656
+
657
+ end
658
+ optparse.parse!(args)
659
+ connect(options)
660
+ if args.count > 1
661
+ raise_command_error "wrong number of arguments, expected 0..1 and got (#{args.count}) #{args}\n#{optparse}"
662
+ return 1
663
+ end
664
+
665
+ begin
666
+ params.merge!(parse_list_options(options))
667
+
668
+ if args.count > 0
669
+ job = find_by_name_or_id('job', args[0])
670
+
671
+ if job.nil?
672
+ print_red_alert "Job #{args[0]} not found"
673
+ exit 1
674
+ end
675
+ params['jobId'] = job['id']
676
+ end
677
+
678
+ @jobs_interface.setopts(options)
679
+ if options[:dry_run]
680
+ print_dry_run @jobs_interface.dry.list_executions(params)
681
+ return
682
+ end
683
+ json_response = @jobs_interface.list_executions(params)
684
+
685
+ render_result = render_with_format(json_response, options, 'jobExecutions')
686
+ return 0 if render_result
687
+
688
+ title = "Morpheus Job Executions"
689
+ subtitles = job ? ["Job: #{job['name']}"] : []
690
+ subtitles += parse_list_subtitles(options)
691
+ print_h1 title, subtitles
692
+
693
+ print_job_executions(json_response['jobExecutions'], options)
694
+ print_results_pagination(json_response)
695
+ print reset,"\n"
696
+ return 0
697
+ rescue RestClient::Exception => e
698
+ print_rest_exception(e, options)
699
+ exit 1
700
+ end
701
+ end
702
+
703
+ def get_execution(args)
704
+ options = {}
705
+ params = {}
706
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
707
+ opts.banner = subcommand_usage("[id]")
708
+ opts.on('-D', '--details [on|off]', String, "Can be used to enable / disable execution details. Default if on") do |val|
709
+ options[:details] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
710
+ end
711
+ build_common_options(opts, options, [:json, :dry_run, :remote])
712
+ opts.footer = "Get details about a job.\n" +
713
+ "[id] is required. Job execution ID."
714
+ end
715
+ optparse.parse!(args)
716
+ if args.count != 1
717
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
718
+ end
719
+ connect(options)
720
+
721
+ begin
722
+ @jobs_interface.setopts(options)
723
+
724
+ if options[:dry_run]
725
+ print_dry_run @jobs_interface.dry.get_execution(args[0], params)
726
+ return
727
+ end
728
+ json_response = @jobs_interface.get_execution(args[0], params)
729
+
730
+ render_result = render_with_format(json_response, options, 'job')
731
+ return 0 if render_result
732
+
733
+ title = "Morpheus Job Execution"
734
+ subtitles = []
735
+ subtitles += parse_list_subtitles(options)
736
+ print_h1 title, subtitles
737
+
738
+ exec = json_response['jobExecution']
739
+ process = exec['process']
740
+ print cyan
741
+ description_cols = {
742
+ "Job" => lambda {|it| it['job'] ? it['job']['name'] : ''},
743
+ "Job Type" => lambda {|it| it['job'] && it['job']['type'] ? (it['job']['type']['code'] == 'morpheus.workflow' ? 'Workflow' : 'Task') : ''},
744
+ # "Description" => lambda {|it| it['description'] || (it['job'] ? it['job']['description'] : '') },
745
+ "Start Date" => lambda {|it| format_local_dt(it['startDate'])},
746
+ "ETA/Time" => lambda {|it| it['duration'] ? format_human_duration(it['duration'] / 1000.0) : ''},
747
+ "Status" => lambda {|it| format_status(it['status'])},
748
+ "Error" => lambda {|it| it['process'] && (it['process']['message'] || it['process']['error']) ? red + (it['process']['message'] || it['process']['error']) + cyan : ''},
749
+ "Created By" => lambda {|it| it['createdBy'].nil? ? '' : it['createdBy']['displayName'] || it['createdBy']['username']}
750
+ }
751
+ description_cols["Process ID"] = lambda {|it| process['id']} if !process.nil?
752
+
753
+ print_description_list(description_cols, exec)
754
+
755
+ if !process.nil?
756
+ if options[:details]
757
+ process_data = get_process_event_data(process)
758
+ print_h2 "Execution Details"
759
+ description_cols = {
760
+ "Process ID" => lambda {|it| it[:id]},
761
+ "Description" => lambda {|it| it[:description]},
762
+ "Start Data" => lambda {|it| it[:start_date]},
763
+ "Created By" => lambda {|it| it[:created_by]},
764
+ "Duration" => lambda {|it| it[:duration]},
765
+ "Status" => lambda {|it| it[:status]}
766
+ }
767
+ if !options[:details]
768
+ description_cols["Output"] = lambda {|it| it[:output]} if process_data[:output] && process_data[:output].strip.length > 0
769
+ description_cols["Error"] = lambda {|it| it[:error]} if process_data[:error] && process_data[:error].strip.length > 0
770
+ end
771
+
772
+ print_description_list(description_cols, process_data)
773
+
774
+ if process_data[:output] && process_data[:output].strip.length > 0
775
+ print_h2 "Output"
776
+ print process['output']
777
+ end
778
+ if process_data[:error] && process_data[:error].strip.length > 0
779
+ print_h2 "Error"
780
+ print process['message'] || process['error']
781
+ print reset,"\n"
782
+ end
783
+ end
784
+
785
+ if process['events'] && !process['events'].empty?
786
+ print_h2 "Execution Events"
787
+ print_process_events(process['events'])
788
+ end
789
+ end
790
+ print reset,"\n"
791
+ return 0
792
+ rescue RestClient::Exception => e
793
+ print_rest_exception(e, options)
794
+ exit 1
795
+ end
796
+ end
797
+
798
+ def get_execution_event(args)
799
+ options = {}
800
+ params = {}
801
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
802
+ opts.banner = subcommand_usage("[id] [event]")
803
+ build_common_options(opts, options, [:json, :dry_run, :remote])
804
+ opts.footer = "Get details about a job.\n" +
805
+ "[id] is required. Job execution ID.\n" +
806
+ "[event] is required. Process event ID."
807
+ end
808
+ optparse.parse!(args)
809
+ if args.count != 2
810
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
811
+ end
812
+ connect(options)
813
+
814
+ begin
815
+ @jobs_interface.setopts(options)
816
+
817
+ if options[:dry_run]
818
+ print_dry_run @jobs_interface.dry.get_execution_event(args[0].to_i, args[1].to_i, params)
819
+ return
820
+ end
821
+ json_response = @jobs_interface.get_execution_event(args[0].to_i, args[1].to_i, params)
822
+
823
+ render_result = render_with_format(json_response, options, 'processEvent')
824
+ return 0 if render_result
825
+
826
+ title = "Morpheus Job Execution Event"
827
+ subtitles = []
828
+ subtitles += parse_list_subtitles(options)
829
+ print_h1 title, subtitles
830
+
831
+ event = json_response['processEvent']
832
+ event_data = get_process_event_data(event)
833
+ description_cols = {
834
+ "ID" => lambda {|it| it[:id]},
835
+ "Description" => lambda {|it| it[:description]},
836
+ "Start Data" => lambda {|it| it[:start_date]},
837
+ "Created By" => lambda {|it| it[:created_by]},
838
+ "Duration" => lambda {|it| it[:duration]},
839
+ "Status" => lambda {|it| it[:status]}
840
+ }
841
+
842
+ print_description_list(description_cols, event_data)
843
+
844
+ if event_data[:output] && event_data[:output].strip.length > 0
845
+ print_h2 "Output"
846
+ print event['output']
847
+ end
848
+ if event_data[:error] && event_data[:error].strip.length > 0
849
+ print_h2 "Error"
850
+ print event['message'] || event['error']
851
+ end
852
+ print reset,"\n"
853
+ return 0
854
+ rescue RestClient::Exception => e
855
+ print_rest_exception(e, options)
856
+ exit 1
857
+ end
858
+ end
859
+
860
+ private
861
+
862
+ def get_process_event_data(process_or_event)
863
+ {
864
+ id: process_or_event['id'],
865
+ description: process_or_event['description'] || (process_or_event['refType'] == 'instance' ? process_or_event['displayName'] : (process_or_event['processTypeName'] || '').capitalize),
866
+ start_date: format_local_dt(process_or_event['startDate']),
867
+ created_by: process_or_event['createdBy'] ? process_or_event['createdBy']['displayName'] : '',
868
+ duration: format_human_duration((process_or_event['duration'] || process_or_event['statusEta'] || 0) / 1000.0),
869
+ status: format_status(process_or_event['status']),
870
+ error: truncate_string(process_or_event['message'] || process_or_event['error'], 32),
871
+ output: truncate_string(process_or_event['output'], 32)
872
+ }
873
+ end
874
+
875
+ # both process and process events
876
+ def print_process_events(events, options={})
877
+ print as_pretty_table(events.collect {|it| get_process_event_data(it)}, [:id, :description, :start_date, :created_by, :duration, :status, :error], options)
878
+ print reset,"\n"
879
+ end
880
+
881
+ def print_job_executions(execs, options={})
882
+ if execs.empty?
883
+ print yellow,"No job executions found.",reset,"\n"
884
+ else
885
+ rows = execs.collect do |ex|
886
+ {
887
+ id: ex['id'],
888
+ job: ex['job'] ? ex['job']['name'] : '',
889
+ description: ex['description'] || ex['job'] ? ex['job']['description'] : '',
890
+ type: ex['job'] && ex['job']['type'] ? (ex['job']['type']['code'] == 'morpheus.workflow' ? 'Workflow' : 'Task') : '',
891
+ startDate: format_local_dt(ex['startDate']),
892
+ duration: ex['duration'] ? format_human_duration(ex['duration'] / 1000.0) : '',
893
+ status: format_status(ex['status']),
894
+ error: truncate_string(ex['process'] && (ex['process']['message'] || ex['process']['error']) ? ex['process']['message'] || ex['process']['error'] : '', 32)
895
+ }
896
+ end
897
+
898
+ columns = [
899
+ :id, :job, :type, :startDate, {'ETA/TIME' => :duration}, :status, :error
900
+ ]
901
+ print as_pretty_table(rows, columns, options)
902
+ print reset,"\n"
903
+ end
904
+ end
905
+
906
+ def format_status(status_string, return_color=cyan)
907
+ out = ""
908
+ if status_string
909
+ if ['success', 'successful', 'ok'].include?(status_string)
910
+ out << "#{green}#{status_string.upcase}"
911
+ elsif ['error', 'offline', 'failed', 'failure'].include?(status_string)
912
+ out << "#{red}#{status_string.upcase}"
913
+ else
914
+ out << "#{yellow}#{status_string.upcase}"
915
+ end
916
+ end
917
+ out + return_color
918
+ end
919
+
920
+ def find_by_name_or_id(type, val)
921
+ interface = instance_variable_get "@#{type}s_interface"
922
+ typeCamelCase = type.gsub(/(?:^|_)([a-z])/) do $1.upcase end
923
+ typeCamelCase = typeCamelCase[0, 1].downcase + typeCamelCase[1..-1]
924
+ (val.to_s =~ /\A\d{1,}\Z/) ? interface.get(val.to_i)[typeCamelCase] : interface.list({'name' => val})["#{typeCamelCase}s"].first
925
+ end
926
+
927
+ def load_job_type_id_by_code(code)
928
+ @options_interface.options_for_source('jobTypes', {})['data'].find {|it| it['code'] == code}['value'] rescue nil
929
+ end
930
+ end