morpheus-cli 9.0.1 → 9.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/lib/morpheus/api/affinity_groups_interface.rb +46 -0
- data/lib/morpheus/api/api_client.rb +17 -0
- data/lib/morpheus/api/clouds_interface.rb +42 -0
- data/lib/morpheus/api/clusters_interface.rb +55 -1
- data/lib/morpheus/api/distributed_workers_interface.rb +41 -0
- data/lib/morpheus/api/host_vm_groups_interface.rb +37 -0
- data/lib/morpheus/api/servers_interface.rb +8 -0
- data/lib/morpheus/api/virtual_switches_interface.rb +9 -0
- data/lib/morpheus/cli/commands/affinity_groups_command.rb +554 -0
- data/lib/morpheus/cli/commands/backups_command.rb +11 -0
- data/lib/morpheus/cli/commands/clouds.rb +421 -29
- data/lib/morpheus/cli/commands/clusters.rb +507 -20
- data/lib/morpheus/cli/commands/distributed_workers_command.rb +282 -0
- data/lib/morpheus/cli/commands/host_vm_groups_command.rb +423 -0
- data/lib/morpheus/cli/commands/hosts.rb +72 -1
- data/lib/morpheus/cli/commands/instances.rb +12 -1
- data/lib/morpheus/cli/commands/networks_command.rb +35 -3
- data/lib/morpheus/cli/commands/virtual_switches_command.rb +336 -0
- data/lib/morpheus/cli/mixins/affinity_helper.rb +118 -0
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +143 -4
- data/lib/morpheus/cli/option_types.rb +36 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/affinity_groups_interface_test.rb +43 -0
- data/test/api/clouds_interface_test.rb +67 -0
- data/test/api/host_vm_groups_interface_test.rb +35 -0
- data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
- data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
- data/test/cli/affinity_groups_test.rb +57 -0
- data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
- data/test/cli/affinity_helper_test.rb +367 -0
- data/test/cli/backups_test.rb +212 -0
- data/test/cli/clusters_uuid_test.rb +131 -0
- data/test/cli/distributed_workers_test.rb +68 -0
- data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
- data/test/cli/host_vm_groups_test.rb +63 -0
- data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
- metadata +39 -2
|
@@ -7,6 +7,7 @@ class Morpheus::Cli::Clusters
|
|
|
7
7
|
include Morpheus::Cli::WhoamiHelper
|
|
8
8
|
include Morpheus::Cli::AccountsHelper
|
|
9
9
|
include Morpheus::Cli::ExecutionRequestHelper
|
|
10
|
+
include Morpheus::Cli::AffinityHelper
|
|
10
11
|
|
|
11
12
|
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :logs, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}
|
|
12
13
|
register_subcommands :list_types, :get_type
|
|
@@ -31,6 +32,8 @@ class Morpheus::Cli::Clusters
|
|
|
31
32
|
register_subcommands :list_replicasets, :list_daemonsets, :list_endpoints, :list_ingresses, :list_policies, :list_volumes, :list_volume_claims, :list_config_maps, :list_secrets
|
|
32
33
|
register_subcommands :get_pod, :get_deployment, :get_replicaset, :get_daemonset, :get_endpoint, :get_ingress, :get_policy, :get_volume_claim, :get_volume, :get_config_map, :get_secret, :get_stateful_set, :get_job, :get_service
|
|
33
34
|
register_subcommands :list_affinity_groups, :get_affinity_group, :update_affinity_group, :add_affinity_group, :remove_affinity_group
|
|
35
|
+
register_subcommands :'list-affinity-group-violations' => :list_affinity_group_violations
|
|
36
|
+
register_subcommands :list_host_vm_groups, :get_host_vm_group, :add_host_vm_group, :update_host_vm_group, :remove_host_vm_group
|
|
34
37
|
|
|
35
38
|
def connect(opts)
|
|
36
39
|
@api_client = establish_remote_appliance_connection(opts)
|
|
@@ -71,8 +74,11 @@ class Morpheus::Cli::Clusters
|
|
|
71
74
|
opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
|
|
72
75
|
add_query_parameter(params, 'allLabels', parse_labels(val))
|
|
73
76
|
end
|
|
77
|
+
opts.on('--uuid UUID', String, "Filter by uuid, can be passed multiple times to match any of the values") do |val|
|
|
78
|
+
add_query_parameter(params, 'uuid', val)
|
|
79
|
+
end
|
|
74
80
|
build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
|
75
|
-
opts.footer = "List clusters."
|
|
81
|
+
opts.footer = "List clusters.\nFilter by one or more uuids using --uuid UUID."
|
|
76
82
|
end
|
|
77
83
|
optparse.parse!(args)
|
|
78
84
|
if args.count != 0
|
|
@@ -146,7 +152,7 @@ class Morpheus::Cli::Clusters
|
|
|
146
152
|
def get(args)
|
|
147
153
|
options = {}
|
|
148
154
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
149
|
-
opts.banner = subcommand_usage("[
|
|
155
|
+
opts.banner = subcommand_usage("[cluster]")
|
|
150
156
|
opts.on( nil, '--hosts', "Display masters and workers" ) do
|
|
151
157
|
options[:show_masters] = true
|
|
152
158
|
options[:show_workers] = true
|
|
@@ -170,7 +176,7 @@ class Morpheus::Cli::Clusters
|
|
|
170
176
|
options[:refresh_until_status] = val.to_s.downcase
|
|
171
177
|
end
|
|
172
178
|
build_common_options(opts, options, [:json, :dry_run, :remote])
|
|
173
|
-
opts.footer = "Get details about a cluster."
|
|
179
|
+
opts.footer = "Get details about a cluster.\n[cluster] is required. This is the id, name, or uuid of a cluster."
|
|
174
180
|
end
|
|
175
181
|
optparse.parse!(args)
|
|
176
182
|
if args.count < 1
|
|
@@ -191,6 +197,8 @@ class Morpheus::Cli::Clusters
|
|
|
191
197
|
if options[:dry_run]
|
|
192
198
|
if arg.to_s =~ /\A\d{1,}\Z/
|
|
193
199
|
print_dry_run @clusters_interface.dry.get(arg.to_i)
|
|
200
|
+
elsif cluster_uuid?(arg)
|
|
201
|
+
print_dry_run @clusters_interface.dry.get(arg.to_s)
|
|
194
202
|
else
|
|
195
203
|
print_dry_run @clusters_interface.dry.list({name:arg})
|
|
196
204
|
end
|
|
@@ -3363,10 +3371,11 @@ class Morpheus::Cli::Clusters
|
|
|
3363
3371
|
"ID" => 'id',
|
|
3364
3372
|
"Name" => 'name',
|
|
3365
3373
|
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
|
3374
|
+
"VM Group" => lambda {|it| it['vmGroup'] ? (it['vmGroup']['name'] || it['vmGroup']['id']) : '' },
|
|
3375
|
+
"Host Group" => lambda {|it| it['hostGroup'] ? (it['hostGroup']['name'] || it['hostGroup']['id']) : '' },
|
|
3366
3376
|
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
|
3367
3377
|
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
|
3368
|
-
"Servers" => lambda {|it| it['servers'].size
|
|
3369
|
-
# "Source" => lambda {|it| it['source'] },
|
|
3378
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
3370
3379
|
}.upcase_keys!
|
|
3371
3380
|
print as_pretty_table(affinity_groups, columns, options)
|
|
3372
3381
|
print_results_pagination(json_response)
|
|
@@ -3414,14 +3423,18 @@ class Morpheus::Cli::Clusters
|
|
|
3414
3423
|
"ID" => 'id',
|
|
3415
3424
|
"Name" => 'name',
|
|
3416
3425
|
"Type" => lambda {|it| format_affinity_type(it['affinityType']) },
|
|
3426
|
+
"Ref Type" => 'refType',
|
|
3427
|
+
"Ref ID" => 'refId',
|
|
3428
|
+
"VM Group" => lambda {|it| it['vmGroup'] ? (it['vmGroup']['name'] || it['vmGroup']['id']) : '' },
|
|
3429
|
+
"Host Group" => lambda {|it| it['hostGroup'] ? (it['hostGroup']['name'] || it['hostGroup']['id']) : '' },
|
|
3417
3430
|
"Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
|
|
3418
3431
|
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
|
3419
|
-
"Servers" => lambda {|it| it['servers'].size
|
|
3432
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
3420
3433
|
"Source" => lambda {|it| it['source'] },
|
|
3421
3434
|
"Active" => lambda {|it| format_boolean(it['active']) },
|
|
3422
3435
|
}
|
|
3423
3436
|
print_description_list(columns, affinity_group)
|
|
3424
|
-
if affinity_group['servers'].size > 0
|
|
3437
|
+
if affinity_group['servers'] && affinity_group['servers'].size > 0
|
|
3425
3438
|
print_h2 "Servers", options
|
|
3426
3439
|
print as_pretty_table(affinity_group['servers'], [:id, :name], options)
|
|
3427
3440
|
end
|
|
@@ -3435,6 +3448,18 @@ class Morpheus::Cli::Clusters
|
|
|
3435
3448
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3436
3449
|
opts.banner = subcommand_usage( "[cluster] [name] [options]")
|
|
3437
3450
|
build_option_type_options(opts, options, add_affinity_group_option_types)
|
|
3451
|
+
opts.on('--vm-group ID', String, "VM Group ID (for VM-to-Host rules)") do |val|
|
|
3452
|
+
options[:options] ||= {}
|
|
3453
|
+
options[:options]['vmGroup'] = {'id' => val.to_i}
|
|
3454
|
+
end
|
|
3455
|
+
opts.on('--host-group ID', String, "Host Group ID (for VM-to-Host rules)") do |val|
|
|
3456
|
+
options[:options] ||= {}
|
|
3457
|
+
options[:options]['hostGroup'] = {'id' => val.to_i}
|
|
3458
|
+
end
|
|
3459
|
+
opts.on('--servers ID1,ID2', Array, "Server IDs (for VM-to-VM rules)") do |val|
|
|
3460
|
+
options[:options] ||= {}
|
|
3461
|
+
options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
|
|
3462
|
+
end
|
|
3438
3463
|
add_perms_options(opts, options, ['plans'])
|
|
3439
3464
|
build_standard_add_options(opts, options)
|
|
3440
3465
|
opts.footer = "Add affinity group to a cluster.\n" +
|
|
@@ -3450,6 +3475,7 @@ class Morpheus::Cli::Clusters
|
|
|
3450
3475
|
cluster = find_cluster_by_name_or_id(args[0])
|
|
3451
3476
|
return 1 if cluster.nil?
|
|
3452
3477
|
if args[1]
|
|
3478
|
+
options[:options] ||= {}
|
|
3453
3479
|
options[:options]['name'] = args[1]
|
|
3454
3480
|
end
|
|
3455
3481
|
if options[:payload]
|
|
@@ -3465,13 +3491,72 @@ class Morpheus::Cli::Clusters
|
|
|
3465
3491
|
option_types = add_affinity_group_option_types
|
|
3466
3492
|
affinity_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
|
|
3467
3493
|
|
|
3468
|
-
#
|
|
3469
|
-
|
|
3494
|
+
# Rule shape — servers OR vmGroup+hostGroup. Skip if already provided via CLI flags/-O or --payload merge.
|
|
3495
|
+
preset_servers = options[:options] && options[:options].key?('servers')
|
|
3496
|
+
preset_vmhost = options[:options] && options[:options].key?('vmGroup') && options[:options].key?('hostGroup')
|
|
3497
|
+
unless preset_servers || preset_vmhost
|
|
3498
|
+
form_opts = @clusters_interface.affinity_group_form_options(cluster['id']) rescue {}
|
|
3499
|
+
available_servers = form_opts['availableServers'] || []
|
|
3500
|
+
available_vm_groups = form_opts['availableVmGroups'] || []
|
|
3501
|
+
available_host_groups = form_opts['availableHostGroups'] || []
|
|
3502
|
+
|
|
3503
|
+
shape_options = [{'name' => 'VM-to-VM (pick servers)', 'value' => 'vmToVm'}]
|
|
3504
|
+
if !available_vm_groups.empty? && !available_host_groups.empty?
|
|
3505
|
+
shape_options << {'name' => 'VM-to-Host (pick groups)', 'value' => 'vmToHost'}
|
|
3506
|
+
end
|
|
3507
|
+
shape = if shape_options.size == 1
|
|
3508
|
+
'vmToVm'
|
|
3509
|
+
else
|
|
3510
|
+
Morpheus::Cli::OptionTypes.prompt([{
|
|
3511
|
+
'fieldName' => 'shape', 'fieldLabel' => 'Rule Shape', 'type' => 'select', 'required' => true,
|
|
3512
|
+
'defaultValue' => 'vmToVm', 'selectOptions' => shape_options
|
|
3513
|
+
}], options[:options], @api_client, options[:params])['shape']
|
|
3514
|
+
end
|
|
3470
3515
|
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3516
|
+
if shape == 'vmToVm'
|
|
3517
|
+
if available_servers.empty?
|
|
3518
|
+
# Fall back to the classic multiTypeahead prompt when the form-options endpoint returns nothing
|
|
3519
|
+
server_prompt = Morpheus::Cli::OptionTypes.prompt([
|
|
3520
|
+
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers', 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'}
|
|
3521
|
+
], options[:options], @api_client, options[:params])
|
|
3522
|
+
affinity_group['servers'] = server_prompt['servers'] if server_prompt['servers']
|
|
3523
|
+
else
|
|
3524
|
+
print cyan, "Available Servers:", reset, "\n"
|
|
3525
|
+
available_servers.each {|s| print " #{s['id']}\t#{s['name']}\n" }
|
|
3526
|
+
raw = Morpheus::Cli::OptionTypes.prompt([{
|
|
3527
|
+
'fieldName' => 'servers', 'fieldLabel' => 'Servers (comma-separated IDs)', 'type' => 'text', 'required' => true,
|
|
3528
|
+
'description' => 'Server IDs to include, comma-separated'
|
|
3529
|
+
}], options[:options], @api_client, options[:params])['servers']
|
|
3530
|
+
selected_ids = raw.to_s.split(',').map(&:strip).reject(&:empty?).map(&:to_i)
|
|
3531
|
+
valid_ids = available_servers.map {|s| s['id'].to_i}
|
|
3532
|
+
invalid = selected_ids - valid_ids
|
|
3533
|
+
if !invalid.empty?
|
|
3534
|
+
print_red_alert "Invalid server IDs for this cluster: #{invalid.join(', ')}"
|
|
3535
|
+
return 1
|
|
3536
|
+
end
|
|
3537
|
+
if selected_ids.empty?
|
|
3538
|
+
print_red_alert "At least one server ID is required"
|
|
3539
|
+
return 1
|
|
3540
|
+
end
|
|
3541
|
+
affinity_group['servers'] = selected_ids.map {|id| {'id' => id}}
|
|
3542
|
+
end
|
|
3543
|
+
else
|
|
3544
|
+
vm_group_id = Morpheus::Cli::OptionTypes.prompt([{
|
|
3545
|
+
'fieldName' => 'vmGroup', 'fieldLabel' => 'VM Group', 'type' => 'select', 'required' => true,
|
|
3546
|
+
'selectOptions' => available_vm_groups.map {|g| {'name' => g['name'], 'value' => g['id']} }
|
|
3547
|
+
}], options[:options], @api_client, options[:params])['vmGroup']
|
|
3548
|
+
host_group_id = Morpheus::Cli::OptionTypes.prompt([{
|
|
3549
|
+
'fieldName' => 'hostGroup', 'fieldLabel' => 'Host Group', 'type' => 'select', 'required' => true,
|
|
3550
|
+
'selectOptions' => available_host_groups.map {|g| {'name' => g['name'], 'value' => g['id']} }
|
|
3551
|
+
}], options[:options], @api_client, options[:params])['hostGroup']
|
|
3552
|
+
affinity_group['vmGroup'] = {'id' => vm_group_id.to_i}
|
|
3553
|
+
affinity_group['hostGroup'] = {'id' => host_group_id.to_i}
|
|
3554
|
+
end
|
|
3555
|
+
else
|
|
3556
|
+
affinity_group['servers'] = options[:options]['servers'] if preset_servers
|
|
3557
|
+
affinity_group['vmGroup'] = options[:options]['vmGroup'] if preset_vmhost
|
|
3558
|
+
affinity_group['hostGroup'] = options[:options]['hostGroup'] if preset_vmhost
|
|
3559
|
+
end
|
|
3475
3560
|
|
|
3476
3561
|
# perms
|
|
3477
3562
|
perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
|
|
@@ -3659,6 +3744,367 @@ class Morpheus::Cli::Clusters
|
|
|
3659
3744
|
return 0, nil
|
|
3660
3745
|
end
|
|
3661
3746
|
|
|
3747
|
+
def list_affinity_group_violations(args)
|
|
3748
|
+
options = {}
|
|
3749
|
+
params = {}
|
|
3750
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3751
|
+
opts.banner = subcommand_usage("[cluster]")
|
|
3752
|
+
build_standard_list_options(opts, options)
|
|
3753
|
+
opts.footer = "List active MUST-rule affinity violations.\n" +
|
|
3754
|
+
"[cluster] is required. This is the name or id of an existing cluster."
|
|
3755
|
+
end
|
|
3756
|
+
optparse.parse!(args)
|
|
3757
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
3758
|
+
connect(options)
|
|
3759
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
3760
|
+
return 1 if cluster.nil?
|
|
3761
|
+
params['clusterId'] = cluster['id']
|
|
3762
|
+
params.merge!(parse_list_options(options))
|
|
3763
|
+
@clusters_interface.setopts(options)
|
|
3764
|
+
if options[:dry_run]
|
|
3765
|
+
print_dry_run @clusters_interface.dry.affinity_group_violations(params)
|
|
3766
|
+
return
|
|
3767
|
+
end
|
|
3768
|
+
json_response = @clusters_interface.affinity_group_violations(params)
|
|
3769
|
+
render_response(json_response, options, 'violations') do
|
|
3770
|
+
rows = json_response['violations'] || []
|
|
3771
|
+
print_h1 "Affinity Rule Violations", parse_list_subtitles(options), options
|
|
3772
|
+
if rows.empty?
|
|
3773
|
+
print cyan, "No violations.", reset, "\n"
|
|
3774
|
+
else
|
|
3775
|
+
columns = {
|
|
3776
|
+
"Rule" => 'ruleName',
|
|
3777
|
+
"Type" => lambda {|r| r['keepTogether'] ? 'Keep Together' : 'Keep Separate' },
|
|
3778
|
+
"VMs" => lambda {|r| (r['affectedVms'] || []).map {|v| v['name']}.join(', ') },
|
|
3779
|
+
"Occurred" => 'occurredAt'
|
|
3780
|
+
}.upcase_keys!
|
|
3781
|
+
print as_pretty_table(rows, columns, options)
|
|
3782
|
+
end
|
|
3783
|
+
print reset, "\n"
|
|
3784
|
+
end
|
|
3785
|
+
return 0, nil
|
|
3786
|
+
end
|
|
3787
|
+
|
|
3788
|
+
def list_host_vm_groups(args)
|
|
3789
|
+
options = {}
|
|
3790
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3791
|
+
opts.banner = subcommand_usage("[cluster]")
|
|
3792
|
+
build_standard_list_options(opts, options)
|
|
3793
|
+
opts.footer = "List host/VM groups on a cluster.\n" +
|
|
3794
|
+
"[cluster] is required. This is the name or id of an existing cluster."
|
|
3795
|
+
end
|
|
3796
|
+
optparse.parse!(args)
|
|
3797
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
3798
|
+
connect(options)
|
|
3799
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
3800
|
+
return 1 if cluster.nil?
|
|
3801
|
+
params = {}
|
|
3802
|
+
params.merge!(parse_list_options(options))
|
|
3803
|
+
@clusters_interface.setopts(options)
|
|
3804
|
+
if options[:dry_run]
|
|
3805
|
+
print_dry_run @clusters_interface.dry.list_host_vm_groups(cluster['id'], params)
|
|
3806
|
+
return
|
|
3807
|
+
end
|
|
3808
|
+
json_response = @clusters_interface.list_host_vm_groups(cluster['id'], params)
|
|
3809
|
+
render_response(json_response, options, 'hostVmGroups') do
|
|
3810
|
+
rows = json_response['hostVmGroups'] || []
|
|
3811
|
+
print_h1 "Morpheus Cluster Host/VM Groups: #{cluster['name']}", parse_list_subtitles(options), options
|
|
3812
|
+
if rows.empty?
|
|
3813
|
+
print cyan, "No host/VM groups found.", reset, "\n"
|
|
3814
|
+
else
|
|
3815
|
+
columns = {
|
|
3816
|
+
"ID" => 'id',
|
|
3817
|
+
"Name" => 'name',
|
|
3818
|
+
"Type" => lambda {|it| format_host_vm_group_type(it['type']) },
|
|
3819
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
3820
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
|
3821
|
+
}.upcase_keys!
|
|
3822
|
+
print as_pretty_table(rows, columns, options)
|
|
3823
|
+
print_results_pagination(json_response)
|
|
3824
|
+
end
|
|
3825
|
+
print reset, "\n"
|
|
3826
|
+
end
|
|
3827
|
+
return 0, nil
|
|
3828
|
+
end
|
|
3829
|
+
|
|
3830
|
+
def get_host_vm_group(args)
|
|
3831
|
+
options = {}
|
|
3832
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3833
|
+
opts.banner = subcommand_usage("[cluster] [host-vm-group]")
|
|
3834
|
+
build_standard_get_options(opts, options)
|
|
3835
|
+
opts.footer = "Get details about a cluster host/VM group.\n" +
|
|
3836
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
|
3837
|
+
"[host-vm-group] is required. This is the name or id of an existing host/VM group."
|
|
3838
|
+
end
|
|
3839
|
+
optparse.parse!(args)
|
|
3840
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
|
3841
|
+
connect(options)
|
|
3842
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
3843
|
+
return 1 if cluster.nil?
|
|
3844
|
+
host_vm_group = find_cluster_host_vm_group_by_name_or_id(cluster['id'], args[1])
|
|
3845
|
+
if host_vm_group.nil?
|
|
3846
|
+
print_red_alert "Host/VM Group not found for '#{args[1]}'"
|
|
3847
|
+
exit 1
|
|
3848
|
+
end
|
|
3849
|
+
params = {}
|
|
3850
|
+
params.merge!(parse_query_options(options))
|
|
3851
|
+
@clusters_interface.setopts(options)
|
|
3852
|
+
if options[:dry_run]
|
|
3853
|
+
print_dry_run @clusters_interface.dry.get_host_vm_group(cluster['id'], host_vm_group['id'], params)
|
|
3854
|
+
return
|
|
3855
|
+
end
|
|
3856
|
+
json_response = @clusters_interface.get_host_vm_group(cluster['id'], host_vm_group['id'], params)
|
|
3857
|
+
render_response(json_response, options, 'hostVmGroup') do
|
|
3858
|
+
row = json_response['hostVmGroup']
|
|
3859
|
+
print_h1 "Host/VM Group Details", [], options
|
|
3860
|
+
columns = {
|
|
3861
|
+
"ID" => 'id',
|
|
3862
|
+
"Name" => 'name',
|
|
3863
|
+
"Type" => lambda {|it| format_host_vm_group_type(it['type']) },
|
|
3864
|
+
"Ref Type" => 'refType',
|
|
3865
|
+
"Ref ID" => 'refId',
|
|
3866
|
+
"Servers" => lambda {|it| (it['servers'] || []).size },
|
|
3867
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
|
3868
|
+
"Active" => lambda {|it| format_boolean(it['active']) },
|
|
3869
|
+
}
|
|
3870
|
+
print_description_list(columns, row)
|
|
3871
|
+
if row['servers'] && row['servers'].size > 0
|
|
3872
|
+
print_h2 "Servers", options
|
|
3873
|
+
print as_pretty_table(row['servers'], [:id, :name], options)
|
|
3874
|
+
end
|
|
3875
|
+
print reset, "\n"
|
|
3876
|
+
end
|
|
3877
|
+
return 0, nil
|
|
3878
|
+
end
|
|
3879
|
+
|
|
3880
|
+
def add_host_vm_group(args)
|
|
3881
|
+
options = {}
|
|
3882
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3883
|
+
opts.banner = subcommand_usage("[cluster] [name] [options]")
|
|
3884
|
+
build_option_type_options(opts, options, add_host_vm_group_option_types)
|
|
3885
|
+
opts.on('--servers ID1,ID2', Array, "Server IDs to include") do |val|
|
|
3886
|
+
options[:options] ||= {}
|
|
3887
|
+
options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
|
|
3888
|
+
end
|
|
3889
|
+
add_perms_options(opts, options, ['plans'])
|
|
3890
|
+
build_standard_add_options(opts, options)
|
|
3891
|
+
opts.footer = "Add a host/VM group to a cluster.\n" +
|
|
3892
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
|
3893
|
+
"[name] is required. This is the name of the new host/VM group."
|
|
3894
|
+
end
|
|
3895
|
+
optparse.parse!(args)
|
|
3896
|
+
verify_args!(args:args, optparse:optparse, min:1, max:2)
|
|
3897
|
+
connect(options)
|
|
3898
|
+
|
|
3899
|
+
begin
|
|
3900
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
3901
|
+
return 1 if cluster.nil?
|
|
3902
|
+
if args[1]
|
|
3903
|
+
options[:options] ||= {}
|
|
3904
|
+
options[:options]['name'] = args[1]
|
|
3905
|
+
end
|
|
3906
|
+
if options[:payload]
|
|
3907
|
+
payload = options[:payload]
|
|
3908
|
+
if options[:options]
|
|
3909
|
+
payload ||= {}
|
|
3910
|
+
payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
|
|
3911
|
+
end
|
|
3912
|
+
else
|
|
3913
|
+
options[:params] ||= {}
|
|
3914
|
+
options[:params].merge!({:serverGroupId => cluster['id']})
|
|
3915
|
+
option_types = add_host_vm_group_option_types
|
|
3916
|
+
host_vm_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
|
|
3917
|
+
|
|
3918
|
+
# Servers — form-options driven, filtered by chosen type
|
|
3919
|
+
unless options[:options] && options[:options].key?('servers')
|
|
3920
|
+
form_opts = @clusters_interface.host_vm_group_form_options(cluster['id'], {'hostVmGroupType' => host_vm_group['type']}) rescue {}
|
|
3921
|
+
available_servers = form_opts['availableServers'] || []
|
|
3922
|
+
if available_servers.empty?
|
|
3923
|
+
print_red_alert "No servers available on cluster #{cluster['name']} for type #{host_vm_group['type']}"
|
|
3924
|
+
return 1
|
|
3925
|
+
end
|
|
3926
|
+
print cyan, "Available Servers:", reset, "\n"
|
|
3927
|
+
available_servers.each {|s| print " #{s['id']}\t#{s['name']}\n" }
|
|
3928
|
+
raw = Morpheus::Cli::OptionTypes.prompt([{
|
|
3929
|
+
'fieldName' => 'servers', 'fieldLabel' => 'Servers (comma-separated IDs)', 'type' => 'text', 'required' => true,
|
|
3930
|
+
'description' => 'Server IDs to include, comma-separated'
|
|
3931
|
+
}], options[:options], @api_client, options[:params])['servers']
|
|
3932
|
+
selected_ids = raw.to_s.split(',').map(&:strip).reject(&:empty?).map(&:to_i)
|
|
3933
|
+
valid_ids = available_servers.map {|s| s['id'].to_i}
|
|
3934
|
+
invalid = selected_ids - valid_ids
|
|
3935
|
+
if !invalid.empty?
|
|
3936
|
+
print_red_alert "Invalid server IDs for this cluster/type: #{invalid.join(', ')}"
|
|
3937
|
+
return 1
|
|
3938
|
+
end
|
|
3939
|
+
if selected_ids.empty?
|
|
3940
|
+
print_red_alert "At least one server ID is required"
|
|
3941
|
+
return 1
|
|
3942
|
+
end
|
|
3943
|
+
host_vm_group['servers'] = selected_ids.map {|id| {'id' => id}}
|
|
3944
|
+
else
|
|
3945
|
+
host_vm_group['servers'] = options[:options]['servers']
|
|
3946
|
+
end
|
|
3947
|
+
|
|
3948
|
+
perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
|
|
3949
|
+
host_vm_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
|
|
3950
|
+
host_vm_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil? || perms['tenantPermissions']['accounts'].empty?
|
|
3951
|
+
host_vm_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
|
3952
|
+
|
|
3953
|
+
payload = {'hostVmGroup' => host_vm_group}
|
|
3954
|
+
end
|
|
3955
|
+
|
|
3956
|
+
@clusters_interface.setopts(options)
|
|
3957
|
+
if options[:dry_run]
|
|
3958
|
+
print_dry_run @clusters_interface.dry.create_host_vm_group(cluster['id'], payload)
|
|
3959
|
+
return
|
|
3960
|
+
end
|
|
3961
|
+
json_response = @clusters_interface.create_host_vm_group(cluster['id'], payload)
|
|
3962
|
+
if options[:json]
|
|
3963
|
+
puts as_json(json_response)
|
|
3964
|
+
else
|
|
3965
|
+
if json_response['success']
|
|
3966
|
+
print_green_success (json_response['msg'] || "Added host/VM group to cluster #{cluster['name']}")
|
|
3967
|
+
else
|
|
3968
|
+
print_red_alert "Failed to create host/VM group #{json_response['msg']}"
|
|
3969
|
+
end
|
|
3970
|
+
end
|
|
3971
|
+
return 0
|
|
3972
|
+
rescue RestClient::Exception => e
|
|
3973
|
+
print_rest_exception(e, options)
|
|
3974
|
+
exit 1
|
|
3975
|
+
end
|
|
3976
|
+
end
|
|
3977
|
+
|
|
3978
|
+
def update_host_vm_group(args)
|
|
3979
|
+
options = {}
|
|
3980
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
3981
|
+
opts.banner = subcommand_usage("[cluster] [host-vm-group] [options]")
|
|
3982
|
+
build_option_type_options(opts, options, update_host_vm_group_option_types)
|
|
3983
|
+
opts.on('--servers ID1,ID2', Array, "Replace server IDs") do |val|
|
|
3984
|
+
options[:options] ||= {}
|
|
3985
|
+
options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
|
|
3986
|
+
end
|
|
3987
|
+
opts.on('--visibility VISIBILITY', String, "Visibility: private | public") do |val|
|
|
3988
|
+
options[:options] ||= {}
|
|
3989
|
+
options[:options]['visibility'] = val
|
|
3990
|
+
end
|
|
3991
|
+
add_perms_options(opts, options, ['plans'])
|
|
3992
|
+
build_standard_update_options(opts, options)
|
|
3993
|
+
opts.footer = "Update a cluster host/VM group.\n" +
|
|
3994
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
|
3995
|
+
"[host-vm-group] is required. This is the name or id of an existing host/VM group."
|
|
3996
|
+
end
|
|
3997
|
+
optparse.parse!(args)
|
|
3998
|
+
if args.count != 2
|
|
3999
|
+
raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
|
|
4000
|
+
end
|
|
4001
|
+
connect(options)
|
|
4002
|
+
|
|
4003
|
+
begin
|
|
4004
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
4005
|
+
return 1 if cluster.nil?
|
|
4006
|
+
host_vm_group = find_cluster_host_vm_group_by_name_or_id(cluster['id'], args[1])
|
|
4007
|
+
if host_vm_group.nil?
|
|
4008
|
+
print_red_alert "Host/VM Group not found by '#{args[1]}'"
|
|
4009
|
+
exit 1
|
|
4010
|
+
end
|
|
4011
|
+
payload = nil
|
|
4012
|
+
if options[:payload]
|
|
4013
|
+
payload = options[:payload]
|
|
4014
|
+
if options[:options]
|
|
4015
|
+
payload.deep_merge!({'hostVmGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
|
|
4016
|
+
end
|
|
4017
|
+
else
|
|
4018
|
+
payload = {'hostVmGroup' => {}}
|
|
4019
|
+
options[:params] ||= {}
|
|
4020
|
+
options[:params].merge!({:serverGroupId => cluster['id']})
|
|
4021
|
+
option_types = update_host_vm_group_option_types
|
|
4022
|
+
v_prompt = Morpheus::Cli::OptionTypes.no_prompt(option_types, options[:options], @api_client, options[:params])
|
|
4023
|
+
v_prompt.deep_compact!.booleanize!
|
|
4024
|
+
payload.deep_merge!({'hostVmGroup' => v_prompt})
|
|
4025
|
+
|
|
4026
|
+
perms = prompt_permissions(options.merge({:no_prompt => true}), is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
|
|
4027
|
+
perms_payload = {}
|
|
4028
|
+
perms_payload['resourcePermissions'] = perms['resourcePermissions'] if !perms['resourcePermissions'].nil? && !perms['resourcePermissions'].empty?
|
|
4029
|
+
perms_payload['tenantPermissions'] = perms['tenantPermissions'] if !perms['tenantPermissions'].nil? && !perms['tenantPermissions']['accounts'].empty?
|
|
4030
|
+
payload['hostVmGroup']['permissions'] = perms_payload if !perms_payload.empty?
|
|
4031
|
+
payload['hostVmGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
|
|
4032
|
+
|
|
4033
|
+
if options[:options]
|
|
4034
|
+
payload.deep_merge!({'hostVmGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) || payload['hostVmGroup'].key?(k) }})
|
|
4035
|
+
end
|
|
4036
|
+
|
|
4037
|
+
if payload['hostVmGroup'].nil? || payload['hostVmGroup'].empty?
|
|
4038
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
|
4039
|
+
end
|
|
4040
|
+
end
|
|
4041
|
+
|
|
4042
|
+
@clusters_interface.setopts(options)
|
|
4043
|
+
if options[:dry_run]
|
|
4044
|
+
print_dry_run @clusters_interface.dry.update_host_vm_group(cluster['id'], host_vm_group['id'], payload)
|
|
4045
|
+
return
|
|
4046
|
+
end
|
|
4047
|
+
json_response = @clusters_interface.update_host_vm_group(cluster['id'], host_vm_group['id'], payload)
|
|
4048
|
+
if options[:json]
|
|
4049
|
+
puts as_json(json_response)
|
|
4050
|
+
elsif !options[:quiet]
|
|
4051
|
+
updated = json_response['hostVmGroup'] || host_vm_group
|
|
4052
|
+
print_green_success "Updated host/VM group #{updated['name'] || updated['id']}"
|
|
4053
|
+
end
|
|
4054
|
+
return 0
|
|
4055
|
+
rescue RestClient::Exception => e
|
|
4056
|
+
print_rest_exception(e, options)
|
|
4057
|
+
exit 1
|
|
4058
|
+
end
|
|
4059
|
+
end
|
|
4060
|
+
|
|
4061
|
+
def remove_host_vm_group(args)
|
|
4062
|
+
params = {}
|
|
4063
|
+
options = {}
|
|
4064
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
4065
|
+
opts.banner = subcommand_usage("[cluster] [host-vm-group]")
|
|
4066
|
+
build_standard_remove_options(opts, options)
|
|
4067
|
+
opts.footer = "Delete a host/VM group from a cluster.\n" +
|
|
4068
|
+
"[cluster] is required. This is the name or id of an existing cluster.\n" +
|
|
4069
|
+
"[host-vm-group] is required. This is the name or id of an existing host/VM group."
|
|
4070
|
+
end
|
|
4071
|
+
optparse.parse!(args)
|
|
4072
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
|
4073
|
+
connect(options)
|
|
4074
|
+
params.merge!(parse_query_options(options))
|
|
4075
|
+
|
|
4076
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
|
4077
|
+
return 1 if cluster.nil?
|
|
4078
|
+
host_vm_group_id = args[1]
|
|
4079
|
+
if host_vm_group_id.empty?
|
|
4080
|
+
raise_command_error "missing required host/VM group parameter"
|
|
4081
|
+
end
|
|
4082
|
+
host_vm_group = find_cluster_host_vm_group_by_name_or_id(cluster['id'], host_vm_group_id)
|
|
4083
|
+
if host_vm_group.nil?
|
|
4084
|
+
print_red_alert "Host/VM Group not found for '#{host_vm_group_id}'"
|
|
4085
|
+
return 1
|
|
4086
|
+
end
|
|
4087
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster host/VM group '#{host_vm_group['name'] || host_vm_group['id']}'?", options)
|
|
4088
|
+
return 9, "aborted command"
|
|
4089
|
+
end
|
|
4090
|
+
@clusters_interface.setopts(options)
|
|
4091
|
+
if options[:dry_run]
|
|
4092
|
+
print_dry_run @clusters_interface.dry.destroy_host_vm_group(cluster['id'], host_vm_group['id'], params)
|
|
4093
|
+
return
|
|
4094
|
+
end
|
|
4095
|
+
json_response = @clusters_interface.destroy_host_vm_group(cluster['id'], host_vm_group['id'], params)
|
|
4096
|
+
if options[:json]
|
|
4097
|
+
puts as_json(json_response)
|
|
4098
|
+
else
|
|
4099
|
+
if json_response['success']
|
|
4100
|
+
print_green_success "Removed host/VM group #{host_vm_group['name']}"
|
|
4101
|
+
else
|
|
4102
|
+
print_red_alert "Failed to remove cluster host/VM group #{json_response['msg']}"
|
|
4103
|
+
end
|
|
4104
|
+
end
|
|
4105
|
+
return 0, nil
|
|
4106
|
+
end
|
|
4107
|
+
|
|
3662
4108
|
def api_config(args)
|
|
3663
4109
|
options = {}
|
|
3664
4110
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
@@ -4589,11 +5035,17 @@ class Morpheus::Cli::Clusters
|
|
|
4589
5035
|
def find_cluster_by_name_or_id(val)
|
|
4590
5036
|
if val.to_s =~ /\A\d{1,}\Z/
|
|
4591
5037
|
find_cluster_by_id(val)
|
|
5038
|
+
elsif cluster_uuid?(val)
|
|
5039
|
+
find_cluster_by_uuid(val)
|
|
4592
5040
|
else
|
|
4593
5041
|
find_cluster_by_name(val)
|
|
4594
5042
|
end
|
|
4595
5043
|
end
|
|
4596
5044
|
|
|
5045
|
+
def cluster_uuid?(val)
|
|
5046
|
+
!!(val.to_s =~ /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
|
5047
|
+
end
|
|
5048
|
+
|
|
4597
5049
|
def find_cluster_by_id(id)
|
|
4598
5050
|
json_results = @clusters_interface.get(id.to_i)
|
|
4599
5051
|
if json_results['cluster'].empty?
|
|
@@ -4603,6 +5055,22 @@ class Morpheus::Cli::Clusters
|
|
|
4603
5055
|
json_results['cluster']
|
|
4604
5056
|
end
|
|
4605
5057
|
|
|
5058
|
+
def find_cluster_by_uuid(uuid)
|
|
5059
|
+
json_results = @clusters_interface.get(uuid.to_s)
|
|
5060
|
+
if json_results['cluster'].nil? || json_results['cluster'].empty?
|
|
5061
|
+
print_red_alert "Cluster not found by uuid #{uuid}"
|
|
5062
|
+
exit 1
|
|
5063
|
+
end
|
|
5064
|
+
json_results['cluster']
|
|
5065
|
+
rescue RestClient::Exception => e
|
|
5066
|
+
if e.response && e.response.code == 404
|
|
5067
|
+
print_red_alert "Cluster not found by uuid #{uuid}"
|
|
5068
|
+
exit 1
|
|
5069
|
+
else
|
|
5070
|
+
raise e
|
|
5071
|
+
end
|
|
5072
|
+
end
|
|
5073
|
+
|
|
4606
5074
|
def find_cluster_by_name(name)
|
|
4607
5075
|
json_results = @clusters_interface.list({name: name})
|
|
4608
5076
|
if json_results['clusters'].empty? || json_results['clusters'].count > 1
|
|
@@ -5343,25 +5811,44 @@ class Morpheus::Cli::Clusters
|
|
|
5343
5811
|
end
|
|
5344
5812
|
end
|
|
5345
5813
|
|
|
5346
|
-
|
|
5814
|
+
def add_affinity_group_option_types
|
|
5347
5815
|
[
|
|
5348
5816
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
|
5349
|
-
|
|
5817
|
+
affinity_type_option_type(required: true, default: 'KEEP_TOGETHER'),
|
|
5350
5818
|
{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'defaultValue' => true},
|
|
5351
|
-
# {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware', 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
|
|
5352
|
-
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers', 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
|
|
5353
5819
|
]
|
|
5354
5820
|
end
|
|
5355
5821
|
|
|
5356
5822
|
def update_affinity_group_option_types
|
|
5357
5823
|
[
|
|
5824
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text'},
|
|
5825
|
+
affinity_type_option_type,
|
|
5358
5826
|
{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox'},
|
|
5359
|
-
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers',
|
|
5827
|
+
{'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers',
|
|
5828
|
+
'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
|
|
5829
|
+
]
|
|
5830
|
+
end
|
|
5831
|
+
|
|
5832
|
+
def add_host_vm_group_option_types
|
|
5833
|
+
[
|
|
5834
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
|
5835
|
+
host_vm_group_type_option_type(required: true, default: 'HOST_GROUP'),
|
|
5836
|
+
]
|
|
5837
|
+
end
|
|
5838
|
+
|
|
5839
|
+
def update_host_vm_group_option_types
|
|
5840
|
+
[
|
|
5841
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text'},
|
|
5360
5842
|
]
|
|
5361
5843
|
end
|
|
5362
5844
|
|
|
5363
|
-
def
|
|
5364
|
-
|
|
5845
|
+
def find_cluster_host_vm_group_by_name_or_id(cluster_id, val)
|
|
5846
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
|
5847
|
+
@clusters_interface.get_host_vm_group(cluster_id, val)['hostVmGroup'] rescue nil
|
|
5848
|
+
else
|
|
5849
|
+
list = @clusters_interface.list_host_vm_groups(cluster_id, {name: val})['hostVmGroups'] || []
|
|
5850
|
+
list.first
|
|
5851
|
+
end
|
|
5365
5852
|
end
|
|
5366
5853
|
|
|
5367
5854
|
end
|