morpheus-cli 6.3.3 → 7.0.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/lib/morpheus/api/api_client.rb +8 -0
- data/lib/morpheus/api/backup_restores_interface.rb +4 -0
- data/lib/morpheus/api/backup_types_interface.rb +9 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +1 -2
- data/lib/morpheus/api/hub_interface.rb +25 -0
- data/lib/morpheus/cli/cli_command.rb +3 -0
- data/lib/morpheus/cli/commands/backup_types_command.rb +53 -0
- data/lib/morpheus/cli/commands/backups_command.rb +100 -21
- data/lib/morpheus/cli/commands/catalog_item_types_command.rb +3 -31
- data/lib/morpheus/cli/commands/clouds.rb +22 -4
- data/lib/morpheus/cli/commands/curl_command.rb +1 -1
- data/lib/morpheus/cli/commands/groups.rb +50 -7
- data/lib/morpheus/cli/commands/hub.rb +215 -0
- data/lib/morpheus/cli/commands/jobs_command.rb +10 -6
- data/lib/morpheus/cli/commands/library_forms_command.rb +1 -1
- data/lib/morpheus/cli/commands/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/commands/library_option_types_command.rb +1 -1
- data/lib/morpheus/cli/commands/license.rb +151 -86
- data/lib/morpheus/cli/commands/networks_command.rb +1 -1
- data/lib/morpheus/cli/commands/security_packages.rb +1 -1
- data/lib/morpheus/cli/commands/setup.rb +1 -1
- data/lib/morpheus/cli/commands/tasks.rb +2 -2
- data/lib/morpheus/cli/commands/user_settings_command.rb +10 -1
- data/lib/morpheus/cli/mixins/backups_helper.rb +4 -3
- data/lib/morpheus/cli/mixins/jobs_helper.rb +3 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +80 -2
- data/lib/morpheus/cli/option_types.rb +9 -2
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/routes.rb +2 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 763ccc16e1c853b5d76e8466a21fd94abbd098615a884ff252f72015d5031338
|
4
|
+
data.tar.gz: c8ea0dcb3534ca6c3c557ce58763dfcef22ed59e7bdd33bde6eff00ee8513c1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a68ef51fb607ce70ba7c93bdeb5ab637c741ce03ad24e987a63a2ba0db2d2ffac674bae2ff4aab02c543c76df93da178fe5a1d8e709df99444f2b4ba8e506945
|
7
|
+
data.tar.gz: 90b6702b8ebc4b75a0aa29da1802155dab2aca529c2d9538adb363f6c663b4241f632c7b22cc17e9e5b949598994e152107072ff19d7429bf944c69fcc4ad35e
|
data/Dockerfile
CHANGED
@@ -846,6 +846,10 @@ class Morpheus::APIClient
|
|
846
846
|
Morpheus::HealthInterface.new(common_interface_options).setopts(@options)
|
847
847
|
end
|
848
848
|
|
849
|
+
def hub
|
850
|
+
Morpheus::HubInterface.new(common_interface_options).setopts(@options)
|
851
|
+
end
|
852
|
+
|
849
853
|
def audit
|
850
854
|
Morpheus::AuditInterface.new(common_interface_options).setopts(@options)
|
851
855
|
end
|
@@ -894,6 +898,10 @@ class Morpheus::APIClient
|
|
894
898
|
Morpheus::BackupServiceTypesInterface.new(common_interface_options).setopts(@options)
|
895
899
|
end
|
896
900
|
|
901
|
+
def backup_types
|
902
|
+
Morpheus::BackupTypesInterface.new(common_interface_options).setopts(@options)
|
903
|
+
end
|
904
|
+
|
897
905
|
def catalog_item_types
|
898
906
|
Morpheus::CatalogItemTypesInterface.new(common_interface_options).setopts(@options)
|
899
907
|
end
|
@@ -15,6 +15,10 @@ class Morpheus::BackupRestoresInterface < Morpheus::APIClient
|
|
15
15
|
execute(method: :get, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
|
16
16
|
end
|
17
17
|
|
18
|
+
def create(payload, params={}, headers={})
|
19
|
+
execute(method: :post, url: "#{base_path}", params: params, payload: payload, headers: headers)
|
20
|
+
end
|
21
|
+
|
18
22
|
def destroy(id, params = {}, headers={})
|
19
23
|
validate_id!(id)
|
20
24
|
execute(method: :delete, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
|
@@ -8,8 +8,7 @@ class Morpheus::CatalogItemTypesInterface < Morpheus::RestInterface
|
|
8
8
|
|
9
9
|
# NOT json, multipart file upload, uses PUT update endpoint
|
10
10
|
def update_logo(id, logo_file, dark_logo_file=nil)
|
11
|
-
|
12
|
-
url = "#{base_path}/#{id}"
|
11
|
+
url = "#{base_path}/#{id}/update-logo"
|
13
12
|
headers = { :params => {}, :authorization => "Bearer #{@access_token}"}
|
14
13
|
payload = {}
|
15
14
|
payload["catalogItemType"] = {}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
|
3
|
+
class Morpheus::HubInterface < Morpheus::APIClient
|
4
|
+
|
5
|
+
def base_path
|
6
|
+
"/api/hub"
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(params={}, headers={})
|
10
|
+
execute(method: :get, url: "#{base_path}", params: params, headers: headers)
|
11
|
+
end
|
12
|
+
|
13
|
+
def usage(params={}, headers={})
|
14
|
+
execute(method: :get, url: "#{base_path}/usage", params: params, headers: headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
def checkin(payload={}, params={}, headers={})
|
18
|
+
execute(method: :post, url: "#{base_path}/checkin", payload: payload, params: params, headers: headers)
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(payload={}, params={}, headers={})
|
22
|
+
execute(method: :post, url: "#{base_path}/register", payload: payload, params: params, headers: headers)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -200,6 +200,9 @@ module Morpheus
|
|
200
200
|
# value_label = 'SELECT'
|
201
201
|
# elsif option['type'] == 'select'
|
202
202
|
end
|
203
|
+
if option_type['optionalValue']
|
204
|
+
value_label = "[#{value_label}]"
|
205
|
+
end
|
203
206
|
full_option = "--#{full_field_name} #{value_label}"
|
204
207
|
# switch is an alias for the full option name, fieldName is the default
|
205
208
|
if option_type['switch']
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
class Morpheus::Cli::BackupTypes
|
4
|
+
include Morpheus::Cli::CliCommand
|
5
|
+
include Morpheus::Cli::RestCommand
|
6
|
+
|
7
|
+
set_command_description "View backup types."
|
8
|
+
set_command_name :'backup-types'
|
9
|
+
register_subcommands :list, :get
|
10
|
+
register_interfaces :backup_types
|
11
|
+
|
12
|
+
# This is a hidden command, could move to backup list-types and backup get-type
|
13
|
+
set_command_hidden
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def backup_type_list_column_definitions(options)
|
18
|
+
{
|
19
|
+
"ID" => 'id',
|
20
|
+
"Name" => 'name',
|
21
|
+
"Code" => 'code',
|
22
|
+
# "Active" => lambda {|it| format_boolean it['active'] },
|
23
|
+
# "Provider Code" => 'providerCode',
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def backup_type_column_definitions(options)
|
28
|
+
{
|
29
|
+
"ID" => 'id',
|
30
|
+
"Name" => 'name',
|
31
|
+
"Code" => 'code',
|
32
|
+
#"Backup Format" => 'backupFormat',
|
33
|
+
"Active" => lambda {|it| format_boolean it['active'] },
|
34
|
+
# "Container Type" => 'containerType',
|
35
|
+
# "Container Format" => 'containerFormat',
|
36
|
+
# "Container Category" => 'containerCategory',
|
37
|
+
"Restore Type" => 'restoreType',
|
38
|
+
# "Has Stream To Store" => lambda {|it| format_boolean it['hasStreamToStore'] },
|
39
|
+
# "Has Copy To Store" => lambda {|it| format_boolean it['hasCopyToStore'] },
|
40
|
+
"Download" => lambda {|it| format_boolean it['downloadEnabled'] },
|
41
|
+
# "Download From Store Only" => lambda {|it| format_boolean it['downloadFromStoreOnly'] },
|
42
|
+
# "Copy To Store" => lambda {|it| format_boolean it['copyToStore'] },
|
43
|
+
"Restore Existing" => lambda {|it| format_boolean it['restoreExistingEnabled'] },
|
44
|
+
"Restore New" => lambda {|it| format_boolean it['restoreNewEnabled'] },
|
45
|
+
# "Restore New Mode" => 'restoreNewMode',
|
46
|
+
# "Prune Results On Restore Existing" => lambda {|it| format_boolean it['pruneResultsOnRestoreExisting'] },
|
47
|
+
# "Restrict Targets" => lambda {|it| format_boolean it['restrictTargets'] },
|
48
|
+
# "Provider Code" => 'providerCode',
|
49
|
+
"Plugin" => lambda {|it| format_boolean it['isPlugin'] },
|
50
|
+
"Embedded" => lambda {|it| format_boolean it['isEmbedded'] },
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
@@ -9,7 +9,7 @@ class Morpheus::Cli::BackupsCommand
|
|
9
9
|
|
10
10
|
set_command_description "View and manage backups"
|
11
11
|
set_command_name :'backups'
|
12
|
-
register_subcommands :list, :get, :add, :update, :remove, :execute
|
12
|
+
register_subcommands :list, :get, :add, :update, :remove, :execute, :restore
|
13
13
|
register_subcommands :list_jobs, :get_job, :add_job, :update_job, :remove_job, :execute_job
|
14
14
|
register_subcommands :list_results, :get_result, :remove_result
|
15
15
|
register_subcommands :list_restores, :get_restore, :remove_restore
|
@@ -45,7 +45,7 @@ class Morpheus::Cli::BackupsCommand
|
|
45
45
|
end
|
46
46
|
params.merge!(parse_list_options(options))
|
47
47
|
parse_options(options, params)
|
48
|
-
execute_api(@backups_interface, :list, [], options, '
|
48
|
+
execute_api(@backups_interface, :list, [], options, 'backups') do |json_response|
|
49
49
|
backups = json_response['backups']
|
50
50
|
print_h1 "Morpheus Backups", parse_list_subtitles(options), options
|
51
51
|
if backups.empty?
|
@@ -315,19 +315,18 @@ EOT
|
|
315
315
|
end
|
316
316
|
|
317
317
|
def restore(args)
|
318
|
-
raise "Not Yet Implemented"
|
319
318
|
options = {}
|
320
319
|
params = {}
|
321
320
|
payload = {}
|
322
321
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
323
322
|
opts.banner = subcommand_usage("[backup] [result] [options]")
|
324
|
-
build_standard_post_options(opts, options)
|
323
|
+
build_standard_post_options(opts, options, [:auto_confirm])
|
325
324
|
opts.on('--result ID', String, "Backup Result ID that is being restored") do |val|
|
326
325
|
options[:options]['backupResultId'] = val
|
327
326
|
end
|
328
327
|
opts.on('--restore-instance existing|new', String, "Instance being targeted for the restore, existing to restore the current instance or new to create a new instance. The current instance is targeted by default.") do |val|
|
329
|
-
#
|
330
|
-
options[:options]['
|
328
|
+
# restoreInstance=existing|new and the flag on the restore object is called 'restoreToNew'
|
329
|
+
options[:options]['restoreInstance'] = val
|
331
330
|
end
|
332
331
|
opts.footer = <<-EOT
|
333
332
|
Restore a backup, replacing the existing target with the specified backup result.
|
@@ -350,32 +349,71 @@ EOT
|
|
350
349
|
available_backups = @backups_interface.list({max:10000})['backups'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
|
351
350
|
backup_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'backupId', 'fieldLabel' => 'Backup', 'type' => 'select', 'selectOptions' => available_backups, 'required' => true}], options[:options], @api_client)['backupId']
|
352
351
|
backup = find_backup_by_name_or_id(backup_id)
|
353
|
-
|
352
|
+
return 1 if backup.nil?
|
354
353
|
end
|
355
354
|
end
|
356
355
|
# Prompt for backup result
|
357
356
|
if backup_result.nil?
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
# Name
|
363
|
-
params['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Backup Name'}], options[:options], @api_client)['name']
|
357
|
+
#available_backup_results = @backup_results_interface.list({backupId: backup['id'], status: ['success', 'succeeded'], max:10000})['results'].collect {|it| {format_backup_result_option_name(it), 'value' => it['id']}}
|
358
|
+
available_backup_results = @backup_results_interface.list({backupId: backup['id'], max:10000})['results'].select {|it| it['status'].to_s.downcase == 'succeeded' || it['status'].to_s.downcase == 'success' }.collect {|it| {'name' => format_backup_result_option_name(it), 'value' => it['id']} }
|
359
|
+
params['backupResultId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'backupResultId', 'fieldLabel' => 'Backup Result', 'type' => 'select', 'selectOptions' => available_backup_results, 'required' => true}], options[:options], @api_client)['backupResultId']
|
360
|
+
backup_result = @backup_results_interface.get(params['backupResultId'].to_i)['result']
|
364
361
|
end
|
365
|
-
|
366
362
|
parse_payload(options, 'restore') do |payload|
|
367
363
|
# Prompt for restore configuration
|
368
|
-
#
|
364
|
+
# todo: These options should be based on backup type
|
365
|
+
# Look at backup_type['restoreExistingEnabled'] and backup_type['restoreNewEnabled']
|
369
366
|
# Target Instance
|
370
|
-
if backup_result['
|
371
|
-
|
367
|
+
#if backup_result['instanceId']
|
368
|
+
if backup['locationType'] == 'instance'
|
369
|
+
instance = backup['instance']
|
370
|
+
# could actually fetch the instance.., only need name and id right now though.
|
371
|
+
raise_command_error "Backup instance not found" if instance.nil?
|
372
|
+
params['restoreInstance'] = prompt_value({'fieldName' => 'restoreInstance', 'fieldLabel' => 'Restore Instance', 'type' => 'select', 'selectOptions' => [{'name' => 'Current Instance', 'value' => 'existing'}, {'name' => 'New Instance', 'value' => 'new'}], 'defaultValue' => 'existing', 'required' => true, 'description' => 'Restore the current instance or a new instance?'}, options)
|
373
|
+
if params['restoreInstance'] == 'new'
|
374
|
+
# new instance
|
375
|
+
config_map = prompt_restore_instance_config(options)
|
376
|
+
params['instanceConfig'] = config_map
|
377
|
+
else
|
378
|
+
# existing instance
|
379
|
+
# confirm the instance
|
380
|
+
keep_prompting = !options[:no_prompt]
|
381
|
+
while keep_prompting
|
382
|
+
instance_id = prompt_value({'fieldName' => 'instanceId', 'fieldLabel' => 'Confirm Instance ID', 'type' => 'text', 'required' => true, 'description' => "Enter the current instance ID to confirm that you wish to restore it."}, options)
|
383
|
+
if instance_id && instance_id.to_i == instance['id']
|
384
|
+
params['instanceId'] = instance_id.to_i
|
385
|
+
keep_prompting = false
|
386
|
+
elsif instance_id.to_s.downcase == instance['name'].to_s.downcase # allow matching on name too
|
387
|
+
params['instanceId'] = instance['id']
|
388
|
+
keep_prompting = false
|
389
|
+
else
|
390
|
+
print_red_alert "The value '#{instance_id}' does not match the existing instance #{instance['name']} [#{instance['id'] rescue ''}]. Please try again."
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
elsif backup['locationType'] == 'server'
|
395
|
+
# prompt for server type backup restore
|
396
|
+
elsif backup['locationType'] == 'storage'
|
397
|
+
# prompt for storage type backup restore
|
398
|
+
else
|
399
|
+
print yellow, "Backup location type is unknown: #{backup['locationType']}",reset,"\n"
|
372
400
|
end
|
373
|
-
|
401
|
+
|
402
|
+
payload['restore'].deep_merge!(params)
|
374
403
|
end
|
375
404
|
|
376
|
-
|
377
|
-
|
378
|
-
|
405
|
+
if params['restoreInstance'] != 'new'
|
406
|
+
if backup['instance']
|
407
|
+
print cyan,"You have selected to restore the existing instance #{backup['instance']['name'] rescue ''} [#{backup['instance']['id'] rescue ''}] with the backup result #{format_backup_result_option_name(backup_result)} [#{backup_result['id']}]",reset,"\n"
|
408
|
+
end
|
409
|
+
if backup['sourceProviderId']
|
410
|
+
print yellow,"#{bold}WARNING!#{reset}#{yellow} Restoring a backup will overwite objects when restored to an existing object store.",reset,"\n"
|
411
|
+
else
|
412
|
+
print yellow,"#{bold}WARNING!#{reset}#{yellow} Restoring a backup will erase all data when restored to an existing instance.",reset,"\n"
|
413
|
+
end
|
414
|
+
end
|
415
|
+
confirm!("Are you sure you want to restore the backup result?", options)
|
416
|
+
execute_api(@backup_restores_interface, :create, [], options, 'restore') do |json_response|
|
379
417
|
print_green_success "Restoring backup result ID: #{backup_result['id']} Name: #{backup_result['backup']['name'] rescue ''} Date: (#{format_local_dt(backup_result['dateCreated'])}"
|
380
418
|
# should get the restore maybe, or could even support refreshing until it is complete...
|
381
419
|
# restore = json_response["restore"]
|
@@ -506,4 +544,45 @@ EOT
|
|
506
544
|
]
|
507
545
|
end
|
508
546
|
|
547
|
+
def format_backup_result_option_name(result)
|
548
|
+
"#{result['backup']['name']} (#{format_local_dt(result['startDate'])})"
|
549
|
+
end
|
550
|
+
|
551
|
+
# prompt for an instance config (vdiPool.instanceConfig)
|
552
|
+
def prompt_restore_instance_config(options)
|
553
|
+
# use config if user passed one in..
|
554
|
+
scope_context = 'instanceConfig'
|
555
|
+
scoped_instance_config = {}
|
556
|
+
if options[:options][scope_context].is_a?(Hash)
|
557
|
+
scoped_instance_config = options[:options][scope_context]
|
558
|
+
end
|
559
|
+
|
560
|
+
# now configure an instance like normal, use the config as default options with :always_prompt
|
561
|
+
instance_prompt_options = {}
|
562
|
+
# instance_prompt_options[:group] = group ? group['id'] : nil
|
563
|
+
# #instance_prompt_options[:cloud] = cloud ? cloud['name'] : nil
|
564
|
+
# instance_prompt_options[:default_cloud] = cloud ? cloud['name'] : nil
|
565
|
+
# instance_prompt_options[:environment] = selected_environment ? selected_environment['code'] : nil
|
566
|
+
# instance_prompt_options[:default_security_groups] = scoped_instance_config['securityGroups'] ? scoped_instance_config['securityGroups'] : nil
|
567
|
+
|
568
|
+
instance_prompt_options[:no_prompt] = options[:no_prompt]
|
569
|
+
#instance_prompt_options[:always_prompt] = options[:no_prompt] != true # options[:always_prompt]
|
570
|
+
instance_prompt_options[:options] = scoped_instance_config
|
571
|
+
#instance_prompt_options[:options][:always_prompt] = instance_prompt_options[:no_prompt] != true
|
572
|
+
instance_prompt_options[:options][:no_prompt] = instance_prompt_options[:no_prompt]
|
573
|
+
|
574
|
+
#instance_prompt_options[:name_required] = true
|
575
|
+
# instance_prompt_options[:instance_type_code] = instance_type_code
|
576
|
+
# todo: an effort to render more useful help eg. -O Web.0.instance.name
|
577
|
+
help_field_prefix = scope_context
|
578
|
+
instance_prompt_options[:help_field_prefix] = help_field_prefix
|
579
|
+
instance_prompt_options[:options][:help_field_prefix] = help_field_prefix
|
580
|
+
# instance_prompt_options[:locked_fields] = scoped_instance_config['lockedFields']
|
581
|
+
# instance_prompt_options[:for_app] = true
|
582
|
+
instance_prompt_options[:select_datastore] = true
|
583
|
+
instance_prompt_options[:name_required] = true
|
584
|
+
# this provisioning helper method handles all (most) of the parsing and prompting
|
585
|
+
instance_config_payload = prompt_new_instance(instance_prompt_options)
|
586
|
+
return instance_config_payload
|
587
|
+
end
|
509
588
|
end
|
@@ -71,9 +71,6 @@ class Morpheus::Cli::CatalogItemTypesCommand
|
|
71
71
|
print cyan,"No catalog item types found.",reset,"\n"
|
72
72
|
else
|
73
73
|
list_columns = catalog_item_type_list_column_definitions.upcase_keys!
|
74
|
-
list_columns.delete("Blueprint")
|
75
|
-
list_columns.delete("Workflow")
|
76
|
-
list_columns.delete("Context")
|
77
74
|
#list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
|
78
75
|
print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
|
79
76
|
print_results_pagination(json_response)
|
@@ -456,32 +453,6 @@ EOT
|
|
456
453
|
opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
|
457
454
|
options[:options]['labels'] = parse_labels(val)
|
458
455
|
end
|
459
|
-
opts.on('--logo FILE', String, "Upload a custom logo icon") do |val|
|
460
|
-
filename = val
|
461
|
-
logo_file = nil
|
462
|
-
if filename == 'null'
|
463
|
-
logo_file = 'null' # clear it
|
464
|
-
else
|
465
|
-
filename = File.expand_path(filename)
|
466
|
-
if !File.exist?(filename)
|
467
|
-
raise_command_error "File not found: #{filename}"
|
468
|
-
end
|
469
|
-
logo_file = File.new(filename, 'rb')
|
470
|
-
end
|
471
|
-
end
|
472
|
-
opts.on('--dark-logo FILE', String, "Upload a custom dark logo icon") do |val|
|
473
|
-
filename = val
|
474
|
-
dark_logo_file = nil
|
475
|
-
if filename == 'null'
|
476
|
-
dark_logo_file = 'null' # clear it
|
477
|
-
else
|
478
|
-
filename = File.expand_path(filename)
|
479
|
-
if !File.exist?(filename)
|
480
|
-
raise_command_error "File not found: #{filename}"
|
481
|
-
end
|
482
|
-
dark_logo_file = File.new(filename, 'rb')
|
483
|
-
end
|
484
|
-
end
|
485
456
|
opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
|
486
457
|
options[:config_file] = val.to_s
|
487
458
|
file_content = nil
|
@@ -727,12 +698,13 @@ EOT
|
|
727
698
|
"Description" => 'description',
|
728
699
|
"Type" => lambda {|it| format_catalog_type(it) },
|
729
700
|
"Visibility" => 'visibility',
|
730
|
-
"Layout Code" => 'layoutCode',
|
701
|
+
#"Layout Code" => 'layoutCode',
|
731
702
|
"Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
|
732
703
|
"Workflow" => lambda {|it| it['workflow'] ? it['workflow']['name'] : nil },
|
733
|
-
"Context" => lambda {|it| it['context'] },
|
704
|
+
# "Context" => lambda {|it| it['context'] },
|
734
705
|
# "Content" => lambda {|it| it['content'] },
|
735
706
|
"Form Type" => lambda {|it| it['formType'] == 'form' ? "Form" : "Inputs" },
|
707
|
+
"Form" => lambda {|it| it['form'] ? it['form']['name'] : nil },
|
736
708
|
"Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
737
709
|
"Featured" => lambda {|it| format_boolean(it['featured']) },
|
738
710
|
#"Config" => lambda {|it| it['config'] },
|
@@ -41,6 +41,12 @@ class Morpheus::Cli::Clouds
|
|
41
41
|
opts.on( '-t', '--type TYPE', "Cloud Type" ) do |val|
|
42
42
|
options[:zone_type] = val
|
43
43
|
end
|
44
|
+
opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
|
45
|
+
add_query_parameter(params, 'labels', parse_labels(val))
|
46
|
+
end
|
47
|
+
opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
|
48
|
+
add_query_parameter(params, 'allLabels', parse_labels(val))
|
49
|
+
end
|
44
50
|
build_standard_list_options(opts, options)
|
45
51
|
opts.footer = "List clouds."
|
46
52
|
end
|
@@ -179,6 +185,7 @@ class Morpheus::Cli::Clouds
|
|
179
185
|
"Type" => lambda {|it| it['zoneType'] ? it['zoneType']['name'] : '' },
|
180
186
|
"Code" => 'code',
|
181
187
|
"Location" => 'location',
|
188
|
+
"Labels" => lambda {|it| format_list(it['labels'], '') rescue '' },
|
182
189
|
"Region Code" => 'regionCode',
|
183
190
|
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
184
191
|
"Groups" => lambda {|it| it['groups'].collect {|g| g.instance_of?(Hash) ? g['name'] : g.to_s }.join(', ') },
|
@@ -229,6 +236,9 @@ class Morpheus::Cli::Clouds
|
|
229
236
|
opts.on('--credential VALUE', String, "Credential ID or \"local\"" ) do |val|
|
230
237
|
options[:options]['credential'] = val
|
231
238
|
end
|
239
|
+
opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
|
240
|
+
options[:options]['labels'] = parse_labels(val)
|
241
|
+
end
|
232
242
|
|
233
243
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
234
244
|
end
|
@@ -342,6 +352,9 @@ class Morpheus::Cli::Clouds
|
|
342
352
|
# opts.on( '-d', '--description DESCRIPTION', "Description (optional)" ) do |desc|
|
343
353
|
# params[:description] = desc
|
344
354
|
# end
|
355
|
+
opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
|
356
|
+
options[:options]['labels'] = parse_labels(val)
|
357
|
+
end
|
345
358
|
opts.on('--costing-mode VALUE', String, "Costing Mode can be off, costing, or full. Default is off." ) do |val|
|
346
359
|
options[:options]['costingMode'] = val
|
347
360
|
end
|
@@ -400,6 +413,9 @@ class Morpheus::Cli::Clouds
|
|
400
413
|
if params['zone'].is_a?(Hash)
|
401
414
|
cloud_payload.merge!(params.delete('zone'))
|
402
415
|
end
|
416
|
+
if params.key?('labels')
|
417
|
+
params['labels'] = parse_labels(params['labels'])
|
418
|
+
end
|
403
419
|
cloud_payload.merge!(params)
|
404
420
|
payload = {zone: cloud_payload}
|
405
421
|
end
|
@@ -1141,6 +1157,7 @@ EOT
|
|
1141
1157
|
"ID" => 'id',
|
1142
1158
|
"Name" => 'name',
|
1143
1159
|
"Type" => lambda {|it| it['zoneType'] ? it['zoneType']['name'] : '' },
|
1160
|
+
"Labels" => lambda {|it| format_list(it['labels'], '', 3) rescue '' },
|
1144
1161
|
"Location" => 'location',
|
1145
1162
|
"Region Code" => lambda {|it| it['regionCode'] },
|
1146
1163
|
"Groups" => lambda {|it| (it['groups'] || []).collect {|g| g.instance_of?(Hash) ? g['name'] : g.to_s }.join(', ') },
|
@@ -1155,10 +1172,11 @@ EOT
|
|
1155
1172
|
#{'fieldName' => 'zoneType.code', 'fieldLabel' => 'Image Type', 'type' => 'select', 'selectOptions' => cloud_types_for_dropdown, 'required' => true, 'description' => 'Cloud Type.', 'displayOrder' => 0},
|
1156
1173
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
|
1157
1174
|
{'fieldName' => 'code', 'fieldLabel' => 'Code', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
|
1158
|
-
{'fieldName' => '
|
1159
|
-
{'fieldName' => '
|
1160
|
-
{'fieldName' => '
|
1161
|
-
{'fieldName' => '
|
1175
|
+
{'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
|
1176
|
+
{'fieldName' => 'location', 'fieldLabel' => 'Location', 'type' => 'text', 'required' => false, 'displayOrder' => 4},
|
1177
|
+
{'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}], 'required' => false, 'description' => 'Visibility', 'category' => 'permissions', 'defaultValue' => 'private', 'displayOrder' => 5},
|
1178
|
+
{'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'required' => false, 'defaultValue' => true, 'displayOrder' => 6},
|
1179
|
+
{'fieldName' => 'autoRecoverPowerState', 'fieldLabel' => 'Automatically Power On VMs', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'displayOrder' => 7}
|
1162
1180
|
]
|
1163
1181
|
|
1164
1182
|
# TODO: Account
|
@@ -33,7 +33,7 @@ class Morpheus::Cli::CurlCommand
|
|
33
33
|
raise ::OptionParser::InvalidOption.new("Failed to parse payload as JSON. Error: #{ex.message}")
|
34
34
|
end
|
35
35
|
end
|
36
|
-
opts.on('--absolute', "Absolute path,
|
36
|
+
opts.on('--absolute', "Absolute path, skip the addition of path prefix '/api/'") do
|
37
37
|
options[:absolute_path] = true
|
38
38
|
end
|
39
39
|
opts.on('--inspect', "Inspect response, prints headers. By default only the body is printed.") do
|
@@ -31,6 +31,12 @@ class Morpheus::Cli::Groups
|
|
31
31
|
params = {}
|
32
32
|
optparse = Morpheus::Cli::OptionParser.new do|opts|
|
33
33
|
opts.banner = subcommand_usage()
|
34
|
+
opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
|
35
|
+
add_query_parameter(params, 'labels', parse_labels(val))
|
36
|
+
end
|
37
|
+
opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
|
38
|
+
add_query_parameter(params, 'allLabels', parse_labels(val))
|
39
|
+
end
|
34
40
|
build_standard_list_options(opts, options)
|
35
41
|
opts.footer = "List groups."
|
36
42
|
end
|
@@ -100,6 +106,14 @@ EOT
|
|
100
106
|
return 0 if render_result
|
101
107
|
|
102
108
|
group = json_response['group']
|
109
|
+
group_stats = group['stats']
|
110
|
+
# serverCounts moved to zone.stats.serverCounts
|
111
|
+
server_counts = nil
|
112
|
+
instance_counts = nil
|
113
|
+
if group_stats
|
114
|
+
instance_counts = group_stats['instanceCounts']
|
115
|
+
server_counts = group_stats['serverCounts']
|
116
|
+
end
|
103
117
|
is_active = @active_group_id && (@active_group_id == group['id'])
|
104
118
|
print_h1 "Group Details"
|
105
119
|
print cyan
|
@@ -108,10 +122,28 @@ EOT
|
|
108
122
|
"Name" => 'name',
|
109
123
|
"Code" => 'code',
|
110
124
|
"Location" => 'location',
|
125
|
+
"Labels" => lambda {|it| format_list(it['labels'], '') rescue '' },
|
111
126
|
"Clouds" => lambda {|it| it['zones'].collect {|z| z['name'] }.join(', ') },
|
112
|
-
"
|
127
|
+
#"Instances" => lambda {|it| it['stats']['instanceCounts']['all'] rescue '' },
|
128
|
+
# "Hosts" => lambda {|it| it['stats']['serverCounts']['host'] rescue it['serverCount'] },
|
129
|
+
# "VMs" => lambda {|it| it['stats']['serverCounts']['vm'] rescue '' },
|
130
|
+
# "Bare Metal" => lambda {|it| it['stats']['serverCounts']['baremetal'] rescue '' },
|
113
131
|
}
|
114
132
|
print_description_list(description_cols, group)
|
133
|
+
|
134
|
+
if server_counts
|
135
|
+
print_h2 "Group Stats"
|
136
|
+
print cyan
|
137
|
+
print "Clouds: #{group['zones'].size}".center(20)
|
138
|
+
print "Instances: #{instance_counts['all']}".center(20) if instance_counts
|
139
|
+
print "Hosts: #{server_counts['host']}".center(20)
|
140
|
+
#print "Container Hosts: #{server_counts['containerHost']}".center(20)
|
141
|
+
#print "Hypervisors: #{server_counts['hypervisor']}".center(20)
|
142
|
+
print "Virtual Machines: #{server_counts['vm']}".center(20)
|
143
|
+
print "Bare Metal: #{server_counts['baremetal']}".center(20)
|
144
|
+
#print "Unmanaged: #{server_counts['unmanaged']}".center(20)
|
145
|
+
print "\n"
|
146
|
+
end
|
115
147
|
# puts "ID: #{group['id']}"
|
116
148
|
# puts "Name: #{group['name']}"
|
117
149
|
# puts "Code: #{group['code']}"
|
@@ -211,10 +243,12 @@ EOT
|
|
211
243
|
params = options[:options] || {}
|
212
244
|
|
213
245
|
if params.empty?
|
214
|
-
|
215
|
-
exit 1
|
246
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
216
247
|
end
|
217
248
|
|
249
|
+
if params.key?('labels')
|
250
|
+
params['labels'] = parse_labels(params['labels'])
|
251
|
+
end
|
218
252
|
group_payload.merge!(params)
|
219
253
|
|
220
254
|
payload = {group: group_payload}
|
@@ -638,18 +672,26 @@ EOT
|
|
638
672
|
{
|
639
673
|
id: active_group ? (((@active_group_id && (@active_group_id == group['id'])) ? "=> " : " ") + group['id'].to_s) : group['id'],
|
640
674
|
name: group['name'],
|
675
|
+
labels: group['labels'],
|
641
676
|
location: group['location'],
|
642
677
|
cloud_count: group['zones'] ? group['zones'].size : 0,
|
643
|
-
|
678
|
+
instance_count: (group['stats']['instanceCounts']['all'] rescue ''),
|
679
|
+
host_count: (group['stats']['serverCounts']['host'] rescue group['serverCount']),
|
680
|
+
vm_count: (group['stats']['serverCounts']['vm'] rescue ''),
|
681
|
+
baremetal_count: (group['stats']['serverCounts']['baremetal'] rescue '')
|
644
682
|
}
|
645
683
|
end
|
646
684
|
columns = [
|
647
685
|
#{:active => {:display_name => ""}},
|
648
686
|
{:id => {:display_name => (active_group ? " ID" : "ID")}},
|
649
|
-
{:name => {:width =>
|
687
|
+
{:name => {:width => 64}},
|
650
688
|
{:location => {:width => 32}},
|
689
|
+
{:labels => {:display_method => lambda {|it| format_list(it[:labels], '', 3) rescue '' }}},
|
651
690
|
{:cloud_count => {:display_name => "CLOUDS"}},
|
652
|
-
{:
|
691
|
+
{:instance_count => {:display_name => "INSTANCES"}},
|
692
|
+
{:host_count => {:display_name => "HOSTS"}},
|
693
|
+
{:vm_count => {:display_name => "VMS"}},
|
694
|
+
{:baremetal_count => {:display_name => "BARE METAL"}},
|
653
695
|
]
|
654
696
|
print as_pretty_table(rows, columns, opts)
|
655
697
|
end
|
@@ -658,7 +700,8 @@ EOT
|
|
658
700
|
tmp_option_types = [
|
659
701
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
|
660
702
|
{'fieldName' => 'code', 'fieldLabel' => 'Code', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
|
661
|
-
{'fieldName' => '
|
703
|
+
{'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
|
704
|
+
{'fieldName' => 'location', 'fieldLabel' => 'Location', 'type' => 'text', 'required' => false, 'displayOrder' => 4},
|
662
705
|
]
|
663
706
|
|
664
707
|
# Advanced Options
|