morpheus-cli 8.0.13 → 8.1.0
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/bin/morpheus +7 -4
- data/lib/morpheus/api/api_client.rb +8 -0
- data/lib/morpheus/api/roles_interface.rb +14 -0
- data/lib/morpheus/api/system_types_interface.rb +13 -0
- data/lib/morpheus/api/systems_interface.rb +9 -0
- data/lib/morpheus/cli/commands/backups_command.rb +31 -0
- data/lib/morpheus/cli/commands/library_option_lists_command.rb +10 -2
- data/lib/morpheus/cli/commands/roles.rb +333 -38
- data/lib/morpheus/cli/commands/service_plans_command.rb +7 -0
- data/lib/morpheus/cli/commands/storage_providers_command.rb +6 -2
- data/lib/morpheus/cli/commands/systems.rb +270 -0
- data/lib/morpheus/cli/commands/tenants_command.rb +12 -3
- data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -0
- data/lib/morpheus/cli/mixins/backups_helper.rb +29 -1
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f058297ee2bc6b9cc1dac3f97bb2aad27785687af9283e89b5275b101a126145
|
|
4
|
+
data.tar.gz: 21f00f181e173a464d579d2f6368d12fbe9880530d01fd59cebc40775eb9af78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fea8e30a0ff487c060873ca4ba6a3d4af91c52f819e6956675d653dc57c330d495b2f27b9d821e7314da332573acb8bf6bc17380a283fe1404a80789eebda5b0
|
|
7
|
+
data.tar.gz: 8d5fb8b37a542e13f5e9656e2d355b9224ed51ede6a4e749620b3dbdf059a3449b2b09e64c4d97a0b2458469d99508dbf3029a158c1a234a72736ccdc7b6437f
|
data/Dockerfile
CHANGED
data/bin/morpheus
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
require 'morpheus'
|
|
3
3
|
|
|
4
4
|
# arguments
|
|
5
|
-
args = ARGV
|
|
5
|
+
args = ARGV.dup
|
|
6
6
|
|
|
7
|
-
# input pipe
|
|
8
|
-
#
|
|
9
|
-
|
|
7
|
+
# input pipe: only read stdin as arguments when --stdin is explicitly passed.
|
|
8
|
+
# Previously stdin was always consumed when not a TTY, which caused morpheus
|
|
9
|
+
# invoked inside a piped bash script (e.g. base64 -d <<< <script> | bash -l)
|
|
10
|
+
# to consume the remaining script lines as CLI arguments (MORPH-6237).
|
|
11
|
+
# Usage: echo 42 | morpheus instances get --stdin
|
|
12
|
+
if args.delete('--stdin') && !$stdin.tty?
|
|
10
13
|
pipe_data = $stdin.read
|
|
11
14
|
if pipe_data
|
|
12
15
|
args += pipe_data.split
|
|
@@ -412,6 +412,14 @@ class Morpheus::APIClient
|
|
|
412
412
|
Morpheus::ServersInterface.new(common_interface_options).setopts(@options)
|
|
413
413
|
end
|
|
414
414
|
|
|
415
|
+
def systems
|
|
416
|
+
Morpheus::SystemsInterface.new(common_interface_options).setopts(@options)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def system_types
|
|
420
|
+
Morpheus::SystemTypesInterface.new(common_interface_options).setopts(@options)
|
|
421
|
+
end
|
|
422
|
+
|
|
415
423
|
def server_devices
|
|
416
424
|
Morpheus::ServerDevicesInterface.new(common_interface_options).setopts(@options)
|
|
417
425
|
end
|
|
@@ -118,6 +118,20 @@ class Morpheus::RolesInterface < Morpheus::APIClient
|
|
|
118
118
|
execute(method: :put, url: url, headers: headers, payload: payload.to_json)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
def update_cluster_type(account_id, id, options)
|
|
122
|
+
url = build_url(account_id, id) + "/update-cluster-type"
|
|
123
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
|
124
|
+
payload = options
|
|
125
|
+
execute(method: :put, url: url, headers: headers, payload: payload.to_json)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def validate(account_id, options, params={})
|
|
129
|
+
url = "#{@base_url}/api/roles/validate"
|
|
130
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
|
131
|
+
payload = options
|
|
132
|
+
execute(method: :post, url: url, headers: headers, payload: payload.to_json, params: params)
|
|
133
|
+
end
|
|
134
|
+
|
|
121
135
|
private
|
|
122
136
|
|
|
123
137
|
def build_url(account_id=nil, role_id=nil)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'morpheus/api/rest_interface'
|
|
2
|
+
|
|
3
|
+
class Morpheus::SystemTypesInterface < Morpheus::RestInterface
|
|
4
|
+
|
|
5
|
+
def base_path
|
|
6
|
+
"/api/infrastructure/system-types"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list_layouts(type_id, params = {})
|
|
10
|
+
execute(method: :get, url: "#{base_path}/#{type_id}/layouts", params: params)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
@@ -528,6 +528,8 @@ EOT
|
|
|
528
528
|
{
|
|
529
529
|
"ID" => 'id',
|
|
530
530
|
"Name" => 'name',
|
|
531
|
+
"Type" => lambda {|it| format_backup_type_tag(it) },
|
|
532
|
+
"Location" => lambda {|it| format_backup_location_tag(it) },
|
|
531
533
|
"Schedule" => lambda {|it| it['schedule']['name'] rescue '' },
|
|
532
534
|
"Backup Job" => lambda {|it| it['job']['name'] rescue '' },
|
|
533
535
|
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
|
@@ -539,6 +541,8 @@ EOT
|
|
|
539
541
|
{
|
|
540
542
|
"ID" => 'id',
|
|
541
543
|
"Name" => 'name',
|
|
544
|
+
"Backup Type" => lambda {|it| format_backup_type_tag(it) },
|
|
545
|
+
"Backup Location" => lambda {|it| format_backup_location_tag(it) },
|
|
542
546
|
"Location Type" => lambda {|it|
|
|
543
547
|
if it['locationType'] == "instance"
|
|
544
548
|
"Instance"
|
|
@@ -589,6 +593,33 @@ EOT
|
|
|
589
593
|
"#{result['backup']['name']} (#{format_local_dt(result['startDate'])})"
|
|
590
594
|
end
|
|
591
595
|
|
|
596
|
+
# format backup type tag (manual or policy-based)
|
|
597
|
+
def format_backup_type_tag(backup)
|
|
598
|
+
# check if backup has a schdule or job associated with it
|
|
599
|
+
# manual backups typically dont have a schedule while policy-based do
|
|
600
|
+
if backup['cronExpression'] || (backup['schedule'] && backup['schedule']['id'])
|
|
601
|
+
"#{cyan}POLICY#{reset}"
|
|
602
|
+
elsif backup['job'] && backup['job']['id']
|
|
603
|
+
"#{cyan}POLICY#{reset}"
|
|
604
|
+
else
|
|
605
|
+
"#{yellow}MANUAL#{reset}"
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# format backup location tag (local or remote)
|
|
610
|
+
def format_backup_location_tag(backup)
|
|
611
|
+
# check storage provider or backup provider indicating remote storage
|
|
612
|
+
if backup['storageProvider'] && backup['storageProvider']['id']
|
|
613
|
+
provider_type = backup['storageProvider']['type'] || backup['storageProvider']['providerType'] || ''
|
|
614
|
+
"#{green}REMOTE#{reset} (#{provider_type})"
|
|
615
|
+
elsif backup['backupProvider'] && backup['backupProvider']['id']
|
|
616
|
+
provider_type = backup['backupProvider']['type'] || backup['backupProvider']['providerType'] || ''
|
|
617
|
+
"#{green}REMOTE#{reset} (#{provider_type})"
|
|
618
|
+
else
|
|
619
|
+
"#{blue}LOCAL#{reset}"
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
592
623
|
# prompt for an instance config (vdiPool.instanceConfig)
|
|
593
624
|
def prompt_restore_instance_config(options)
|
|
594
625
|
# use config if user passed one in..
|
|
@@ -171,11 +171,17 @@ class Morpheus::Cli::LibraryOptionListsCommand
|
|
|
171
171
|
"Credentials" => lambda {|it| it['credential'] ? (it['credential']['type'] == 'local' ? '(Local)' : it['credential']['name']) : nil },
|
|
172
172
|
"Username" => 'serviceUsername',
|
|
173
173
|
"Password" => 'servicePassword',
|
|
174
|
+
"Inject System Auth Header" => lambda {|it| format_boolean it['injectExecutionLeaseAuth'] },
|
|
175
|
+
"Use Owner Authorization" => lambda {|it| format_boolean it['useOwnerAuth'] }
|
|
174
176
|
}
|
|
175
177
|
option_list_columns.delete("API Type") if option_type_list['type'] != 'api'
|
|
176
178
|
option_list_columns.delete("Credentials") if !['rest','ldap'].include?(option_type_list['type']) # || !(option_type_list['credential'] && option_type_list['credential']['id'])
|
|
177
179
|
option_list_columns.delete("Username") if !['rest','ldap'].include?(option_type_list['type']) || !(option_type_list['serviceUsername'])
|
|
178
180
|
option_list_columns.delete("Password") if !['rest','ldap'].include?(option_type_list['type']) || !(option_type_list['servicePassword'])
|
|
181
|
+
option_list_columns.delete("Inject System Auth Header") if option_type_list['type'] != 'rest'
|
|
182
|
+
option_list_columns.delete("Use Owner Authorization") if option_type_list['type'] != 'rest'
|
|
183
|
+
option_list_columns.delete("Inject System Auth Header") if option_type_list['type'] != 'rest'
|
|
184
|
+
option_list_columns.delete("Use Owner Authorization") if option_type_list['type'] != 'rest'
|
|
179
185
|
source_headers = []
|
|
180
186
|
if option_type_list['config'] && option_type_list['config']['sourceHeaders']
|
|
181
187
|
source_headers = option_type_list['config']['sourceHeaders'].collect do |header|
|
|
@@ -291,7 +297,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
|
|
|
291
297
|
end
|
|
292
298
|
end
|
|
293
299
|
# tweak payload for API
|
|
294
|
-
['ignoreSSLErrors', 'realTime'].each { |k|
|
|
300
|
+
['ignoreSSLErrors', 'realTime', 'injectExecutionLeaseAuth', 'useOwnerAuth'].each { |k|
|
|
295
301
|
list_payload[k] = ['on','true'].include?(list_payload[k].to_s) if list_payload.key?(k)
|
|
296
302
|
}
|
|
297
303
|
payload.deep_merge!({'optionTypeList' => list_payload})
|
|
@@ -348,7 +354,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
|
|
|
348
354
|
end
|
|
349
355
|
end
|
|
350
356
|
# tweak payload for API
|
|
351
|
-
['ignoreSSLErrors', 'realTime'].each { |k|
|
|
357
|
+
['ignoreSSLErrors', 'realTime', 'injectExecutionLeaseAuth', 'useOwnerAuth'].each { |k|
|
|
352
358
|
list_payload[k] = ['on','true'].include?(list_payload[k].to_s) if list_payload.key?(k)
|
|
353
359
|
}
|
|
354
360
|
payload.deep_merge!({'optionTypeList' => list_payload})
|
|
@@ -437,6 +443,8 @@ class Morpheus::Cli::LibraryOptionListsCommand
|
|
|
437
443
|
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'sourceUrl', 'fieldLabel' => 'Source Url', 'type' => 'text', 'required' => true, 'description' => "A REST URL can be used to fetch list data and is cached in the appliance database.", 'displayOrder' => 6},
|
|
438
444
|
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'ignoreSSLErrors', 'fieldLabel' => 'Ignore SSL Errors', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 7},
|
|
439
445
|
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'realTime', 'fieldLabel' => 'Real Time', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 8},
|
|
446
|
+
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'injectExecutionLeaseAuth', 'switch' => 'inject-execution-lease-auth', 'fieldLabel' => 'Inject System Auth Header', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Injects an authorization header using a system lease token when making the REST call.', 'displayOrder' => 21},
|
|
447
|
+
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'useOwnerAuth', 'switch' => 'use-owner-auth', 'fieldLabel' => 'Use Owner Authorization', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Uses the authorization credentials of the owner of the option list.', 'displayOrder' => 22},
|
|
440
448
|
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'sourceMethod', 'fieldLabel' => 'Source Method', 'type' => 'select', 'selectOptions' => [{'name' => 'GET', 'value' => 'GET'}, {'name' => 'POST', 'value' => 'POST'}], 'defaultValue' => 'GET', 'required' => true, 'displayOrder' => 9},
|
|
441
449
|
{'dependsOnCode' => 'optionTypeList.type:rest|ldap', 'fieldName' => 'credential', 'fieldLabel' => 'Credentials', 'type' => 'select', 'optionSource' => 'credentials', 'description' => 'Credential ID or use "local" to specify username and password', 'displayOrder' => 10, 'defaultValue' => "local", 'required' => true, :for_help_only => true}, # hacky way to render this but not prompt for it
|
|
442
450
|
{'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'serviceUsername', 'fieldLabel' => 'Username', 'type' => 'text', 'description' => "A Basic Auth Username for use when type is 'rest'.", 'displayOrder' => 11, "credentialFieldContext" => 'credential', "credentialFieldName" => 'username', "credentialType" => "username-password,oauth2"},
|
|
@@ -5,7 +5,7 @@ class Morpheus::Cli::Roles
|
|
|
5
5
|
include Morpheus::Cli::AccountsHelper
|
|
6
6
|
include Morpheus::Cli::ProvisioningHelper
|
|
7
7
|
include Morpheus::Cli::WhoamiHelper
|
|
8
|
-
register_subcommands :list, :get, :add, :update, :remove,
|
|
8
|
+
register_subcommands :list, :get, :add, :update, :remove, :validate,
|
|
9
9
|
:'list-permissions', :'update-feature-access',
|
|
10
10
|
:'update-group-access', :'update-global-group-access', :'update-default-group-access',
|
|
11
11
|
:'update-global-cloud-access', :'update-cloud-access', :'update-default-cloud-access',
|
|
@@ -16,7 +16,8 @@ class Morpheus::Cli::Roles
|
|
|
16
16
|
:'update-global-vdi-pool-access', :'update-vdi-pool-access', :'update-default-vdi-pool-access',
|
|
17
17
|
:'update-global-report-type-access', :'update-report-type-access', :'update-default-report-type-access',
|
|
18
18
|
:'update-global-task-access', :'update-task-access', :'update-default-task-access',
|
|
19
|
-
:'update-global-workflow-access', :'update-workflow-access', :'update-default-workflow-access'
|
|
19
|
+
:'update-global-workflow-access', :'update-workflow-access', :'update-default-workflow-access',
|
|
20
|
+
:'update-cluster-type-access', :'update-default-cluster-type-access'
|
|
20
21
|
set_subcommands_hidden(
|
|
21
22
|
subcommands.keys.select{|c|
|
|
22
23
|
c.include?('update-global')
|
|
@@ -47,7 +48,7 @@ class Morpheus::Cli::Roles
|
|
|
47
48
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
48
49
|
opts.banner = subcommand_usage("[search phrase]")
|
|
49
50
|
opts.on( '--tenant TENANT', "Tenant Filter for list of Roles." ) do |val|
|
|
50
|
-
options[:
|
|
51
|
+
options[:account] = val
|
|
51
52
|
end
|
|
52
53
|
build_standard_list_options(opts, options)
|
|
53
54
|
opts.footer = "List roles."
|
|
@@ -67,9 +68,6 @@ class Morpheus::Cli::Roles
|
|
|
67
68
|
return 0, nil
|
|
68
69
|
end
|
|
69
70
|
load_whoami()
|
|
70
|
-
if options[:tenant]
|
|
71
|
-
params[:tenant] = options[:tenant]
|
|
72
|
-
end
|
|
73
71
|
json_response = @roles_interface.list(account_id, params)
|
|
74
72
|
|
|
75
73
|
render_response(json_response, options, "roles") do
|
|
@@ -136,6 +134,9 @@ class Morpheus::Cli::Roles
|
|
|
136
134
|
opts.on(nil,'--task-access', "Display Task Access") do
|
|
137
135
|
options[:include_task_access] = true
|
|
138
136
|
end
|
|
137
|
+
opts.on(nil,'--cluster-type-access', "Display Cluster Type Access") do
|
|
138
|
+
options[:include_cluster_type_access] = true
|
|
139
|
+
end
|
|
139
140
|
opts.on('-a','--all', "Display All Access Lists") do
|
|
140
141
|
options[:include_all_access] = true
|
|
141
142
|
end
|
|
@@ -143,9 +144,7 @@ class Morpheus::Cli::Roles
|
|
|
143
144
|
options[:include_default_access] = true
|
|
144
145
|
end
|
|
145
146
|
opts.on('--account-id ID', String, "Clarify Owner of Role") do |val|
|
|
146
|
-
|
|
147
|
-
options[:account_id] = val.to_s
|
|
148
|
-
end
|
|
147
|
+
options[:account_id] = val.to_s
|
|
149
148
|
end
|
|
150
149
|
build_standard_get_options(opts, options)
|
|
151
150
|
opts.footer = <<-EOT
|
|
@@ -246,6 +245,7 @@ EOT
|
|
|
246
245
|
"VDI Pools" => lambda {|it| get_access_string(it['globalVdiPoolAccess']) },
|
|
247
246
|
"Workflows" => lambda {|it| get_access_string(it['globalTaskSetAccess']) },
|
|
248
247
|
"Tasks" => lambda {|it| get_access_string(it['globalTaskAccess']) },
|
|
248
|
+
"Cluster Types" => lambda {|it| get_access_string(it['globalClusterTypeAccess']) },
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
if role['roleType'].to_s.downcase == 'account'
|
|
@@ -449,7 +449,7 @@ EOT
|
|
|
449
449
|
workflow_permissions = role['taskSets'] ? role['taskSets'] : (json_response['taskSetPermissions'] || [])
|
|
450
450
|
print cyan
|
|
451
451
|
if options[:include_workflow_access] || options[:include_all_access]
|
|
452
|
-
print_h2 "Workflow", options
|
|
452
|
+
print_h2 "Workflow Access", options
|
|
453
453
|
rows = workflow_permissions.collect do |it|
|
|
454
454
|
{
|
|
455
455
|
name: it['name'],
|
|
@@ -461,9 +461,30 @@ EOT
|
|
|
461
461
|
end
|
|
462
462
|
print as_pretty_table(rows, [:name, :access], options)
|
|
463
463
|
elsif workflow_permissions.find {|it| it['access'] && it['access'] != 'default'}
|
|
464
|
-
print_h2 "Workflow", options
|
|
464
|
+
print_h2 "Workflow Access", options
|
|
465
465
|
print cyan,"Use --workflow-access to list custom access","\n"
|
|
466
466
|
end
|
|
467
|
+
|
|
468
|
+
cluster_type_global_access = json_response['globalClusterTypeAccess']
|
|
469
|
+
cluster_type_permissions = role['clusterTypes'] ? role['clusterTypes'] : (json_response['clusterTypePermissions'] || [])
|
|
470
|
+
print cyan
|
|
471
|
+
if options[:include_cluster_type_access] || options[:include_all_access]
|
|
472
|
+
print_h2 "Cluster Type Access", options
|
|
473
|
+
rows = cluster_type_permissions.collect do |it|
|
|
474
|
+
{
|
|
475
|
+
name: it['name'],
|
|
476
|
+
access: format_access_string(it['access'], ["none","full"]),
|
|
477
|
+
}
|
|
478
|
+
end
|
|
479
|
+
if !options[:include_default_access]
|
|
480
|
+
rows = rows.select {|row| row[:access] && row[:access] != 'default '}
|
|
481
|
+
end
|
|
482
|
+
print as_pretty_table(rows, [:name, :access], options)
|
|
483
|
+
elsif cluster_type_permissions.find {|it| it['access'] && it['access'] != 'default'}
|
|
484
|
+
print_h2 "Cluster Type Access", options
|
|
485
|
+
print cyan,"Use --cluster-type-access to list custom access","\n"
|
|
486
|
+
end
|
|
487
|
+
|
|
467
488
|
print reset,"\n"
|
|
468
489
|
return 0, nil
|
|
469
490
|
end
|
|
@@ -471,7 +492,7 @@ EOT
|
|
|
471
492
|
|
|
472
493
|
def list_permissions(args)
|
|
473
494
|
options = {}
|
|
474
|
-
available_categories = ['feature', 'group', 'cloud', 'instance-type', 'blueprint', 'report-type', 'persona', 'catalog-item-type', 'vdi-pool', 'workflow', 'task']
|
|
495
|
+
available_categories = ['feature', 'group', 'cloud', 'instance-type', 'blueprint', 'report-type', 'persona', 'catalog-item-type', 'vdi-pool', 'workflow', 'task', 'cluster-type']
|
|
475
496
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
476
497
|
opts.banner = subcommand_usage("[role] [category]")
|
|
477
498
|
build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
|
@@ -585,7 +606,7 @@ EOT
|
|
|
585
606
|
opts.banner = subcommand_usage("[name] [options]")
|
|
586
607
|
build_option_type_options(opts, options, add_role_option_types)
|
|
587
608
|
build_role_access_options(opts, options, params)
|
|
588
|
-
opts.on('--owner ID', String, "Set the owner/tenant/account for the role by account id.
|
|
609
|
+
opts.on('--owner ID', String, "Set the owner/tenant/account for the role by account id. This option requires the admin permission to manage tenants." ) do |val|
|
|
589
610
|
params['owner'] = val
|
|
590
611
|
end
|
|
591
612
|
opts.on(nil, '--include-default-access', "Include default access levels in the response (returns all available resources)") do
|
|
@@ -631,24 +652,19 @@ EOT
|
|
|
631
652
|
params['authority'] = v_prompt['authority']
|
|
632
653
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2}], options[:options])
|
|
633
654
|
params['description'] = v_prompt['description']
|
|
634
|
-
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'landingUrl', 'fieldLabel' => '
|
|
655
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'landingUrl', 'fieldLabel' => 'Landing URL', 'type' => 'text', 'displayOrder' => 3, 'description' => 'An optional override for the default landing page after login for a user.'}], options[:options])
|
|
635
656
|
params['landingUrl'] = v_prompt['landingUrl']
|
|
636
657
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
else
|
|
641
|
-
print_red_alert "You do not have the necessary authority to use owner option"
|
|
642
|
-
return
|
|
643
|
-
end
|
|
644
|
-
elsif @is_master_account && has_complete_access
|
|
658
|
+
can_manage_accounts = @user_permissions.find { |it| it['code'] == 'admin-accounts' && it['access'] == 'full'}
|
|
659
|
+
|
|
660
|
+
if can_manage_accounts
|
|
645
661
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'owner', 'fieldLabel' => 'Owner', 'type' => 'select', 'selectOptions' => role_owner_options, 'defaultValue' => current_account['id'], 'displayOrder' => 3}], options[:options])
|
|
646
662
|
params['owner'] = v_prompt['owner']
|
|
647
663
|
else
|
|
648
664
|
params['owner'] = current_account['id']
|
|
649
665
|
end
|
|
650
666
|
|
|
651
|
-
if
|
|
667
|
+
if can_manage_accounts
|
|
652
668
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'roleType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => role_type_options, 'defaultValue' => 'user', 'displayOrder' => 4}], options[:options])
|
|
653
669
|
params['roleType'] = v_prompt['roleType']
|
|
654
670
|
else
|
|
@@ -661,6 +677,12 @@ EOT
|
|
|
661
677
|
if options[:group_permissions] && params['roleType'] == 'account'
|
|
662
678
|
raise_command_error "The --groups option is only available for account roles, not user roles"
|
|
663
679
|
end
|
|
680
|
+
if params['globalZoneAccess'] && params['roleType'] == 'user'
|
|
681
|
+
raise_command_error "The --default-cloud-access option is only available for account roles, not user roles"
|
|
682
|
+
end
|
|
683
|
+
if params['globalSiteAccess'] && params['roleType'] == 'account'
|
|
684
|
+
raise_command_error "The --default-group-access option is only available for user roles, not account roles"
|
|
685
|
+
end
|
|
664
686
|
|
|
665
687
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'baseRole', 'fieldLabel' => 'Copy From Role', 'type' => 'select', 'selectOptions' => base_role_options(params), 'displayOrder' => 5}], options[:options])
|
|
666
688
|
if v_prompt['baseRole'].to_s != ''
|
|
@@ -779,9 +801,9 @@ EOT
|
|
|
779
801
|
# merge -O options into normally parsed options
|
|
780
802
|
params.deep_merge!(passed_options)
|
|
781
803
|
prompt_option_types = update_role_option_types()
|
|
782
|
-
if
|
|
783
|
-
|
|
784
|
-
end
|
|
804
|
+
# if !has_complete_access
|
|
805
|
+
# prompt_option_types = prompt_option_types.reject {|it| ['roleType', 'multitenant','multitenantLocked'].include?(it['fieldName']) }
|
|
806
|
+
# end
|
|
785
807
|
if role['roleType'] != 'user'
|
|
786
808
|
prompt_option_types = prompt_option_types.reject {|it| ['multitenant','multitenantLocked'].include?(it['fieldName']) }
|
|
787
809
|
end
|
|
@@ -792,6 +814,12 @@ EOT
|
|
|
792
814
|
if options[:group_permissions] && role['roleType'] == 'account'
|
|
793
815
|
raise_command_error "The --groups option is only available for account roles, not user roles"
|
|
794
816
|
end
|
|
817
|
+
if params['globalZoneAccess'] && role['roleType'] == 'user'
|
|
818
|
+
raise_command_error "The --default-cloud-access option is only available for account roles, not user roles"
|
|
819
|
+
end
|
|
820
|
+
if params['globalSiteAccess'] && role['roleType'] == 'account'
|
|
821
|
+
raise_command_error "The --default-group-access option is only available for user roles, not account roles"
|
|
822
|
+
end
|
|
795
823
|
# bulk role permissions
|
|
796
824
|
parse_role_access_options(options, params)
|
|
797
825
|
|
|
@@ -872,6 +900,109 @@ EOT
|
|
|
872
900
|
end
|
|
873
901
|
end
|
|
874
902
|
|
|
903
|
+
def validate(args)
|
|
904
|
+
options = {}
|
|
905
|
+
params = {}
|
|
906
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
907
|
+
opts.banner = subcommand_usage("[role] [options]")
|
|
908
|
+
build_option_type_options(opts, options, add_role_option_types)
|
|
909
|
+
build_role_access_options(opts, options, params)
|
|
910
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
|
911
|
+
opts.footer = <<-EOT
|
|
912
|
+
Validate role permissions without creating or updating a role.
|
|
913
|
+
[role] is optional. This is the name (authority) or id of a role.
|
|
914
|
+
This is useful for testing permission configurations before applying them.
|
|
915
|
+
All the role permissions and access values can be validated.
|
|
916
|
+
Use --feature-access "CODE=ACCESS,CODE=ACCESS" to validate access levels for specific feature permissions.
|
|
917
|
+
Example: morpheus roles validate --authority "Test Role" --feature-access "admin=full,activity=read"
|
|
918
|
+
Example: morpheus roles validate "Existing Role" --feature-access "activity=full"
|
|
919
|
+
EOT
|
|
920
|
+
end
|
|
921
|
+
optparse.parse!(args)
|
|
922
|
+
|
|
923
|
+
# allow 0-1 arguments
|
|
924
|
+
verify_args!(args:args, optparse:optparse, max:1)
|
|
925
|
+
|
|
926
|
+
connect(options)
|
|
927
|
+
begin
|
|
928
|
+
account = find_account_from_options(options)
|
|
929
|
+
account_id = account ? account['id'] : nil
|
|
930
|
+
|
|
931
|
+
# load existing role if arg passed
|
|
932
|
+
role = nil
|
|
933
|
+
if args[0]
|
|
934
|
+
role = find_role_by_name_or_id(account_id, args[0])
|
|
935
|
+
exit 1 if role.nil?
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
|
|
939
|
+
payload = nil
|
|
940
|
+
|
|
941
|
+
if options[:payload]
|
|
942
|
+
payload = options[:payload]
|
|
943
|
+
payload.deep_merge!({'role' => passed_options}) unless passed_options.empty?
|
|
944
|
+
else
|
|
945
|
+
# merge -O options into normally parsed options
|
|
946
|
+
params.deep_merge!(passed_options)
|
|
947
|
+
|
|
948
|
+
# Parse role access options
|
|
949
|
+
parse_role_access_options(options, params)
|
|
950
|
+
|
|
951
|
+
# Validate role type constraints
|
|
952
|
+
role_type = role ? role['roleType'] : params['roleType']
|
|
953
|
+
if role_type
|
|
954
|
+
if params['globalZoneAccess'] && role_type == 'user'
|
|
955
|
+
raise_command_error "The --default-cloud-access option is only available for account roles, not user roles"
|
|
956
|
+
end
|
|
957
|
+
if params['globalSiteAccess'] && role_type == 'account'
|
|
958
|
+
raise_command_error "The --default-group-access option is only available for user roles, not account roles"
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
if params.empty? && passed_options.empty? && role.nil?
|
|
963
|
+
raise_command_error "Specify at least one role configuration option to validate.\n#{optparse}"
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
payload = {"role" => params}
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
if role
|
|
970
|
+
payload['role']['id'] = role['id']
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
query_params = parse_query_options(options)
|
|
974
|
+
@roles_interface.setopts(options)
|
|
975
|
+
|
|
976
|
+
if options[:dry_run]
|
|
977
|
+
print_dry_run @roles_interface.dry.validate(account_id, payload, query_params)
|
|
978
|
+
return 0, nil
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
json_response = @roles_interface.validate(account_id, payload, query_params)
|
|
982
|
+
|
|
983
|
+
render_response(json_response, options) do
|
|
984
|
+
if json_response['success'] && json_response['valid']
|
|
985
|
+
print_green_success json_response['msg'] || "Role permissions are valid"
|
|
986
|
+
else
|
|
987
|
+
print_red_alert "Validation failed: #{json_response['msg'] || 'Invalid role permissions'}"
|
|
988
|
+
if json_response['errors'] && !json_response['errors'].empty?
|
|
989
|
+
print_h2 "Validation Errors", options
|
|
990
|
+
json_response['errors'].each do |key, msg|
|
|
991
|
+
print red, " #{key}: #{msg}", reset, "\n"
|
|
992
|
+
end
|
|
993
|
+
end
|
|
994
|
+
end
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
# Return exit code based on validation result
|
|
998
|
+
return json_response['success'] && json_response['valid'] ? 0 : 1
|
|
999
|
+
|
|
1000
|
+
rescue RestClient::Exception => e
|
|
1001
|
+
print_rest_exception(e, options)
|
|
1002
|
+
return 1
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
875
1006
|
def update_feature_access(args)
|
|
876
1007
|
options = {}
|
|
877
1008
|
allowed_access_values = ["full", "full_decrypted", "group", "listfiles", "managerules", "no", "none", "provision", "read", "rolemappings", "user", "view", "yes"]
|
|
@@ -979,6 +1110,10 @@ EOT
|
|
|
979
1110
|
role = find_role_by_name_or_id(account_id, name)
|
|
980
1111
|
exit 1 if role.nil?
|
|
981
1112
|
|
|
1113
|
+
if role['roleType'] == 'account'
|
|
1114
|
+
raise_command_error "The default-group-access command is only available for user roles, not account roles"
|
|
1115
|
+
end
|
|
1116
|
+
|
|
982
1117
|
params = {permissionCode: 'ComputeSite', access: access_value}
|
|
983
1118
|
@roles_interface.setopts(options)
|
|
984
1119
|
if options[:dry_run]
|
|
@@ -1132,6 +1267,10 @@ EOT
|
|
|
1132
1267
|
role = find_role_by_name_or_id(account_id, name)
|
|
1133
1268
|
exit 1 if role.nil?
|
|
1134
1269
|
|
|
1270
|
+
if role['roleType'] == 'user'
|
|
1271
|
+
raise_command_error "The default-cloud-access command is only available for account roles, not user roles"
|
|
1272
|
+
end
|
|
1273
|
+
|
|
1135
1274
|
params = {permissionCode: 'ComputeZone', access: access_value}
|
|
1136
1275
|
@roles_interface.setopts(options)
|
|
1137
1276
|
if options[:dry_run]
|
|
@@ -2475,6 +2614,153 @@ Update default workflow access for a role.
|
|
|
2475
2614
|
end
|
|
2476
2615
|
end
|
|
2477
2616
|
|
|
2617
|
+
def update_default_cluster_type_access(args)
|
|
2618
|
+
options = {}
|
|
2619
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
2620
|
+
opts.banner = subcommand_usage("[role] [access]")
|
|
2621
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
|
2622
|
+
opts.footer = <<-EOT
|
|
2623
|
+
Update default cluster type access for a role.
|
|
2624
|
+
[role] is required. This is the id of a role.
|
|
2625
|
+
[access] is required. This is the access level to assign: full or none.
|
|
2626
|
+
EOT
|
|
2627
|
+
end
|
|
2628
|
+
optparse.parse!(args)
|
|
2629
|
+
verify_args!(args:args, optparse:optparse, count: 2)
|
|
2630
|
+
name = args[0]
|
|
2631
|
+
access_value = args[1].to_s.downcase
|
|
2632
|
+
if !['full', 'none', 'custom'].include?(access_value)
|
|
2633
|
+
raise_command_error("invalid access value: #{args[1]}", args, optparse)
|
|
2634
|
+
end
|
|
2635
|
+
|
|
2636
|
+
connect(options)
|
|
2637
|
+
begin
|
|
2638
|
+
account = find_account_from_options(options)
|
|
2639
|
+
account_id = account ? account['id'] : nil
|
|
2640
|
+
role = find_role_by_name_or_id(account_id, name)
|
|
2641
|
+
exit 1 if role.nil?
|
|
2642
|
+
params = {permissionCode: 'ServerGroupType', access: access_value}
|
|
2643
|
+
@roles_interface.setopts(options)
|
|
2644
|
+
if options[:dry_run]
|
|
2645
|
+
print_dry_run @roles_interface.dry.update_permission(account_id, role['id'], params)
|
|
2646
|
+
return
|
|
2647
|
+
end
|
|
2648
|
+
json_response = @roles_interface.update_permission(account_id, role['id'], params)
|
|
2649
|
+
|
|
2650
|
+
if options[:json]
|
|
2651
|
+
print JSON.pretty_generate(json_response)
|
|
2652
|
+
print "\n"
|
|
2653
|
+
else
|
|
2654
|
+
print_green_success "Role #{role['authority']} default cluster type access updated"
|
|
2655
|
+
end
|
|
2656
|
+
rescue RestClient::Exception => e
|
|
2657
|
+
print_rest_exception(e, options)
|
|
2658
|
+
exit 1
|
|
2659
|
+
end
|
|
2660
|
+
end
|
|
2661
|
+
|
|
2662
|
+
def update_cluster_type_access(args)
|
|
2663
|
+
options = {}
|
|
2664
|
+
cluster_type_id = nil
|
|
2665
|
+
access_value = nil
|
|
2666
|
+
do_all = false
|
|
2667
|
+
allowed_access_values = ['full', 'none', 'default']
|
|
2668
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
2669
|
+
opts.banner = subcommand_usage("[role] [cluster-type] [access]")
|
|
2670
|
+
opts.on( '--cluster-type ID', String, "Cluster Type ID, code or Name" ) do |val|
|
|
2671
|
+
cluster_type_id = val
|
|
2672
|
+
end
|
|
2673
|
+
opts.on( nil, '--all', "Update all cluster types at once." ) do
|
|
2674
|
+
do_all = true
|
|
2675
|
+
end
|
|
2676
|
+
opts.on( '--access VALUE', String, "Access value [#{allowed_access_values.join('|')}]" ) do |val|
|
|
2677
|
+
access_value = val
|
|
2678
|
+
end
|
|
2679
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
|
2680
|
+
opts.footer = "Update role access for a cluster type or all cluster types.\n" +
|
|
2681
|
+
"[role] is required. This is the name or id of a role.\n" +
|
|
2682
|
+
"--cluster-type or --all is required. This is the name, code or id of a cluster type.\n" +
|
|
2683
|
+
"--access is required. This is the new access value: #{ored_list(allowed_access_values)}"
|
|
2684
|
+
end
|
|
2685
|
+
optparse.parse!(args)
|
|
2686
|
+
|
|
2687
|
+
name = args[0]
|
|
2688
|
+
if do_all
|
|
2689
|
+
verify_args!(args:args, optparse:optparse, min:1, max:2)
|
|
2690
|
+
access_value = args[1] if args[1]
|
|
2691
|
+
else
|
|
2692
|
+
verify_args!(args:args, optparse:optparse, min:1, max:3)
|
|
2693
|
+
cluster_type_id = args[1] if args[1]
|
|
2694
|
+
access_value = args[2] if args[2]
|
|
2695
|
+
end
|
|
2696
|
+
if !cluster_type_id && !do_all
|
|
2697
|
+
raise_command_error("missing required argument: [cluster-type] or --all", args, optparse)
|
|
2698
|
+
end
|
|
2699
|
+
if !access_value
|
|
2700
|
+
raise_command_error("missing required argument: [access]", args, optparse)
|
|
2701
|
+
end
|
|
2702
|
+
access_value = access_value.to_s.downcase
|
|
2703
|
+
if !allowed_access_values.include?(access_value)
|
|
2704
|
+
raise_command_error("invalid access value: #{access_value}", args, optparse)
|
|
2705
|
+
puts optparse
|
|
2706
|
+
return 1
|
|
2707
|
+
end
|
|
2708
|
+
|
|
2709
|
+
connect(options)
|
|
2710
|
+
begin
|
|
2711
|
+
account = find_account_from_options(options)
|
|
2712
|
+
account_id = account ? account['id'] : nil
|
|
2713
|
+
role = find_role_by_name_or_id(account_id, name)
|
|
2714
|
+
return 1 if role.nil?
|
|
2715
|
+
|
|
2716
|
+
role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
|
|
2717
|
+
cluster_type_permissions = role_json['clusterTypePermissions'] || role_json['clusterTypes'] || []
|
|
2718
|
+
|
|
2719
|
+
# hacky, but support name or code lookup via the list returned in the show payload
|
|
2720
|
+
cluster_type = nil
|
|
2721
|
+
if !do_all
|
|
2722
|
+
if cluster_type_id.to_s =~ /\A\d{1,}\Z/
|
|
2723
|
+
cluster_type = cluster_type_permissions.find {|b| b['id'] == cluster_type_id.to_i }
|
|
2724
|
+
else
|
|
2725
|
+
cluster_type = cluster_type_permissions.find {|b| b['name'] == cluster_type_id }
|
|
2726
|
+
end
|
|
2727
|
+
if cluster_type.nil?
|
|
2728
|
+
print_red_alert "Cluster Type not found: '#{cluster_type_id}'"
|
|
2729
|
+
return 1
|
|
2730
|
+
end
|
|
2731
|
+
end
|
|
2732
|
+
|
|
2733
|
+
params = {}
|
|
2734
|
+
if do_all
|
|
2735
|
+
params['allClusterTypes'] = true
|
|
2736
|
+
else
|
|
2737
|
+
params['clusterTypeId'] = cluster_type['id']
|
|
2738
|
+
end
|
|
2739
|
+
params['access'] = access_value == 'default' ? nil : access_value
|
|
2740
|
+
@roles_interface.setopts(options)
|
|
2741
|
+
if options[:dry_run]
|
|
2742
|
+
print_dry_run @roles_interface.dry.update_cluster_type(account_id, role['id'], params)
|
|
2743
|
+
return
|
|
2744
|
+
end
|
|
2745
|
+
json_response = @roles_interface.update_cluster_type(account_id, role['id'], params)
|
|
2746
|
+
|
|
2747
|
+
if options[:json]
|
|
2748
|
+
print JSON.pretty_generate(json_response)
|
|
2749
|
+
print "\n"
|
|
2750
|
+
else
|
|
2751
|
+
if do_all
|
|
2752
|
+
print_green_success "Role #{role['authority']} access updated for all cluster types"
|
|
2753
|
+
else
|
|
2754
|
+
print_green_success "Role #{role['authority']} access updated for cluster type #{cluster_type['name']}"
|
|
2755
|
+
end
|
|
2756
|
+
end
|
|
2757
|
+
return 0
|
|
2758
|
+
rescue RestClient::Exception => e
|
|
2759
|
+
print_rest_exception(e, options)
|
|
2760
|
+
exit 1
|
|
2761
|
+
end
|
|
2762
|
+
end
|
|
2763
|
+
|
|
2478
2764
|
private
|
|
2479
2765
|
|
|
2480
2766
|
def add_role_option_types
|
|
@@ -2511,21 +2797,10 @@ Update default workflow access for a role.
|
|
|
2511
2797
|
end
|
|
2512
2798
|
|
|
2513
2799
|
def base_role_options(role_payload)
|
|
2514
|
-
params = {"tenantId" => role_payload['owner'], "
|
|
2800
|
+
params = {"tenantId" => role_payload['owner'], "roleType" => role_payload['roleType'] }
|
|
2515
2801
|
@options_interface.options_for_source("copyFromRole", params)['data']
|
|
2516
2802
|
end
|
|
2517
2803
|
|
|
2518
|
-
def has_complete_access
|
|
2519
|
-
has_access = false
|
|
2520
|
-
if @is_master_account
|
|
2521
|
-
admin_accounts = @user_permissions.select { |it| it['code'] == 'admin-accounts' && it['access'] == 'full'}
|
|
2522
|
-
admin_roles = @user_permissions.select { |it| it['code'] == 'admin-roles' && it['access'] == 'full' }
|
|
2523
|
-
if admin_accounts != nil && admin_roles != nil
|
|
2524
|
-
has_access = true
|
|
2525
|
-
end
|
|
2526
|
-
end
|
|
2527
|
-
has_access
|
|
2528
|
-
end
|
|
2529
2804
|
|
|
2530
2805
|
def parse_access_csv(output, val)
|
|
2531
2806
|
output ||= {}
|
|
@@ -2664,6 +2939,13 @@ Update default workflow access for a role.
|
|
|
2664
2939
|
options[:workflow_permissions] ||= {}
|
|
2665
2940
|
parse_access_csv(options[:workflow_permissions], val)
|
|
2666
2941
|
end
|
|
2942
|
+
opts.on('--default-cluster-type-access ACCESS', String, "Set the default cluster type access: [none|full]" ) do |val|
|
|
2943
|
+
params['globalTaskSetAccess'] = val.to_s.downcase
|
|
2944
|
+
end
|
|
2945
|
+
opts.on('--cluster-types CODE=ACCESS', String, "Set cluster type to a custom access by cluster type code. Example: kubernetes-cluster=none,mvm-cluster=full" ) do |val|
|
|
2946
|
+
options[:cluster_type_permissions] ||= {}
|
|
2947
|
+
parse_access_csv(options[:cluster_type_permissions], val)
|
|
2948
|
+
end
|
|
2667
2949
|
opts.on('--reset-permissions', "Reset all feature permission access to none. This can be used in conjunction with --permissions to recreate the feature permission access for the role." ) do
|
|
2668
2950
|
options[:reset_permissions] = true
|
|
2669
2951
|
end
|
|
@@ -2814,6 +3096,19 @@ Update default workflow access for a role.
|
|
|
2814
3096
|
end
|
|
2815
3097
|
params['taskSets'] = perms_array
|
|
2816
3098
|
end
|
|
3099
|
+
if options[:cluster_type_permissions]
|
|
3100
|
+
perms_array = []
|
|
3101
|
+
options[:cluster_type_permissions].each do |k,v|
|
|
3102
|
+
cluster_type_code = k
|
|
3103
|
+
access_value = v.to_s.empty? ? "none" : v.to_s
|
|
3104
|
+
if cluster_type_code =~ /\A\d{1,}\Z/
|
|
3105
|
+
perms_array << {"id" => cluster_type_code.to_i, "access" => access_value}
|
|
3106
|
+
else
|
|
3107
|
+
perms_array << {"code" => cluster_type_code, "access" => access_value}
|
|
3108
|
+
end
|
|
3109
|
+
end
|
|
3110
|
+
params['clusterTypes'] = perms_array
|
|
3111
|
+
end
|
|
2817
3112
|
if options[:reset_permissions]
|
|
2818
3113
|
params["resetPermissions"] = true
|
|
2819
3114
|
end
|
|
@@ -168,6 +168,7 @@ class Morpheus::Cli::ServicePlanCommand
|
|
|
168
168
|
description_cols['Core Count'] = lambda {|it| it['maxCores']}
|
|
169
169
|
description_cols['Custom Cores'] = lambda {|it| format_boolean(it['customCores'])}
|
|
170
170
|
description_cols['Cores Per Socket'] = lambda {|it| it['coresPerSocket']} if provision_type['hasConfigurableCpuSockets'] && service_plan['customCores']
|
|
171
|
+
description_cols['Custom CPU'] = lambda {|it| format_boolean(it['customCpu'])}
|
|
171
172
|
|
|
172
173
|
ranges = (service_plan['config'] ? service_plan['config']['ranges'] : nil) || {}
|
|
173
174
|
|
|
@@ -279,6 +280,9 @@ class Morpheus::Cli::ServicePlanCommand
|
|
|
279
280
|
opts.on('--custom-cores [on|off]', String, "Can be used to enable / disable customizable cores. Default is on") do |val|
|
|
280
281
|
params['customCores'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
281
282
|
end
|
|
283
|
+
opts.on('--custom-cpu [on|off]', String, "Can be used to enable / disable customizable CPUs. Default is on") do |val|
|
|
284
|
+
params['customCpu'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
285
|
+
end
|
|
282
286
|
opts.on('--custom-storage [on|off]', String, "Can be used to enable / disable customizable storage. Default is on") do |val|
|
|
283
287
|
params['customMaxStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
284
288
|
end
|
|
@@ -551,6 +555,9 @@ class Morpheus::Cli::ServicePlanCommand
|
|
|
551
555
|
opts.on('--custom-cores [on|off]', String, "Can be used to enable / disable customizable cores. Default is on") do |val|
|
|
552
556
|
params['customCores'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
553
557
|
end
|
|
558
|
+
opts.on('--custom-cpu [on|off]', String, "Can be used to enable / disable customizable CPUs. Default is on") do |val|
|
|
559
|
+
params['customCpu'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
560
|
+
end
|
|
554
561
|
opts.on('--custom-storage [on|off]', String, "Can be used to enable / disable customizable storage. Default is on") do |val|
|
|
555
562
|
params['customMaxStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
|
556
563
|
end
|
|
@@ -513,8 +513,12 @@ class Morpheus::Cli::StorageProvidersCommand
|
|
|
513
513
|
|
|
514
514
|
def remove(args)
|
|
515
515
|
options = {}
|
|
516
|
+
params = {:removeResources => 'on'}
|
|
516
517
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
517
518
|
opts.banner = subcommand_usage("[storage-bucket]")
|
|
519
|
+
opts.on('--remove-resources [on|off]', ['on','off'], "Remove From Server. Default is on.") do |val|
|
|
520
|
+
params[:removeResources] = val.nil? ? 'on' : val
|
|
521
|
+
end
|
|
518
522
|
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
|
519
523
|
opts.footer = "Delete a storage bucket." + "\n" +
|
|
520
524
|
"[storage-bucket] is required. This is the name or id of a storage bucket."
|
|
@@ -537,10 +541,10 @@ class Morpheus::Cli::StorageProvidersCommand
|
|
|
537
541
|
end
|
|
538
542
|
@storage_providers_interface.setopts(options)
|
|
539
543
|
if options[:dry_run]
|
|
540
|
-
print_dry_run @storage_providers_interface.dry.destroy(storage_provider['id'])
|
|
544
|
+
print_dry_run @storage_providers_interface.dry.destroy(storage_provider['id'], params)
|
|
541
545
|
return 0
|
|
542
546
|
end
|
|
543
|
-
json_response = @storage_providers_interface.destroy(storage_provider['id'])
|
|
547
|
+
json_response = @storage_providers_interface.destroy(storage_provider['id'], params)
|
|
544
548
|
if options[:json]
|
|
545
549
|
print JSON.pretty_generate(json_response)
|
|
546
550
|
print "\n"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
|
2
|
+
|
|
3
|
+
class Morpheus::Cli::Systems
|
|
4
|
+
include Morpheus::Cli::CliCommand
|
|
5
|
+
include Morpheus::Cli::RestCommand
|
|
6
|
+
|
|
7
|
+
set_command_name :systems
|
|
8
|
+
set_command_description "View and manage systems."
|
|
9
|
+
register_subcommands :list, :get, :add, :update, :remove
|
|
10
|
+
|
|
11
|
+
set_command_hidden
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
# Systems API uses lowercase keys in payloads.
|
|
16
|
+
def system_object_key
|
|
17
|
+
'system'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def system_list_key
|
|
21
|
+
'systems'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def system_list_column_definitions(options)
|
|
25
|
+
{
|
|
26
|
+
"ID" => 'id',
|
|
27
|
+
"Name" => 'name',
|
|
28
|
+
"Type" => lambda {|it| it['type'] ? it['type']['name'] : '' },
|
|
29
|
+
"Layout" => lambda {|it| it['layout'] ? it['layout']['name'] : '' },
|
|
30
|
+
"Status" => 'status',
|
|
31
|
+
"Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
|
32
|
+
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) }
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def system_column_definitions(options)
|
|
37
|
+
{
|
|
38
|
+
"ID" => 'id',
|
|
39
|
+
"Name" => 'name',
|
|
40
|
+
"Description" => 'description',
|
|
41
|
+
"Status" => 'status',
|
|
42
|
+
"Status Message" => 'statusMessage',
|
|
43
|
+
"Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
|
44
|
+
"External ID" => 'externalId',
|
|
45
|
+
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
|
46
|
+
"Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_response_for_get(json_response, options)
|
|
51
|
+
render_response(json_response, options, rest_object_key) do
|
|
52
|
+
record = json_response[rest_object_key] || json_response
|
|
53
|
+
print_h1 rest_label, [], options
|
|
54
|
+
print cyan
|
|
55
|
+
print_description_list(rest_column_definitions(options), record, options)
|
|
56
|
+
print reset,"\n"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def add(args)
|
|
61
|
+
options = {}
|
|
62
|
+
params = {}
|
|
63
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
64
|
+
opts.banner = subcommand_usage("[name]")
|
|
65
|
+
opts.on('--name NAME', String, "System Name") do |val|
|
|
66
|
+
params['name'] = val.to_s
|
|
67
|
+
end
|
|
68
|
+
opts.on('--description [TEXT]', String, "Description") do |val|
|
|
69
|
+
params['description'] = val.to_s
|
|
70
|
+
end
|
|
71
|
+
opts.on('--type TYPE', String, "System Type ID or name") do |val|
|
|
72
|
+
params['type'] = val
|
|
73
|
+
end
|
|
74
|
+
opts.on('--layout LAYOUT', String, "System Layout ID or name") do |val|
|
|
75
|
+
params['layout'] = val
|
|
76
|
+
end
|
|
77
|
+
build_standard_add_options(opts, options)
|
|
78
|
+
opts.footer = "Create a new system.\n[name] is optional and can be passed as the first argument."
|
|
79
|
+
end
|
|
80
|
+
optparse.parse!(args)
|
|
81
|
+
connect(options)
|
|
82
|
+
|
|
83
|
+
payload = nil
|
|
84
|
+
if options[:payload]
|
|
85
|
+
payload = options[:payload]
|
|
86
|
+
payload[rest_object_key] ||= {}
|
|
87
|
+
payload[rest_object_key].deep_merge!(params) unless params.empty?
|
|
88
|
+
payload[rest_object_key]['name'] ||= args[0] if args[0]
|
|
89
|
+
else
|
|
90
|
+
system_payload = {}
|
|
91
|
+
|
|
92
|
+
# Name
|
|
93
|
+
system_payload['name'] = params['name'] || args[0]
|
|
94
|
+
if !system_payload['name'] && !options[:no_prompt]
|
|
95
|
+
system_payload['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true}], options[:options], @api_client, {})['name']
|
|
96
|
+
end
|
|
97
|
+
raise_command_error "Name is required.\n#{optparse}" if system_payload['name'].to_s.empty?
|
|
98
|
+
|
|
99
|
+
# Description
|
|
100
|
+
if !params['description'] && !options[:no_prompt]
|
|
101
|
+
system_payload['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false}], options[:options], @api_client, {})['description']
|
|
102
|
+
else
|
|
103
|
+
system_payload['description'] = params['description']
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Type
|
|
107
|
+
available_types = system_types_for_dropdown
|
|
108
|
+
type_val = params['type']
|
|
109
|
+
if type_val
|
|
110
|
+
type_id = type_val =~ /\A\d+\Z/ ? type_val.to_i : available_types.find { |t| t['name'] == type_val || t['code'] == type_val }&.dig('id')
|
|
111
|
+
raise_command_error "System type not found: #{type_val}" unless type_id
|
|
112
|
+
system_payload['type'] = {'id' => type_id}
|
|
113
|
+
elsif !options[:no_prompt]
|
|
114
|
+
if available_types.empty?
|
|
115
|
+
raise_command_error "No system types found."
|
|
116
|
+
else
|
|
117
|
+
print cyan, "Available System Types\n", reset
|
|
118
|
+
available_types.each do |t|
|
|
119
|
+
print " #{t['id']}) #{t['name']}#{t['code'] ? " (#{t['code']})" : ''}\n"
|
|
120
|
+
end
|
|
121
|
+
selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'text', 'fieldLabel' => 'System Type ID', 'required' => true}], options[:options], @api_client, {})['type']
|
|
122
|
+
type_id = available_types.find { |t| t['id'].to_s == selected.to_s }&.dig('id')
|
|
123
|
+
raise_command_error "Invalid system type id: #{selected}" unless type_id
|
|
124
|
+
system_payload['type'] = {'id' => type_id}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Layout
|
|
129
|
+
available_layouts = system_layouts_for_dropdown(system_payload.dig('type', 'id'))
|
|
130
|
+
layout_val = params['layout']
|
|
131
|
+
if layout_val
|
|
132
|
+
layout_id = layout_val =~ /\A\d+\Z/ ? layout_val.to_i : available_layouts.find { |l| l['name'] == layout_val || l['code'] == layout_val }&.dig('id')
|
|
133
|
+
raise_command_error "System layout not found: #{layout_val}" unless layout_id
|
|
134
|
+
system_payload['layout'] = {'id' => layout_id}
|
|
135
|
+
elsif !options[:no_prompt]
|
|
136
|
+
if available_layouts.empty?
|
|
137
|
+
raise_command_error "No system layouts found for selected type."
|
|
138
|
+
else
|
|
139
|
+
print cyan, "Available System Layouts\n", reset
|
|
140
|
+
available_layouts.each do |l|
|
|
141
|
+
print " #{l['id']}) #{l['name']}#{l['code'] ? " (#{l['code']})" : ''}\n"
|
|
142
|
+
end
|
|
143
|
+
selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'text', 'fieldLabel' => 'System Layout ID', 'required' => true}], options[:options], @api_client, {})['layout']
|
|
144
|
+
layout_id = available_layouts.find { |l| l['id'].to_s == selected.to_s }&.dig('id')
|
|
145
|
+
raise_command_error "Invalid system layout id: #{selected}" unless layout_id
|
|
146
|
+
system_payload['layout'] = {'id' => layout_id}
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
payload = {rest_object_key => system_payload}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if options[:dry_run]
|
|
154
|
+
print_dry_run rest_interface.dry.create(payload)
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
rest_interface.setopts(options)
|
|
159
|
+
json_response = rest_interface.create(payload)
|
|
160
|
+
render_response(json_response, options, rest_object_key) do
|
|
161
|
+
system_id = json_response['id'] || json_response.dig(rest_object_key, 'id')
|
|
162
|
+
print_green_success "System created"
|
|
163
|
+
get([system_id.to_s] + (options[:remote] ? ['-r', options[:remote]] : [])) if system_id
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def remove(args)
|
|
168
|
+
options = {}
|
|
169
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
170
|
+
opts.banner = subcommand_usage("[system]")
|
|
171
|
+
build_standard_remove_options(opts, options)
|
|
172
|
+
opts.footer = <<-EOT
|
|
173
|
+
Delete an existing system.
|
|
174
|
+
[system] is required. This is the name or id of a system.
|
|
175
|
+
EOT
|
|
176
|
+
end
|
|
177
|
+
optparse.parse!(args)
|
|
178
|
+
verify_args!(args: args, optparse: optparse, count: 1)
|
|
179
|
+
connect(options)
|
|
180
|
+
|
|
181
|
+
system = nil
|
|
182
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
|
183
|
+
json_response = rest_interface.get(args[0].to_i)
|
|
184
|
+
system = json_response[rest_object_key] || json_response
|
|
185
|
+
else
|
|
186
|
+
system = find_by_name(rest_key, args[0])
|
|
187
|
+
end
|
|
188
|
+
return 1, "System not found for '#{args[0]}'" if system.nil?
|
|
189
|
+
|
|
190
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the system #{system['name']}?")
|
|
191
|
+
return 9, "aborted"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if options[:dry_run]
|
|
195
|
+
print_dry_run rest_interface.dry.destroy(system['id'])
|
|
196
|
+
return
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
rest_interface.setopts(options)
|
|
200
|
+
json_response = rest_interface.destroy(system['id'])
|
|
201
|
+
render_response(json_response, options) do
|
|
202
|
+
print_green_success "System #{system['name']} removed"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def update(args)
|
|
207
|
+
options = {}
|
|
208
|
+
params = {}
|
|
209
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
210
|
+
opts.banner = subcommand_usage("[system] --name --description")
|
|
211
|
+
opts.on("--name NAME", String, "Updates System Name") do |val|
|
|
212
|
+
params['name'] = val.to_s
|
|
213
|
+
end
|
|
214
|
+
opts.on("--description [TEXT]", String, "Updates System Description") do |val|
|
|
215
|
+
params['description'] = val.to_s
|
|
216
|
+
end
|
|
217
|
+
build_standard_update_options(opts, options, [:find_by_name])
|
|
218
|
+
opts.footer = <<-EOT
|
|
219
|
+
Update an existing system.
|
|
220
|
+
[system] is required. This is the name or id of a system.
|
|
221
|
+
EOT
|
|
222
|
+
end
|
|
223
|
+
optparse.parse!(args)
|
|
224
|
+
verify_args!(args: args, optparse: optparse, count: 1)
|
|
225
|
+
connect(options)
|
|
226
|
+
|
|
227
|
+
system = nil
|
|
228
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
|
229
|
+
json_response = rest_interface.get(args[0].to_i)
|
|
230
|
+
system = json_response[rest_object_key] || json_response
|
|
231
|
+
else
|
|
232
|
+
system = find_by_name(rest_key, args[0])
|
|
233
|
+
end
|
|
234
|
+
return 1, "System not found for '#{args[0]}'" if system.nil?
|
|
235
|
+
|
|
236
|
+
passed_options = parse_passed_options(options)
|
|
237
|
+
params.deep_merge!(passed_options) unless passed_options.empty?
|
|
238
|
+
params.booleanize!
|
|
239
|
+
|
|
240
|
+
payload = parse_payload(options) || {rest_object_key => params}
|
|
241
|
+
if payload[rest_object_key].nil? || payload[rest_object_key].empty?
|
|
242
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
if options[:dry_run]
|
|
246
|
+
print_dry_run rest_interface.dry.update(system['id'], payload)
|
|
247
|
+
return
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
rest_interface.setopts(options)
|
|
251
|
+
json_response = rest_interface.update(system['id'], payload)
|
|
252
|
+
render_response(json_response, options, rest_object_key) do
|
|
253
|
+
print_green_success "Updated system #{system['id']}"
|
|
254
|
+
get([system['id']] + (options[:remote] ? ['-r', options[:remote]] : []))
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def system_types_for_dropdown
|
|
259
|
+
result = @api_client.system_types.list({'max' => 100})
|
|
260
|
+
items = result ? (result['systemTypes'] || result[:systemTypes] || result['types'] || result[:types] || []) : []
|
|
261
|
+
items.map { |t| {'id' => t['id'] || t[:id], 'name' => t['name'] || t[:name], 'value' => (t['id'] || t[:id]).to_s, 'code' => t['code'] || t[:code]} }
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def system_layouts_for_dropdown(type_id = nil)
|
|
265
|
+
return [] if type_id.nil?
|
|
266
|
+
result = @api_client.system_types.list_layouts(type_id, {'max' => 100})
|
|
267
|
+
items = result ? (result['systemTypeLayouts'] || result[:systemTypeLayouts] || result['layouts'] || result[:layouts] || []) : []
|
|
268
|
+
items.map { |l| {'id' => l['id'] || l[:id], 'name' => l['name'] || l[:name], 'value' => (l['id'] || l[:id]).to_s, 'code' => l['code'] || l[:code]} }
|
|
269
|
+
end
|
|
270
|
+
end
|
|
@@ -3,6 +3,7 @@ require 'morpheus/cli/cli_command'
|
|
|
3
3
|
class Morpheus::Cli::TenantsCommand
|
|
4
4
|
include Morpheus::Cli::CliCommand
|
|
5
5
|
include Morpheus::Cli::AccountsHelper
|
|
6
|
+
include Morpheus::Cli::WhoamiHelper
|
|
6
7
|
set_command_name :tenants
|
|
7
8
|
set_command_description "View and manage tenants (accounts)."
|
|
8
9
|
register_subcommands :list, :count, :get, :add, :update, :remove
|
|
@@ -21,6 +22,7 @@ class Morpheus::Cli::TenantsCommand
|
|
|
21
22
|
@account_users_interface = @api_client.account_users
|
|
22
23
|
@accounts_interface = @api_client.accounts
|
|
23
24
|
@roles_interface = @api_client.roles
|
|
25
|
+
@whoami_interface = @api_client.whoami
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def handle(args)
|
|
@@ -286,18 +288,25 @@ EOT
|
|
|
286
288
|
[
|
|
287
289
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
|
|
288
290
|
{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
|
|
289
|
-
{'fieldContext' => '
|
|
290
|
-
@
|
|
291
|
+
{'fieldContext' => 'parentAccount', 'fieldName' => 'id', 'fieldLabel' => 'Parent Tenant', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
|
292
|
+
@accounts_interface.list({max:10000})['accounts'].collect {|it|
|
|
291
293
|
{"name" => (it["authority"] || it["name"]), "value" => it["id"]}
|
|
292
294
|
}
|
|
293
295
|
}, 'displayOrder' => 3},
|
|
296
|
+
{'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
|
297
|
+
tenant_id = (api_params['parentAccount']['id'] rescue nil)
|
|
298
|
+
params = {max: 10000, roleType:'account', tenantId: tenant_id}
|
|
299
|
+
@roles_interface.list(nil, params)['roles'].collect {|it|
|
|
300
|
+
{"name" => (it["authority"] || it["name"]), "value" => it["id"]}
|
|
301
|
+
}
|
|
302
|
+
}, 'displayOrder' => 4},
|
|
294
303
|
{'fieldName' => 'currency', 'fieldLabel' => 'Currency', 'type' => 'text', 'defaultValue' => 'USD', 'displayOrder' => 4}
|
|
295
304
|
]
|
|
296
305
|
end
|
|
297
306
|
|
|
298
307
|
def update_account_option_types
|
|
299
308
|
list = add_account_option_types()
|
|
300
|
-
|
|
309
|
+
list = list.reject {|it| it['fieldContext'] == 'parentAccount' }
|
|
301
310
|
list.each {|it| it.delete('required') }
|
|
302
311
|
list.each {|it| it.delete('defaultValue') }
|
|
303
312
|
list
|
|
@@ -45,6 +45,7 @@ module Morpheus::Cli::AccountsHelper
|
|
|
45
45
|
"# Instances" => 'stats.instanceCount',
|
|
46
46
|
"# Users" => 'stats.userCount',
|
|
47
47
|
"Role" => lambda {|it| it['role']['authority'] rescue nil },
|
|
48
|
+
"Parent Tenant" => lambda {|it| it['parent']['name'] rescue nil },
|
|
48
49
|
"Master" => lambda {|it| format_boolean(it['master']) },
|
|
49
50
|
"Currency" => 'currency',
|
|
50
51
|
"Status" => lambda {|it|
|
|
@@ -117,6 +117,8 @@ module Morpheus::Cli::BackupsHelper
|
|
|
117
117
|
{
|
|
118
118
|
"ID" => 'id',
|
|
119
119
|
"Backup" => lambda {|it| it['backup']['name'] rescue '' },
|
|
120
|
+
"Type" => lambda {|it| format_backup_result_type_tag(it) },
|
|
121
|
+
"Location" => lambda {|it| format_backup_result_location_tag(it) },
|
|
120
122
|
"Status" => lambda {|it| format_backup_result_status(it) },
|
|
121
123
|
#"Duration" => lambda {|it| format_duration(it['startDate'], it['endDate']) },
|
|
122
124
|
"Duration" => lambda {|it| format_duration_milliseconds(it['durationMillis']) if it['durationMillis'].to_i > 0 },
|
|
@@ -168,5 +170,31 @@ module Morpheus::Cli::BackupsHelper
|
|
|
168
170
|
def format_backup_restore_status(backup_restore, return_color=cyan)
|
|
169
171
|
format_backup_result_status(backup_restore, return_color)
|
|
170
172
|
end
|
|
173
|
+
|
|
174
|
+
# format backup result type tag based on associated backup data
|
|
175
|
+
def format_backup_result_type_tag(backup_result)
|
|
176
|
+
backup = backup_result['backup'] || {}
|
|
177
|
+
if backup['cronExpression'] || (backup['schedule'] && backup['schedule']['id']) || (backup['job'] && backup['job']['id'])
|
|
178
|
+
"#{cyan}POLICY#{reset}"
|
|
179
|
+
else
|
|
180
|
+
"#{yellow}MANUAL#{reset}"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# format backup result location tag based on storage provider
|
|
185
|
+
def format_backup_result_location_tag(backup_result)
|
|
186
|
+
backup = backup_result['backup'] || {}
|
|
187
|
+
if backup_result['storageProvider'] && backup_result['storageProvider']['id']
|
|
188
|
+
"#{green}REMOTE#{reset}"
|
|
189
|
+
elsif backup['storageProvider'] && backup['storageProvider']['id']
|
|
190
|
+
"#{green}REMOTE#{reset}"
|
|
191
|
+
elsif backup_result['backupProvider'] && backup_result['backupProvider']['id']
|
|
192
|
+
"#{green}REMOTE#{reset}"
|
|
193
|
+
elsif backup['backupProvider'] && backup['backupProvider']['id']
|
|
194
|
+
"#{green}REMOTE#{reset}"
|
|
195
|
+
else
|
|
196
|
+
"#{blue}LOCAL#{reset}"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
171
199
|
|
|
172
|
-
end
|
|
200
|
+
end
|
data/lib/morpheus/cli/version.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.1.0
|
|
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: 2026-
|
|
14
|
+
date: 2026-03-11 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: tins
|
|
@@ -381,6 +381,8 @@ files:
|
|
|
381
381
|
- lib/morpheus/api/storage_volumes_interface.rb
|
|
382
382
|
- lib/morpheus/api/subnet_types_interface.rb
|
|
383
383
|
- lib/morpheus/api/subnets_interface.rb
|
|
384
|
+
- lib/morpheus/api/system_types_interface.rb
|
|
385
|
+
- lib/morpheus/api/systems_interface.rb
|
|
384
386
|
- lib/morpheus/api/task_sets_interface.rb
|
|
385
387
|
- lib/morpheus/api/tasks_interface.rb
|
|
386
388
|
- lib/morpheus/api/usage_interface.rb
|
|
@@ -570,6 +572,7 @@ files:
|
|
|
570
572
|
- lib/morpheus/cli/commands/storage_volume_types.rb
|
|
571
573
|
- lib/morpheus/cli/commands/storage_volumes.rb
|
|
572
574
|
- lib/morpheus/cli/commands/subnets_command.rb
|
|
575
|
+
- lib/morpheus/cli/commands/systems.rb
|
|
573
576
|
- lib/morpheus/cli/commands/tasks.rb
|
|
574
577
|
- lib/morpheus/cli/commands/tee_command.rb
|
|
575
578
|
- lib/morpheus/cli/commands/tenants_command.rb
|