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.
- checksums.yaml +4 -4
- data/Dockerfile +2 -2
- data/README.md +22 -10
- data/lib/morpheus/api/api_client.rb +13 -1
- data/lib/morpheus/api/approvals_interface.rb +35 -0
- data/lib/morpheus/api/integrations_interface.rb +29 -0
- data/lib/morpheus/api/jobs_interface.rb +86 -0
- data/lib/morpheus/cli.rb +3 -0
- data/lib/morpheus/cli/appliance_settings_command.rb +13 -7
- data/lib/morpheus/cli/approvals_command.rb +236 -0
- data/lib/morpheus/cli/backup_settings_command.rb +11 -8
- data/lib/morpheus/cli/cli_command.rb +1 -1
- data/lib/morpheus/cli/clouds.rb +6 -1
- data/lib/morpheus/cli/clusters.rb +11 -9
- data/lib/morpheus/cli/groups.rb +3 -2
- data/lib/morpheus/cli/integrations_command.rb +97 -0
- data/lib/morpheus/cli/jobs_command.rb +930 -0
- data/lib/morpheus/cli/log_settings_command.rb +4 -4
- data/lib/morpheus/cli/monitoring_apps_command.rb +3 -3
- data/lib/morpheus/cli/monitoring_checks_command.rb +2 -2
- data/lib/morpheus/cli/monitoring_groups_command.rb +3 -2
- data/lib/morpheus/cli/monitoring_incidents_command.rb +3 -2
- data/lib/morpheus/cli/option_types.rb +21 -6
- data/lib/morpheus/cli/tenants_command.rb +3 -3
- data/lib/morpheus/cli/users.rb +17 -23
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whitelabel_settings_command.rb +2 -2
- data/morpheus-cli.gemspec +1 -1
- metadata +9 -3
@@ -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
|
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['
|
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 =
|
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(
|
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|
|
data/lib/morpheus/cli/clouds.rb
CHANGED
@@ -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, [:
|
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 =
|
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 =
|
4028
|
+
if !options[:planAccessAll].nil?
|
4029
|
+
all_plans = options[:planAccessAll]
|
4028
4030
|
end
|
4029
4031
|
|
4030
4032
|
if !options[:planAccessList].empty?
|
data/lib/morpheus/cli/groups.rb
CHANGED
@@ -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
|
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
|