morpheus-cli 8.0.2 → 8.0.4
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 +1 -1
- data/lib/morpheus/api/license_interface.rb +2 -4
- data/lib/morpheus/cli/commands/clusters.rb +7 -38
- data/lib/morpheus/cli/commands/hosts.rb +33 -0
- data/lib/morpheus/cli/commands/instances.rb +20 -63
- data/lib/morpheus/cli/commands/license.rb +170 -87
- data/lib/morpheus/cli/commands/networks_command.rb +5 -4
- data/lib/morpheus/cli/commands/processes_command.rb +2 -2
- data/lib/morpheus/cli/commands/snapshots.rb +24 -8
- data/lib/morpheus/cli/mixins/processes_helper.rb +34 -4
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cbc73275f4621016909ca9b3d12fcdf879a27d948c5d0e2b5e168d849850954
|
4
|
+
data.tar.gz: c30e3333325812730d68d16fa340ed8f858926b22aaf8d3ffd95fc9d4f9d2ef8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9b1b734e792e44addc36e281765be1598d7e9d94e312f4e8c7cdd5a0da8ade497d8989421e56793dc86897a37d1fdafca26da81bda67f0591481ddec642099f
|
7
|
+
data.tar.gz: e6ff86116594db8837b84a0baa8b15e0f6bb777dd3df415fad23f6918a9f097b210026b2479391decaac93c1a21177f082fd4449914d9bcb910f899228b60a70
|
data/Dockerfile
CHANGED
@@ -8,18 +8,16 @@ class Morpheus::LicenseInterface < Morpheus::APIClient
|
|
8
8
|
execute(method: :get, url: url, headers: headers)
|
9
9
|
end
|
10
10
|
|
11
|
-
def install(
|
11
|
+
def install(payload)
|
12
12
|
url = "#{@base_url}/api/license"
|
13
13
|
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
14
|
-
payload = {license: key}
|
15
14
|
execute(method: :post, url: url, headers: headers, payload: payload.to_json)
|
16
15
|
end
|
17
16
|
|
18
|
-
def test(
|
17
|
+
def test(payload)
|
19
18
|
# use /test instead, since 4.1.1
|
20
19
|
url = "#{@base_url}/api/license/decode"
|
21
20
|
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
22
|
-
payload = {license: key}
|
23
21
|
execute(method: :post, url: url, headers: headers, payload: payload.to_json)
|
24
22
|
end
|
25
23
|
|
@@ -422,7 +422,7 @@ class Morpheus::Cli::Clusters
|
|
422
422
|
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
423
423
|
end
|
424
424
|
opts.on('--workflow ID', String, "Workflow") do |val|
|
425
|
-
options[
|
425
|
+
options[:workflow] = val.to_i
|
426
426
|
end
|
427
427
|
add_server_options(opts, options)
|
428
428
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
@@ -608,13 +608,12 @@ class Morpheus::Cli::Clusters
|
|
608
608
|
end
|
609
609
|
|
610
610
|
# Controller type
|
611
|
-
server_types = @server_types_interface.list({computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes'].reject {|it| it['provisionType']['code'] == 'manual'} unless ['
|
611
|
+
server_types = @server_types_interface.list({computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes'].reject {|it| it['provisionType']['code'] == 'manual'} unless ['mvm-cluster'].include?(cluster_type_code)
|
612
612
|
controller_provision_type = nil
|
613
613
|
resource_pool = nil
|
614
614
|
|
615
615
|
if !server_types.empty?
|
616
616
|
controller_type = server_types.first
|
617
|
-
|
618
617
|
if controller_type['provisionType']
|
619
618
|
if provision_type && provision_type['id'] == controller_type['provisionType']['id']
|
620
619
|
controller_provision_type = provision_type
|
@@ -747,10 +746,11 @@ class Morpheus::Cli::Clusters
|
|
747
746
|
|
748
747
|
# Workflow / Automation
|
749
748
|
if provision_type['code'] != 'manual' && controller_type && controller_type['hasAutomation']
|
750
|
-
task_set_id = options[:
|
749
|
+
task_set_id = options[:workflow] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSetId', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, api_params.merge({'phase' => 'postProvision'}))['taskSetId']
|
751
750
|
|
752
751
|
if !task_set_id.nil?
|
753
|
-
server_payload['taskSet'] = {'id' => task_set_id}
|
752
|
+
# server_payload['taskSet'] = {'id' => task_set_id}
|
753
|
+
cluster_payload['taskSetId'] = task_set_id
|
754
754
|
end
|
755
755
|
end
|
756
756
|
|
@@ -3784,7 +3784,7 @@ class Morpheus::Cli::Clusters
|
|
3784
3784
|
subtitles << " Process ID: #{process_id}"
|
3785
3785
|
subtitles += parse_list_subtitles(options)
|
3786
3786
|
print_h1 title, subtitles, options
|
3787
|
-
print_process_details(process)
|
3787
|
+
print_process_details(process, options)
|
3788
3788
|
|
3789
3789
|
print_h2 "Process Events", options
|
3790
3790
|
process_events = process['events'] || process['processEvents'] || []
|
@@ -4003,7 +4003,7 @@ class Morpheus::Cli::Clusters
|
|
4003
4003
|
subtitles = []
|
4004
4004
|
subtitles += parse_list_subtitles(options)
|
4005
4005
|
print_h1 title, subtitles, options
|
4006
|
-
print_process_event_details(process_event)
|
4006
|
+
print_process_event_details(process_event, options)
|
4007
4007
|
print reset, "\n"
|
4008
4008
|
return 0
|
4009
4009
|
end
|
@@ -4015,37 +4015,6 @@ class Morpheus::Cli::Clusters
|
|
4015
4015
|
|
4016
4016
|
private
|
4017
4017
|
|
4018
|
-
def print_process_event_details(process_event, options={})
|
4019
|
-
# process_event =~ process
|
4020
|
-
description_cols = {
|
4021
|
-
"Process ID" => lambda {|it| it['processId'] },
|
4022
|
-
"Event ID" => lambda {|it| it['id'] },
|
4023
|
-
"Name" => lambda {|it| it['displayName'] },
|
4024
|
-
"Description" => lambda {|it| it['description'] },
|
4025
|
-
"Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
|
4026
|
-
"Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
|
4027
|
-
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
4028
|
-
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
4029
|
-
"Duration" => lambda {|it| format_process_duration(it) },
|
4030
|
-
"Status" => lambda {|it| format_process_status(it) },
|
4031
|
-
}
|
4032
|
-
print_description_list(description_cols, process_event)
|
4033
|
-
|
4034
|
-
if process_event['error']
|
4035
|
-
print_h2 "Error", options
|
4036
|
-
print reset
|
4037
|
-
#puts format_process_error(process_event)
|
4038
|
-
puts process_event['error'].to_s.strip
|
4039
|
-
end
|
4040
|
-
|
4041
|
-
if process_event['output']
|
4042
|
-
print_h2 "Output", options
|
4043
|
-
print reset
|
4044
|
-
#puts format_process_error(process_event)
|
4045
|
-
puts process_event['output'].to_s.strip
|
4046
|
-
end
|
4047
|
-
end
|
4048
|
-
|
4049
4018
|
def print_clusters_table(clusters, opts={})
|
4050
4019
|
table_color = opts[:color] || cyan
|
4051
4020
|
rows = clusters.collect do |cluster|
|
@@ -2229,6 +2229,18 @@ EOT
|
|
2229
2229
|
options = {}
|
2230
2230
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2231
2231
|
opts.banner = subcommand_usage("[host]")
|
2232
|
+
opts.on('--ignoreDaemonsets [on|off]', String, "Ignore Daemonsets") do |val|
|
2233
|
+
options[:ignoreDaemonsets] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
|
2234
|
+
end
|
2235
|
+
opts.on('--force [on|off]', String, "Force") do |val|
|
2236
|
+
options[:force] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
|
2237
|
+
end
|
2238
|
+
opts.on('--deleteEmptyDir [on|off]', String, "Delete Empty Directories") do |val|
|
2239
|
+
options[:deleteEmptyDir] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
|
2240
|
+
end
|
2241
|
+
opts.on('--deleteLocalData [on|off]', String, "Delete Local Data") do |val|
|
2242
|
+
options[:deleteLocalData] = (val.to_s == 'on' || val.to_s == 'true')
|
2243
|
+
end
|
2232
2244
|
build_standard_update_options(opts, options, [:auto_confirm])
|
2233
2245
|
opts.footer = <<-EOT
|
2234
2246
|
Enable maintenance mode for a host.
|
@@ -2252,6 +2264,27 @@ EOT
|
|
2252
2264
|
# end
|
2253
2265
|
|
2254
2266
|
payload = {}
|
2267
|
+
|
2268
|
+
if server.dig('config', 'kubernetesRole')
|
2269
|
+
payload[:server] = {}
|
2270
|
+
payload[:server][:ignoreDaemonsets] = options.fetch(:ignoreDaemonsets) do
|
2271
|
+
prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ignoreDaemonsets', 'fieldLabel' => 'Ignore Daemonsets', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
|
2272
|
+
prompt['ignoreDaemonsets'] == 'on'
|
2273
|
+
end
|
2274
|
+
payload[:server][:force] = options.fetch(:force) do
|
2275
|
+
prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'force', 'fieldLabel' => 'Force', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
|
2276
|
+
prompt['force'] == 'on'
|
2277
|
+
end
|
2278
|
+
payload[:server][:deleteEmptyDir] = options.fetch(:deleteEmptyDir) do
|
2279
|
+
prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deleteEmptyDir', 'fieldLabel' => 'Delete Empty Directories', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
|
2280
|
+
prompt['deleteEmptyDir'] == 'on'
|
2281
|
+
end
|
2282
|
+
payload[:server][:deleteLocalData] = options.fetch(:deleteLocalData) do
|
2283
|
+
prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deleteLocalData', 'fieldLabel' => 'Delete Local Data', 'type' => 'checkbox', 'defaultValue' => false, 'required' => false}], options, @api_client, {})
|
2284
|
+
prompt['force'] == 'on'
|
2285
|
+
end
|
2286
|
+
end
|
2287
|
+
|
2255
2288
|
if options[:payload]
|
2256
2289
|
payload = options[:payload]
|
2257
2290
|
payload.deep_merge!(parse_passed_options(options))
|
@@ -2921,6 +2921,12 @@ class Morpheus::Cli::Instances
|
|
2921
2921
|
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2922
2922
|
options[:options]['description'] = val
|
2923
2923
|
end
|
2924
|
+
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
2925
|
+
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
2926
|
+
end
|
2927
|
+
opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
2928
|
+
options[:no_refresh] = true
|
2929
|
+
end
|
2924
2930
|
build_standard_add_options(opts, options, [:auto_confirm])
|
2925
2931
|
opts.footer = <<-EOT
|
2926
2932
|
Create a snapshot for an instance.
|
@@ -2949,6 +2955,18 @@ EOT
|
|
2949
2955
|
json_response = @instances_interface.snapshot(instance['id'], payload)
|
2950
2956
|
render_response(json_response, options, 'snapshots') do
|
2951
2957
|
print_green_success "Snapshot initiated."
|
2958
|
+
process_id = json_response['processIds'][0] rescue nil
|
2959
|
+
if process_id
|
2960
|
+
unless options[:no_refresh]
|
2961
|
+
process = wait_for_process_execution(process_id, options)
|
2962
|
+
snapshot_id = process['resultId']
|
2963
|
+
if snapshot_id
|
2964
|
+
Morpheus::Cli::Snapshots.new.handle(["get", snapshot_id] + (options[:remote] ? ["-r",options[:remote]] : []))
|
2965
|
+
end
|
2966
|
+
end
|
2967
|
+
else
|
2968
|
+
# puts "No process returned"
|
2969
|
+
end
|
2952
2970
|
end
|
2953
2971
|
return 0, nil
|
2954
2972
|
end
|
@@ -3707,7 +3725,7 @@ EOT
|
|
3707
3725
|
if options[:json]
|
3708
3726
|
puts as_json(json_response, options)
|
3709
3727
|
else
|
3710
|
-
print_green_success "Snapshots
|
3728
|
+
print_green_success "Snapshots attached to instance #{instance['name']} queued for deletion."
|
3711
3729
|
end
|
3712
3730
|
return 0
|
3713
3731
|
|
@@ -4496,7 +4514,7 @@ EOT
|
|
4496
4514
|
subtitles << " Process ID: #{process_id}"
|
4497
4515
|
subtitles += parse_list_subtitles(options)
|
4498
4516
|
print_h1 title, subtitles, options
|
4499
|
-
print_process_details(process)
|
4517
|
+
print_process_details(process, options)
|
4500
4518
|
|
4501
4519
|
print_h2 "Process Events", options
|
4502
4520
|
process_events = process['events'] || process['processEvents'] || []
|
@@ -5502,67 +5520,6 @@ private
|
|
5502
5520
|
{'fieldName' => 'maxDisk', 'fieldLabel' => 'Max Disk', 'type' => 'number', 'description' => 'Maximum storage percent (0-100)'},
|
5503
5521
|
]
|
5504
5522
|
end
|
5505
|
-
|
5506
|
-
def print_process_details(process)
|
5507
|
-
description_cols = {
|
5508
|
-
"Process ID" => lambda {|it| it['id'] },
|
5509
|
-
"Name" => lambda {|it| it['displayName'] },
|
5510
|
-
"Description" => lambda {|it| it['description'] },
|
5511
|
-
"Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
|
5512
|
-
"Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
|
5513
|
-
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
5514
|
-
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
5515
|
-
"Duration" => lambda {|it| format_process_duration(it) },
|
5516
|
-
"Status" => lambda {|it| format_process_status(it) },
|
5517
|
-
# "# Events" => lambda {|it| (it['events'] || []).size() },
|
5518
|
-
}
|
5519
|
-
print_description_list(description_cols, process)
|
5520
|
-
|
5521
|
-
if process['error']
|
5522
|
-
print_h2 "Error", options
|
5523
|
-
print reset
|
5524
|
-
#puts format_process_error(process_event)
|
5525
|
-
puts process['error'].to_s.strip
|
5526
|
-
end
|
5527
|
-
|
5528
|
-
if process['output']
|
5529
|
-
print_h2 "Output", options
|
5530
|
-
print reset
|
5531
|
-
#puts format_process_error(process_event)
|
5532
|
-
puts process['output'].to_s.strip
|
5533
|
-
end
|
5534
|
-
end
|
5535
|
-
|
5536
|
-
def print_process_event_details(process_event, options={})
|
5537
|
-
# process_event =~ process
|
5538
|
-
description_cols = {
|
5539
|
-
"Process ID" => lambda {|it| it['processId'] },
|
5540
|
-
"Event ID" => lambda {|it| it['id'] },
|
5541
|
-
"Name" => lambda {|it| it['displayName'] },
|
5542
|
-
"Description" => lambda {|it| it['description'] },
|
5543
|
-
"Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
|
5544
|
-
"Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
|
5545
|
-
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
5546
|
-
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
5547
|
-
"Duration" => lambda {|it| format_process_duration(it) },
|
5548
|
-
"Status" => lambda {|it| format_process_status(it) },
|
5549
|
-
}
|
5550
|
-
print_description_list(description_cols, process_event)
|
5551
|
-
|
5552
|
-
if process_event['error']
|
5553
|
-
print_h2 "Error", options
|
5554
|
-
print reset
|
5555
|
-
#puts format_process_error(process_event)
|
5556
|
-
puts process_event['error'].to_s.strip
|
5557
|
-
end
|
5558
|
-
|
5559
|
-
if process_event['output']
|
5560
|
-
print_h2 "Output", options
|
5561
|
-
print reset
|
5562
|
-
#puts format_process_error(process_event)
|
5563
|
-
puts process_event['output'].to_s.strip
|
5564
|
-
end
|
5565
|
-
end
|
5566
5523
|
|
5567
5524
|
def update_wiki_page_option_types
|
5568
5525
|
[
|
@@ -55,6 +55,10 @@ class Morpheus::Cli::License
|
|
55
55
|
print "#{yellow}No license currently installed#{reset}\n\n"
|
56
56
|
else
|
57
57
|
print_license_details(json_response)
|
58
|
+
licenses = json_response['installedLicenses']
|
59
|
+
if licenses
|
60
|
+
print_installed_licenses(licenses, options)
|
61
|
+
end
|
58
62
|
print_h2 "Current Usage", [], options
|
59
63
|
print_license_usage(license, current_usage)
|
60
64
|
print reset,"\n"
|
@@ -72,40 +76,55 @@ class Morpheus::Cli::License
|
|
72
76
|
account_name = nil
|
73
77
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
74
78
|
opts.banner = subcommand_usage("[key]")
|
75
|
-
|
79
|
+
opts.on('-a', '--add', "Stack this license on top of existing licenses. Without this option all existing licenses will be replaced." ) do
|
80
|
+
options[:options]['installAction'] = 'add'
|
81
|
+
end
|
82
|
+
opts.on('--stack', '--stack', "Alias for --add" ) do
|
83
|
+
options[:options]['installAction'] = 'add'
|
84
|
+
end
|
85
|
+
opts.on('--replace', '--replace', "Replace existing licenses with this new license. This is the default behavior." ) do
|
86
|
+
options[:options]['installAction'] = 'replace'
|
87
|
+
end
|
88
|
+
build_standard_add_options(opts, options)
|
76
89
|
opts.footer = "Install a new license key.\n" +
|
77
90
|
"This will potentially change the enabled features and capabilities of your appliance."
|
78
91
|
end
|
79
92
|
optparse.parse!(args)
|
80
|
-
|
81
|
-
raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
82
|
-
end
|
93
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
83
94
|
connect(options)
|
84
|
-
|
95
|
+
payload = {}
|
96
|
+
if options[:payload]
|
97
|
+
payload = options[:payload]
|
98
|
+
payload.deep_merge!(parse_passed_options(options))
|
99
|
+
else
|
100
|
+
payload.deep_merge!(parse_passed_options(options))
|
85
101
|
if args[0]
|
86
|
-
key = args[0]
|
102
|
+
key = args[0].strip
|
87
103
|
else
|
88
104
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true}], options[:options])
|
89
|
-
key = v_prompt['licenseKey']
|
105
|
+
key = v_prompt['licenseKey'].to_s.strip
|
90
106
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
puts JSON.pretty_generate(json_response)
|
100
|
-
else
|
101
|
-
print_green_success "License installed!"
|
102
|
-
get([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
107
|
+
payload['license'] = key
|
108
|
+
# Stack Licenses?
|
109
|
+
# load current licenses to see if this prompt is required
|
110
|
+
json_response = @license_interface.get()
|
111
|
+
licenses = json_response['installedLicenses']
|
112
|
+
if licenses && !(licenses.size == 1 && key.index(licenses[0]['keyId']) == 0)
|
113
|
+
install_actions = [{'name' => 'Add', 'value' => 'add'},{'name' => 'Replace', 'value' => 'replace'}]
|
114
|
+
payload['installAction'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'installAction', 'fieldLabel' => 'Install Action', 'type' => 'select', 'selectOptions' => install_actions, 'required' => true, 'defaultValue' => 'replace', 'description' => "Install action to perform. Use add to stack this license on top of existing licenses. Use replace (default) to remove any existing license(s)."}], options[:options])['installAction']
|
103
115
|
end
|
116
|
+
end
|
117
|
+
@license_interface.setopts(options)
|
118
|
+
if options[:dry_run]
|
119
|
+
print_dry_run @license_interface.dry.install(payload)
|
104
120
|
return 0
|
105
|
-
rescue RestClient::Exception => e
|
106
|
-
print_rest_exception(e, options)
|
107
|
-
return false
|
108
121
|
end
|
122
|
+
json_response = @license_interface.install(payload)
|
123
|
+
render_response(json_response, options) do
|
124
|
+
print_green_success "License installed!"
|
125
|
+
get([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
126
|
+
end
|
127
|
+
return 0, nil
|
109
128
|
end
|
110
129
|
|
111
130
|
def decode(args)
|
@@ -117,7 +136,16 @@ class Morpheus::Cli::License
|
|
117
136
|
options = {}
|
118
137
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
119
138
|
opts.banner = subcommand_usage("[key]")
|
120
|
-
|
139
|
+
opts.on('-a', '--add', "Stack this license on top of existing licenses. Without this option all existing licenses will be replaced." ) do
|
140
|
+
options[:options]['installAction'] = 'add'
|
141
|
+
end
|
142
|
+
opts.on('--stack', '--stack', "Alias for --add" ) do
|
143
|
+
options[:options]['installAction'] = 'add'
|
144
|
+
end
|
145
|
+
opts.on('--replace', '--replace', "Replace existing licenses with this new license. This is the default behavior." ) do
|
146
|
+
options[:options]['installAction'] = 'replace'
|
147
|
+
end
|
148
|
+
build_standard_add_options(opts, options, [:fields])
|
121
149
|
opts.footer = "Test a license key.\n" +
|
122
150
|
"This is a way to decode and view a license key before installing it."
|
123
151
|
end
|
@@ -126,50 +154,48 @@ class Morpheus::Cli::License
|
|
126
154
|
raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
127
155
|
end
|
128
156
|
connect(options)
|
129
|
-
|
130
|
-
if
|
131
|
-
|
157
|
+
payload = {}
|
158
|
+
if options[:payload]
|
159
|
+
payload = options[:payload]
|
160
|
+
payload.deep_merge!(parse_passed_options(options))
|
132
161
|
else
|
133
|
-
|
134
|
-
key =
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
return
|
141
|
-
end
|
142
|
-
json_response = @license_interface.test(key)
|
143
|
-
license = json_response['license']
|
144
|
-
current_usage = json_response['currentUsage'] || {}
|
145
|
-
exit_code, err = 0, nil
|
146
|
-
if license.nil?
|
147
|
-
err = "Unable to decode license."
|
148
|
-
exit_code = 1
|
149
|
-
#return err, exit_code
|
150
|
-
end
|
151
|
-
render_result = render_with_format(json_response, options)
|
152
|
-
if render_result
|
153
|
-
return exit_code, err
|
154
|
-
end
|
155
|
-
if options[:quiet]
|
156
|
-
return exit_code, err
|
162
|
+
payload.deep_merge!(parse_passed_options(options))
|
163
|
+
key = nil
|
164
|
+
if args[0]
|
165
|
+
key = args[0].strip
|
166
|
+
else
|
167
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true}], options[:options])
|
168
|
+
key = v_prompt['licenseKey'] || ''
|
157
169
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
170
|
+
payload['license'] = key
|
171
|
+
# Stack Licenses?
|
172
|
+
# load current licenses to see if this prompt is required
|
173
|
+
json_response = @license_interface.get()
|
174
|
+
licenses = json_response['installedLicenses']
|
175
|
+
if licenses && !(licenses.size == 1 && key.index(licenses[0]['keyId']) == 0)
|
176
|
+
install_actions = [{'name' => 'Add', 'value' => 'add'},{'name' => 'Replace', 'value' => 'replace'}]
|
177
|
+
payload['installAction'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'installAction', 'fieldLabel' => 'Install Action', 'type' => 'select', 'selectOptions' => install_actions, 'required' => true, 'defaultValue' => 'replace', 'description' => "Install action to perform. Use add to stack this license on top of existing licenses. Use replace (default) to remove any existing license(s)."}], options[:options])['installAction']
|
161
178
|
end
|
162
|
-
|
179
|
+
end
|
180
|
+
@license_interface.setopts(options)
|
181
|
+
if options[:dry_run]
|
182
|
+
print_dry_run @license_interface.dry.test(payload)
|
183
|
+
return
|
184
|
+
end
|
185
|
+
json_response = @license_interface.test(payload)
|
186
|
+
render_response(json_response, options) do
|
187
|
+
license = json_response['license']
|
188
|
+
current_usage = json_response['currentUsage'] || {}
|
163
189
|
# all good
|
164
190
|
print_h1 "License"
|
165
191
|
print_license_details(json_response)
|
192
|
+
licenses = json_response['installedLicenses']
|
193
|
+
if licenses
|
194
|
+
print_installed_licenses(licenses, options)
|
195
|
+
end
|
166
196
|
print_h2 "License Usage", [], options
|
167
197
|
print_license_usage(license, current_usage)
|
168
198
|
print reset,"\n"
|
169
|
-
return exit_code, err
|
170
|
-
rescue RestClient::Exception => e
|
171
|
-
print_rest_exception(e, options)
|
172
|
-
return 1
|
173
199
|
end
|
174
200
|
end
|
175
201
|
|
@@ -177,44 +203,58 @@ class Morpheus::Cli::License
|
|
177
203
|
options = {}
|
178
204
|
params = {}
|
179
205
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
180
|
-
opts.banner = subcommand_usage("[key]")
|
206
|
+
opts.banner = subcommand_usage("[key ID]")
|
207
|
+
opts.on('--key KEY', String, "License Key ID to uninstall (only the first 8 characters are required to identify license to uninstall). By default all licenses are uninstalled.") do |val|
|
208
|
+
options[:key] = val.to_s
|
209
|
+
end
|
181
210
|
build_common_options(opts, options, [:auto_confirm, :json, :yaml, :dry_run, :remote])
|
182
|
-
opts.footer = "Uninstall
|
183
|
-
"
|
211
|
+
opts.footer = "Uninstall license key(s).\n" +
|
212
|
+
"Use [key] or --key to uninstall only the specified license key.\n" +
|
213
|
+
"By default all currently installed license keys are uninstalled.\n" +
|
184
214
|
"The function of the remote appliance will be restricted without a license installed.\n" +
|
185
215
|
"Be careful using this."
|
186
216
|
end
|
187
217
|
optparse.parse!(args)
|
188
|
-
|
189
|
-
raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
190
|
-
end
|
218
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
191
219
|
connect(options)
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
220
|
+
key_id = nil
|
221
|
+
if args[0] || options[:key]
|
222
|
+
key_id = (args[0] || options[:key])[0..7]
|
223
|
+
end
|
224
|
+
# load current licenses to prompt user which to uninstall
|
225
|
+
json_response = @license_interface.get()
|
226
|
+
licenses = json_response['installedLicenses'] # || [json_response['licenses']]
|
227
|
+
if key_id
|
228
|
+
# appliance version < 8.0.4 does not return installedLicenses list
|
229
|
+
if licenses.nil?
|
230
|
+
raise_command_error "Appliance version does not support uninstalling specific keys"
|
203
231
|
end
|
204
|
-
|
205
|
-
|
206
|
-
|
232
|
+
matching_license = licenses.find {|it| it['keyId'] == key_id }
|
233
|
+
if matching_license.nil?
|
234
|
+
raise_command_error "Unable to find installed license key '#{key_id}'"
|
207
235
|
end
|
236
|
+
params['keyId'] = key_id
|
237
|
+
end
|
238
|
+
|
239
|
+
unless options[:quiet]
|
240
|
+
print cyan,"#{bold}WARNING!#{reset}#{cyan} You are about to uninstall your license key (#{key_id ? key_id : 'ALL'})",reset,"\n"
|
241
|
+
print yellow, "Be careful using this. Make sure you have a copy of your key somewhere if you intend to use it again.",reset, "\n"
|
242
|
+
print "\n"
|
243
|
+
end
|
244
|
+
|
245
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you want to uninstall the license key (#{key_id ? key_id : 'ALL'}) for remote #{@appliance_name} - #{@appliance_url}?")
|
246
|
+
return 9, "command aborted"
|
247
|
+
end
|
208
248
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
return
|
249
|
+
@license_interface.setopts(options)
|
250
|
+
if options[:dry_run]
|
251
|
+
print_dry_run @license_interface.dry.uninstall(params)
|
252
|
+
return
|
253
|
+
end
|
254
|
+
json_response = @license_interface.uninstall(params)
|
255
|
+
|
256
|
+
render_response(json_response, options) do
|
213
257
|
print_green_success "License uninstalled!"
|
214
|
-
return 0
|
215
|
-
rescue RestClient::Exception => e
|
216
|
-
print_rest_exception(e, options)
|
217
|
-
return 1
|
218
258
|
end
|
219
259
|
end
|
220
260
|
|
@@ -267,8 +307,10 @@ class Morpheus::Cli::License
|
|
267
307
|
|
268
308
|
def print_license_details(json_response)
|
269
309
|
license = json_response['license']
|
310
|
+
licenses = json_response['installedLicenses']
|
270
311
|
current_usage = json_response['currentUsage'] || {}
|
271
312
|
description_cols = {
|
313
|
+
"Key ID" => lambda {|it| licenses ? licenses.collect {|li| li['keyId'] }.join(", ") : it['keyId'] },
|
272
314
|
"Account" => 'accountName',
|
273
315
|
"Product Tier" => lambda {|it| format_product_tier(it) },
|
274
316
|
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
@@ -285,7 +327,7 @@ class Morpheus::Cli::License
|
|
285
327
|
"Hard Limit" => lambda {|it| format_boolean it["hardLimit"] },
|
286
328
|
"Limit Type" => lambda {|it| format_limit_type(it) },
|
287
329
|
}
|
288
|
-
|
330
|
+
description_cols.delete("Key ID") if !license['keyId']
|
289
331
|
description_cols.delete("Multi-Tenant") if !license['multiTenant']
|
290
332
|
description_cols.delete("White Label") if !license['whitelabel']
|
291
333
|
|
@@ -373,5 +415,46 @@ class Morpheus::Cli::License
|
|
373
415
|
cyan + label.rjust(label_width, ' ') + ": " + generate_usage_bar(used, max, chart_opts) + cyan + used.to_s.rjust(label_width, ' ') + " / " + (max.to_i > 0 ? max.to_s : unlimited_label).to_s.ljust(label_width, ' ') + "\n"
|
374
416
|
end
|
375
417
|
|
418
|
+
def print_installed_licenses(licenses, options)
|
419
|
+
print_h2 "Installed Licenses", [], options
|
420
|
+
license_columns = {
|
421
|
+
"Key ID" => 'keyId',
|
422
|
+
"Account" => 'accountName',
|
423
|
+
"Product Tier" => lambda {|it| format_product_tier(it) },
|
424
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
425
|
+
"End Date" => lambda {|it|
|
426
|
+
if it['endDate']
|
427
|
+
format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
|
428
|
+
else
|
429
|
+
'Never'
|
430
|
+
end
|
431
|
+
},
|
432
|
+
"Multi-Tenant" => lambda {|it| format_boolean it["multiTenant"] },
|
433
|
+
"White Label" => lambda {|it| format_boolean it["whitelabel"] },
|
434
|
+
"Stats Reporting" => lambda {|it| format_boolean it["reportStatus"] },
|
435
|
+
"Hard Limit" => lambda {|it| format_boolean it["hardLimit"] },
|
436
|
+
"Limit Type" => lambda {|it| format_limit_type(it) },
|
437
|
+
"Limit Type" => lambda {|it| format_limit_type(it) },
|
438
|
+
}
|
439
|
+
if licenses[0] && licenses[0]['limitType'] == 'workload'
|
440
|
+
license_columns.merge!({
|
441
|
+
"Workloads" => 'maxInstances'
|
442
|
+
})
|
443
|
+
else
|
444
|
+
license_columns.merge!({
|
445
|
+
"Managed Servers" => 'maxManagedServers',
|
446
|
+
"Discovered Servers" => 'maxDiscoveredServers',
|
447
|
+
"Hosts" => 'maxHosts',
|
448
|
+
"HPE VM Hosts" => 'maxMvm',
|
449
|
+
"HPE VM Sockets" => 'maxMvmSockets',
|
450
|
+
"Iac Deployments" => 'maxIac',
|
451
|
+
"Xaas Instances" => 'maxXaas',
|
452
|
+
"Executions" => 'maxExecutions',
|
453
|
+
"Distributed Workers" => 'maxDistributedWorkers',
|
454
|
+
# "Discovered Objects" => 'maxDiscoveredObjects'
|
455
|
+
})
|
456
|
+
end
|
457
|
+
print as_pretty_table(licenses, license_columns, options)
|
458
|
+
end
|
376
459
|
|
377
460
|
end
|
@@ -493,6 +493,9 @@ class Morpheus::Cli::NetworksCommand
|
|
493
493
|
# allow arbitrary -O options
|
494
494
|
payload['network'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
495
495
|
|
496
|
+
# all of these prompts pass in options instead of options[:options] so merge them here
|
497
|
+
options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
498
|
+
|
496
499
|
# Name
|
497
500
|
if options['name']
|
498
501
|
payload['network']['name'] = options['name']
|
@@ -813,16 +816,14 @@ class Morpheus::Cli::NetworksCommand
|
|
813
816
|
## Advanced Options
|
814
817
|
|
815
818
|
# Network Domain
|
816
|
-
if
|
817
|
-
|
818
|
-
if options['domain'].to_s.empty? # clear it?
|
819
|
+
if payload['network']['networkDomain'].nil?
|
820
|
+
if options.key?('domain') && options['domain'].to_s.empty? # clear it?
|
819
821
|
payload['network']['networkDomain'] = {'id' => nil}
|
820
822
|
else
|
821
823
|
# always prompt to handle value as name instead of id...
|
822
824
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'domain', 'fieldLabel' => 'Network Domain', 'type' => 'select', 'optionSource' => 'networkDomains', 'required' => false, 'description' => ''}], options, @api_client)
|
823
825
|
payload['network']['networkDomain'] = {'id' => v_prompt['domain'].to_i} unless v_prompt['domain'].to_s.empty?
|
824
826
|
end
|
825
|
-
end
|
826
827
|
end
|
827
828
|
|
828
829
|
# Search Domains
|
@@ -295,7 +295,7 @@ class Morpheus::Cli::Processes
|
|
295
295
|
subtitles << " Process ID: #{process_id}"
|
296
296
|
subtitles += parse_list_subtitles(options)
|
297
297
|
print_h1 title, subtitles
|
298
|
-
print_process_details(process)
|
298
|
+
print_process_details(process, options)
|
299
299
|
|
300
300
|
print_h2 "Process Events"
|
301
301
|
process_events = process['events'] || process['processEvents'] || []
|
@@ -456,7 +456,7 @@ EOT
|
|
456
456
|
subtitles = []
|
457
457
|
subtitles += parse_list_subtitles(options)
|
458
458
|
print_h1 title, subtitles
|
459
|
-
print_process_event_details(process_event)
|
459
|
+
print_process_event_details(process_event, options)
|
460
460
|
print reset, "\n"
|
461
461
|
return 0
|
462
462
|
end
|
@@ -73,15 +73,15 @@ class Morpheus::Cli::Snapshots
|
|
73
73
|
"Name" => 'name',
|
74
74
|
"Description" => 'description',
|
75
75
|
"External Id" => 'externalId',
|
76
|
-
"
|
77
|
-
"State" => 'state',
|
76
|
+
# "State" => 'state',
|
78
77
|
"Snapshot Type" => 'snapshotType',
|
79
|
-
"
|
80
|
-
"Cloud" => 'zone
|
81
|
-
"Datastore" => 'datastore',
|
82
|
-
"Parent Snapshot" => 'parentSnapshot',
|
83
|
-
"Active" => 'currentlyActive',
|
84
|
-
"Date Created" => 'dateCreated'
|
78
|
+
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
79
|
+
"Cloud" => lambda {|it| format_name_and_id(it['zone']) },
|
80
|
+
"Datastore" => lambda {|it| format_name_and_id(it['datastore']) },
|
81
|
+
"Parent Snapshot" => lambda {|it| format_name_and_id(it['parentSnapshot']) },
|
82
|
+
"Active" => lambda {|it| format_boolean(it['currentlyActive']) },
|
83
|
+
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
84
|
+
"Status" => lambda {|it| format_snapshot_status(it) }
|
85
85
|
}
|
86
86
|
print_description_list(description_cols, snapshot)
|
87
87
|
|
@@ -134,4 +134,20 @@ class Morpheus::Cli::Snapshots
|
|
134
134
|
exit 1
|
135
135
|
end
|
136
136
|
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def format_snapshot_status(snapshot, return_color=cyan)
|
141
|
+
out = ""
|
142
|
+
status_string = snapshot['status'].to_s
|
143
|
+
if status_string == 'complete'
|
144
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
145
|
+
elsif status_string == 'failed'
|
146
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
147
|
+
else
|
148
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
149
|
+
end
|
150
|
+
out
|
151
|
+
end
|
152
|
+
|
137
153
|
end
|
@@ -8,8 +8,17 @@ module Morpheus::Cli::ProcessesHelper
|
|
8
8
|
klass.send :include, Morpheus::Cli::PrintHelper
|
9
9
|
end
|
10
10
|
|
11
|
+
def api_client
|
12
|
+
raise "#{self.class} has not defined @api_client" if @api_client.nil?
|
13
|
+
@api_client
|
14
|
+
end
|
15
|
+
|
16
|
+
def processes_interface
|
17
|
+
# get_interface('processes')
|
18
|
+
api_client.processes
|
19
|
+
end
|
11
20
|
|
12
|
-
def print_process_details(process)
|
21
|
+
def print_process_details(process, options={})
|
13
22
|
description_cols = {
|
14
23
|
"Process ID" => lambda {|it| it['id'] },
|
15
24
|
"Name" => lambda {|it| it['displayName'] },
|
@@ -22,7 +31,7 @@ module Morpheus::Cli::ProcessesHelper
|
|
22
31
|
"Status" => lambda {|it| format_process_status(it) },
|
23
32
|
# "# Events" => lambda {|it| (it['events'] || []).size() },
|
24
33
|
}
|
25
|
-
print_description_list(description_cols, process)
|
34
|
+
print_description_list(description_cols, process, options)
|
26
35
|
|
27
36
|
if process['error']
|
28
37
|
print_h2 "Error"
|
@@ -39,7 +48,7 @@ module Morpheus::Cli::ProcessesHelper
|
|
39
48
|
end
|
40
49
|
end
|
41
50
|
|
42
|
-
def print_process_event_details(process_event)
|
51
|
+
def print_process_event_details(process_event, options={})
|
43
52
|
# process_event =~ process
|
44
53
|
description_cols = {
|
45
54
|
"Process ID" => lambda {|it| it['processId'] },
|
@@ -53,7 +62,7 @@ module Morpheus::Cli::ProcessesHelper
|
|
53
62
|
"Duration" => lambda {|it| format_process_duration(it) },
|
54
63
|
"Status" => lambda {|it| format_process_status(it) },
|
55
64
|
}
|
56
|
-
print_description_list(description_cols, process_event)
|
65
|
+
print_description_list(description_cols, process_event, options)
|
57
66
|
|
58
67
|
if process_event['error']
|
59
68
|
print_h2 "Error"
|
@@ -111,4 +120,25 @@ module Morpheus::Cli::ProcessesHelper
|
|
111
120
|
out
|
112
121
|
end
|
113
122
|
|
123
|
+
def wait_for_process_execution(process_id, options={}, print_output = true)
|
124
|
+
refresh_interval = 10
|
125
|
+
if options[:refresh_interval].to_i > 0
|
126
|
+
refresh_interval = options[:refresh_interval]
|
127
|
+
end
|
128
|
+
refresh_display_seconds = refresh_interval % 1.0 == 0 ? refresh_interval.to_i : refresh_interval
|
129
|
+
unless options[:quiet]
|
130
|
+
print cyan, "Refreshing every #{refresh_display_seconds} seconds until process is complete...", "\n", reset
|
131
|
+
end
|
132
|
+
process = processes_interface.get(process_id)['process']
|
133
|
+
while ['new','queued','pending','running'].include?(process['status']) do
|
134
|
+
sleep(refresh_interval)
|
135
|
+
process = processes_interface.get(process_id)['process']
|
136
|
+
end
|
137
|
+
if print_output && options[:quiet] != true
|
138
|
+
print_h1 "Process Details", [], options
|
139
|
+
print_process_details(process, options)
|
140
|
+
end
|
141
|
+
return process
|
142
|
+
end
|
143
|
+
|
114
144
|
end
|
data/lib/morpheus/cli/version.rb
CHANGED
data/lib/morpheus/formatters.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: morpheus-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.0.
|
4
|
+
version: 8.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Estes
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2025-
|
14
|
+
date: 2025-03-14 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: public_suffix
|