morpheus-cli 6.3.3 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|