morpheus-cli 4.1.7 → 4.1.8

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