morpheus-cli 9.0.2 → 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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/affinity_groups_interface.rb +46 -0
  4. data/lib/morpheus/api/api_client.rb +13 -0
  5. data/lib/morpheus/api/clouds_interface.rb +42 -0
  6. data/lib/morpheus/api/clusters_interface.rb +7 -1
  7. data/lib/morpheus/api/distributed_workers_interface.rb +41 -0
  8. data/lib/morpheus/api/host_vm_groups_interface.rb +37 -0
  9. data/lib/morpheus/cli/commands/affinity_groups_command.rb +554 -0
  10. data/lib/morpheus/cli/commands/backups_command.rb +1 -1
  11. data/lib/morpheus/cli/commands/clouds.rb +421 -29
  12. data/lib/morpheus/cli/commands/clusters.rb +37 -39
  13. data/lib/morpheus/cli/commands/distributed_workers_command.rb +282 -0
  14. data/lib/morpheus/cli/commands/host_vm_groups_command.rb +423 -0
  15. data/lib/morpheus/cli/mixins/affinity_helper.rb +118 -0
  16. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +137 -0
  17. data/lib/morpheus/cli/option_types.rb +36 -1
  18. data/lib/morpheus/cli/version.rb +1 -1
  19. data/test/api/affinity_groups_interface_test.rb +43 -0
  20. data/test/api/clouds_interface_test.rb +67 -0
  21. data/test/api/host_vm_groups_interface_test.rb +35 -0
  22. data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
  23. data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
  24. data/test/cli/affinity_groups_test.rb +57 -0
  25. data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
  26. data/test/cli/affinity_helper_test.rb +367 -0
  27. data/test/cli/backups_test.rb +212 -0
  28. data/test/cli/clusters_uuid_test.rb +131 -0
  29. data/test/cli/distributed_workers_test.rb +68 -0
  30. data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
  31. data/test/cli/host_vm_groups_test.rb +63 -0
  32. data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
  33. metadata +37 -2
@@ -6,6 +6,7 @@ class Morpheus::Cli::Clouds
6
6
  include Morpheus::Cli::InfrastructureHelper
7
7
  include Morpheus::Cli::ProvisioningHelper
8
8
  include Morpheus::Cli::WhoamiHelper
9
+ include Morpheus::Cli::AffinityHelper
9
10
 
10
11
  register_subcommands :list, :count, :get, :add, :update, :remove, :refresh, :security_groups, :apply_security_groups
11
12
  register_subcommands :types, :type
@@ -14,6 +15,7 @@ class Morpheus::Cli::Clouds
14
15
  register_subcommands :wiki, :update_wiki
15
16
  register_subcommands({:'update-logo' => :update_logo,:'update-dark-logo' => :update_dark_logo})
16
17
  register_subcommands :list_affinity_groups, :get_affinity_group, :update_affinity_group, :add_affinity_group, :remove_affinity_group
18
+ register_subcommands :list_host_vm_groups, :get_host_vm_group, :add_host_vm_group, :update_host_vm_group, :remove_host_vm_group
17
19
  #register_subcommands :firewall_disable, :firewall_enable
18
20
  alias_subcommand :details, :get
19
21
  set_default_subcommand :list
@@ -1181,7 +1183,7 @@ EOT
1181
1183
  def list_affinity_groups(args)
1182
1184
  options = {}
1183
1185
  optparse = Morpheus::Cli::OptionParser.new do |opts|
1184
- opts.banner = subcommand_usage( "[cloud]")
1186
+ opts.banner = subcommand_usage("[cloud]")
1185
1187
  build_standard_list_options(opts, options)
1186
1188
  opts.footer = "List affinity groups for a cloud.\n" +
1187
1189
  "[cloud] is required. This is the name or id of an existing cloud."
@@ -1190,35 +1192,42 @@ EOT
1190
1192
  optparse.parse!(args)
1191
1193
  verify_args!(args:args, optparse:optparse, count:1)
1192
1194
  connect(options)
1193
-
1195
+
1194
1196
  cloud = find_cloud_by_name_or_id(args[0])
1195
1197
  return 1 if cloud.nil?
1196
1198
  params = {}
1197
1199
  params.merge!(parse_list_options(options))
1200
+ @clouds_interface.setopts(options)
1201
+ if options[:dry_run]
1202
+ print_dry_run @clouds_interface.dry.list_affinity_groups(cloud['id'], params)
1203
+ return
1204
+ end
1198
1205
  json_response = @clouds_interface.list_affinity_groups(cloud['id'], params)
1199
1206
  render_response(json_response, options, 'affinityGroups') do
1200
1207
  affinity_groups = json_response['affinityGroups']
1201
- print_h1 "Morpheus Cloud Affinity Groups: #{cloud['name']}", parse_list_subtitles(options)
1208
+ print_h1 "Cloud Affinity Groups: \#{cloud['name']}", parse_list_subtitles(options), options
1202
1209
  if affinity_groups.empty?
1203
- print cyan,"No affinity groups found.",reset,"\n"
1204
- else
1210
+ print cyan, "No affinity groups found.", reset, "\n"
1211
+ else
1205
1212
  columns = {
1206
- "ID" => 'id',
1207
- "Name" => 'name',
1208
- "Type" => lambda {|it| format_affinity_type(it['affinityType']) },
1209
- "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
1210
- "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
1211
- # "Servers" => lambda {|it| it['serverCount'] },
1212
- # "Source" => lambda {|it| it['source'] },
1213
+ "ID" => 'id',
1214
+ "Name" => 'name',
1215
+ "Type" => lambda {|it| format_affinity_type(it['affinityType']) },
1216
+ "VM Group" => lambda {|it| it['vmGroup'] ? (it['vmGroup']['name'] || it['vmGroup']['id']) : '' },
1217
+ "Host Group" => lambda {|it| it['hostGroup'] ? (it['hostGroup']['name'] || it['hostGroup']['id']) : '' },
1218
+ "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
1219
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
1220
+ "Servers" => lambda {|it| (it['servers'] || []).size },
1213
1221
  }.upcase_keys!
1214
1222
  print as_pretty_table(affinity_groups, columns, options)
1215
1223
  print_results_pagination(json_response)
1216
1224
  end
1217
- print reset,"\n"
1225
+ print reset, "\n"
1218
1226
  end
1219
1227
  return 0, nil
1220
1228
  end
1221
1229
 
1230
+
1222
1231
  def get_affinity_group(args)
1223
1232
  options = {}
1224
1233
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -1278,6 +1287,18 @@ EOT
1278
1287
  optparse = Morpheus::Cli::OptionParser.new do |opts|
1279
1288
  opts.banner = subcommand_usage( "[cloud] [name] [options]")
1280
1289
  build_option_type_options(opts, options, add_cloud_affinity_group_option_types)
1290
+ opts.on('--vm-group ID', String, "VM Group id (for VM-to-Host rules)") do |val|
1291
+ options[:options] ||= {}
1292
+ options[:options]['vmGroup'] = {'id' => val.to_i}
1293
+ end
1294
+ opts.on('--host-group ID', String, "Host Group id (for VM-to-Host rules)") do |val|
1295
+ options[:options] ||= {}
1296
+ options[:options]['hostGroup'] = {'id' => val.to_i}
1297
+ end
1298
+ opts.on('--servers ID1,ID2', Array, "Server ids (for VM-to-VM rules)") do |val|
1299
+ options[:options] ||= {}
1300
+ options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
1301
+ end
1281
1302
  add_perms_options(opts, options, ['plans'])
1282
1303
  build_standard_add_options(opts, options)
1283
1304
  opts.footer = "Add affinity group to a cloud.\n" +
@@ -1308,19 +1329,74 @@ EOT
1308
1329
  option_types = add_cloud_affinity_group_option_types
1309
1330
  affinity_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
1310
1331
 
1311
- # affinity_group_type = find_affinity_group_type_by_code(affinity_group['affinityGroupType'])
1312
- # affinity_group['affinityGroupType'] = {id:affinity_group_type['id']}
1332
+ preset_servers = options[:options] && options[:options].key?('servers')
1333
+ preset_vmhost = options[:options] && options[:options].key?('vmGroup') && options[:options].key?('hostGroup')
1334
+
1335
+ form_opts = @clouds_interface.affinity_group_form_options(cloud['id']) rescue {}
1336
+ # Capability gate: AffinityGroupsController#formOptions derives
1337
+ # supportsCombinedRules from zoneType.hasHostVmGroups. Use the flag
1338
+ # itself -- the presence of groups is not the same as support for them.
1339
+ supports_combined = form_opts['supportsCombinedRules'] == true
1340
+
1341
+ # Cluster-in-cloud: the 'pool.id' option type above already prompts for
1342
+ # the cluster. Re-query form-options narrowed by that pool so the group
1343
+ # pickers only offer groups from the cluster the rule will belong to.
1344
+ pool_id = affinity_group['pool'].is_a?(Hash) ? affinity_group['pool']['id'] : nil
1345
+ if pool_id && supports_combined
1346
+ form_opts = (@clouds_interface.affinity_group_form_options(cloud['id'], {'poolId' => pool_id}) rescue form_opts)
1347
+ end
1348
+
1349
+ available_vm_groups = filter_by_pool(form_opts['availableVmGroups'] || [], pool_id)
1350
+ available_host_groups = filter_by_pool(form_opts['availableHostGroups'] || [], pool_id)
1313
1351
 
1314
- # # affinity_group type options
1315
- # unless affinity_group_type['optionTypes'].empty?
1316
- # affinity_group.merge!(Morpheus::Cli::OptionTypes.prompt(affinity_group_type['optionTypes'], options[:options].deep_merge({:context_map => {'domain' => ''}, :checkbox_as_boolean => true}), @api_client, options[:params]))
1317
- # end
1352
+ if !supports_combined && preset_vmhost
1353
+ print yellow, "This cloud does not support VM-to-Host affinity rules.", reset, "\n"
1354
+ end
1355
+
1356
+ unless preset_servers || preset_vmhost
1357
+ if supports_combined && !available_vm_groups.empty? && !available_host_groups.empty?
1358
+ shape = Morpheus::Cli::OptionTypes.prompt([{
1359
+ 'fieldName' => 'shape', 'fieldLabel' => 'Rule Shape', 'type' => 'select',
1360
+ 'required' => true, 'defaultValue' => 'vmToVm',
1361
+ 'selectOptions' => [
1362
+ {'name' => 'VM-to-VM (pick servers)', 'value' => 'vmToVm'},
1363
+ {'name' => 'VM-to-Host (pick groups)', 'value' => 'vmToHost'},
1364
+ ]
1365
+ }], options[:options], @api_client, options[:params])['shape']
1366
+ else
1367
+ shape = 'vmToVm'
1368
+ end
1369
+
1370
+ if shape == 'vmToHost'
1371
+ vm_group_id = Morpheus::Cli::OptionTypes.prompt([{
1372
+ 'fieldName' => 'vmGroup', 'fieldLabel' => 'VM Group', 'type' => 'select', 'required' => true,
1373
+ 'selectOptions' => available_vm_groups.map {|g| {'name' => g['name'], 'value' => g['id']} }
1374
+ }], options[:options], @api_client, options[:params])['vmGroup']
1375
+ host_group_id = Morpheus::Cli::OptionTypes.prompt([{
1376
+ 'fieldName' => 'hostGroup', 'fieldLabel' => 'Host Group', 'type' => 'select', 'required' => true,
1377
+ 'selectOptions' => available_host_groups.map {|g| {'name' => g['name'], 'value' => g['id']} }
1378
+ }], options[:options], @api_client, options[:params])['hostGroup']
1379
+ affinity_group['vmGroup'] = {'id' => vm_group_id.to_i}
1380
+ affinity_group['hostGroup'] = {'id' => host_group_id.to_i}
1381
+ else
1382
+ server_prompt = Morpheus::Cli::OptionTypes.prompt([{
1383
+ 'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead',
1384
+ 'optionSource' => 'searchServers', 'searchParameter' => 'phrase',
1385
+ 'description' => 'Select servers to be in the affinity group.'
1386
+ }], options[:options], @api_client, options[:params])
1387
+ affinity_group['servers'] = server_prompt['servers'] if server_prompt['servers']
1388
+ end
1389
+ else
1390
+ affinity_group['servers'] = options[:options]['servers'] if preset_servers
1391
+ affinity_group['vmGroup'] = options[:options]['vmGroup'] if preset_vmhost
1392
+ affinity_group['hostGroup'] = options[:options]['hostGroup'] if preset_vmhost
1393
+ end
1318
1394
 
1319
1395
  # perms
1320
1396
  perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
1321
1397
 
1322
- affinity_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].empty?
1323
- affinity_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].empty? || perms['tenantPermissions']['accounts'].empty?
1398
+ affinity_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
1399
+ affinity_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil? || perms['tenantPermissions']['accounts'].empty?
1324
1400
  affinity_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
1325
1401
 
1326
1402
  payload = {'affinityGroup' => affinity_group}
@@ -1351,7 +1427,7 @@ EOT
1351
1427
  end
1352
1428
  return 0
1353
1429
  rescue RestClient::Exception => e
1354
- print_rest_exception(e, options)
1430
+ handle_affinity_rest_exception(e, options)
1355
1431
  exit 1
1356
1432
  end
1357
1433
  end
@@ -1502,6 +1578,295 @@ EOT
1502
1578
  return 0, nil
1503
1579
  end
1504
1580
 
1581
+ def list_host_vm_groups(args)
1582
+ options = {}
1583
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1584
+ opts.banner = subcommand_usage("[cloud]")
1585
+ build_standard_list_options(opts, options)
1586
+ opts.footer = "List host/VM groups for a cloud.\n" +
1587
+ "[cloud] is required. This is the name or id of an existing cloud."
1588
+ end
1589
+ optparse.parse!(args)
1590
+ verify_args!(args:args, optparse:optparse, count:1)
1591
+ connect(options)
1592
+
1593
+ cloud = find_cloud_by_name_or_id(args[0])
1594
+ return 1 if cloud.nil?
1595
+ params = {}
1596
+ params.merge!(parse_list_options(options))
1597
+ @clouds_interface.setopts(options)
1598
+ if options[:dry_run]
1599
+ print_dry_run @clouds_interface.dry.list_host_vm_groups(cloud['id'], params)
1600
+ return
1601
+ end
1602
+ json_response = @clouds_interface.list_host_vm_groups(cloud['id'], params)
1603
+ render_response(json_response, options, 'hostVmGroups') do
1604
+ rows = json_response['hostVmGroups'] || []
1605
+ print_h1 "Cloud Host/VM Groups: \#{cloud['name']}", parse_list_subtitles(options), options
1606
+ if rows.empty?
1607
+ print cyan, "No host/VM groups found.", reset, "\n"
1608
+ else
1609
+ columns = {
1610
+ "ID" => 'id',
1611
+ "Name" => 'name',
1612
+ "Type" => lambda {|it| format_host_vm_group_type(it['type']) },
1613
+ "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
1614
+ "Servers" => lambda {|it| (it['servers'] || []).size },
1615
+ }.upcase_keys!
1616
+ print as_pretty_table(rows, columns, options)
1617
+ print_results_pagination(json_response)
1618
+ end
1619
+ print reset, "\n"
1620
+ end
1621
+ return 0, nil
1622
+ end
1623
+
1624
+ def get_host_vm_group(args)
1625
+ options = {}
1626
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1627
+ opts.banner = subcommand_usage("[cloud] [host-vm-group]")
1628
+ build_standard_get_options(opts, options)
1629
+ opts.footer = "Get details about a cloud host/VM group.\n" +
1630
+ "[cloud] is required. This is the name or id of an existing cloud.\n" +
1631
+ "[host-vm-group] is required. This is the name or id of an existing host/VM group."
1632
+ end
1633
+ optparse.parse!(args)
1634
+ verify_args!(args:args, optparse:optparse, count:2)
1635
+ connect(options)
1636
+
1637
+ cloud = find_cloud_by_name_or_id(args[0])
1638
+ return 1 if cloud.nil?
1639
+ host_vm_group = find_cloud_host_vm_group_by_name_or_id(cloud['id'], args[1])
1640
+ if host_vm_group.nil?
1641
+ print_red_alert "Host/VM Group not found for '\#{args[1]}'"
1642
+ exit 1
1643
+ end
1644
+
1645
+ params = {}
1646
+ params.merge!(parse_query_options(options))
1647
+ @clouds_interface.setopts(options)
1648
+ if options[:dry_run]
1649
+ print_dry_run @clouds_interface.dry.get_host_vm_group(cloud['id'], host_vm_group['id'], params)
1650
+ return
1651
+ end
1652
+ json_response = @clouds_interface.get_host_vm_group(cloud['id'], host_vm_group['id'], params)
1653
+ render_response(json_response, options, 'hostVmGroup') do
1654
+ row = json_response['hostVmGroup']
1655
+ print_h1 "Host/VM Group Details", [], options
1656
+ columns = {
1657
+ "ID" => 'id',
1658
+ "Name" => 'name',
1659
+ "Type" => lambda {|it| format_host_vm_group_type(it['type']) },
1660
+ "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
1661
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
1662
+ "Servers" => lambda {|it| (it['servers'] || []).size },
1663
+ "Source" => 'source',
1664
+ }
1665
+ print_description_list(columns, row)
1666
+ if row['servers'] && row['servers'].size > 0
1667
+ print_h2 "Servers", options
1668
+ print as_pretty_table(row['servers'], [:id, :name], options)
1669
+ end
1670
+ print reset, "\n"
1671
+ end
1672
+ return 0, nil
1673
+ end
1674
+
1675
+ def add_host_vm_group(args)
1676
+ options = {}
1677
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1678
+ opts.banner = subcommand_usage("[cloud] [name] [options]")
1679
+ build_option_type_options(opts, options, add_cloud_host_vm_group_option_types)
1680
+ opts.on('--servers ID1,ID2', Array, "Server ids to add to the group") do |val|
1681
+ options[:options] ||= {}
1682
+ options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
1683
+ end
1684
+ add_perms_options(opts, options, ['plans'])
1685
+ build_standard_add_options(opts, options)
1686
+ opts.footer = "Add host/VM group to a cloud.\n" +
1687
+ "[cloud] is required. This is the name or id of an existing cloud.\n" +
1688
+ "[name] is required. This is the name of the new host/VM group."
1689
+ end
1690
+ optparse.parse!(args)
1691
+ verify_args!(args:args, optparse:optparse, min:1, max:2)
1692
+ connect(options)
1693
+
1694
+ begin
1695
+ cloud = find_cloud_by_name_or_id(args[0])
1696
+ return 1 if cloud.nil?
1697
+ if args[1]
1698
+ options[:options] ||= {}
1699
+ options[:options]['name'] = args[1]
1700
+ end
1701
+ if options[:payload]
1702
+ payload = options[:payload]
1703
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
1704
+ else
1705
+ options[:params] ||= {}
1706
+ options[:params].merge!({:cloudId => cloud['id'], :zoneId => cloud['id']})
1707
+ host_vm_group = Morpheus::Cli::OptionTypes.prompt(add_cloud_host_vm_group_option_types, options[:options], @api_client, options[:params])
1708
+
1709
+ unless options[:options] && options[:options].key?('servers')
1710
+ # poolId narrows availableServers to the chosen cluster: HOST_GROUP
1711
+ # matches the cluster pool exactly, VM_GROUP walks descendant pools.
1712
+ form_params = {'hostVmGroupType' => host_vm_group['type']}
1713
+ hvg_pool_id = host_vm_group['pool'].is_a?(Hash) ? host_vm_group['pool']['id'] : nil
1714
+ form_params['poolId'] = hvg_pool_id if hvg_pool_id
1715
+ form_opts = @clouds_interface.host_vm_group_form_options(cloud['id'], form_params) rescue {}
1716
+ available = form_opts['availableServers'] || []
1717
+ if available.empty?
1718
+ server_prompt = Morpheus::Cli::OptionTypes.prompt([{
1719
+ 'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead',
1720
+ 'optionSource' => 'searchServers', 'searchParameter' => 'phrase',
1721
+ 'description' => 'Select servers to add to the group.'
1722
+ }], options[:options], @api_client, options[:params])
1723
+ host_vm_group['servers'] = server_prompt['servers'] if server_prompt['servers']
1724
+ else
1725
+ print cyan, "Available Servers:", reset, "\n"
1726
+ available.each {|s| print " \#{s['id']}\t\#{s['name']}\n" }
1727
+ raw = Morpheus::Cli::OptionTypes.prompt([{
1728
+ 'fieldName' => 'servers', 'fieldLabel' => 'Servers (comma-separated IDs)', 'type' => 'text',
1729
+ 'description' => 'Server IDs to include, comma-separated'
1730
+ }], options[:options], @api_client, options[:params])['servers']
1731
+ selected_ids = raw.to_s.split(',').map(&:strip).reject(&:empty?).map(&:to_i)
1732
+ host_vm_group['servers'] = selected_ids.map {|id| {'id' => id}}
1733
+ end
1734
+ else
1735
+ host_vm_group['servers'] = options[:options]['servers']
1736
+ end
1737
+
1738
+ perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
1739
+ host_vm_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
1740
+ host_vm_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil? || perms['tenantPermissions']['accounts'].empty?
1741
+ host_vm_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
1742
+
1743
+ payload = {'hostVmGroup' => host_vm_group}
1744
+ end
1745
+
1746
+ @clouds_interface.setopts(options)
1747
+ if options[:dry_run]
1748
+ print_dry_run @clouds_interface.dry.create_host_vm_group(cloud['id'], payload)
1749
+ return
1750
+ end
1751
+ json_response = @clouds_interface.create_host_vm_group(cloud['id'], payload)
1752
+ render_response(json_response, options, 'hostVmGroup') do
1753
+ row = json_response['hostVmGroup']
1754
+ print_green_success "Added host/VM group to cloud \#{cloud['name']}"
1755
+ end
1756
+ return 0, nil
1757
+ rescue RestClient::Exception => e
1758
+ print_rest_exception(e, options)
1759
+ exit 1
1760
+ end
1761
+ end
1762
+
1763
+ def update_host_vm_group(args)
1764
+ options = {}
1765
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1766
+ opts.banner = subcommand_usage("[cloud] [host-vm-group] [options]")
1767
+ build_option_type_options(opts, options, update_cloud_host_vm_group_option_types)
1768
+ opts.on('--servers ID1,ID2', Array, "Server ids") do |val|
1769
+ options[:options] ||= {}
1770
+ options[:options]['servers'] = val.map {|s| {'id' => s.to_i}}
1771
+ end
1772
+ add_perms_options(opts, options, ['plans'])
1773
+ build_standard_update_options(opts, options)
1774
+ opts.footer = "Update a cloud host/VM group.\n" +
1775
+ "[cloud] is required. This is the name or id of an existing cloud.\n" +
1776
+ "[host-vm-group] is required. This is the name or id of an existing host/VM group."
1777
+ end
1778
+ optparse.parse!(args)
1779
+ verify_args!(args:args, optparse:optparse, count:2)
1780
+ connect(options)
1781
+
1782
+ begin
1783
+ cloud = find_cloud_by_name_or_id(args[0])
1784
+ return 1 if cloud.nil?
1785
+ host_vm_group = find_cloud_host_vm_group_by_name_or_id(cloud['id'], args[1])
1786
+ if host_vm_group.nil?
1787
+ print_red_alert "Host/VM Group not found for '\#{args[1]}'"
1788
+ return 1
1789
+ end
1790
+
1791
+ if options[:payload]
1792
+ payload = options[:payload]
1793
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
1794
+ else
1795
+ payload = {'hostVmGroup' => {}}
1796
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_cloud_host_vm_group_option_types, options[:options], @api_client, {})
1797
+ payload.deep_merge!({'hostVmGroup' => v_prompt})
1798
+
1799
+ perms = prompt_permissions(options, is_master_account ? ['plans'] : ['plans', 'visibility', 'tenants'])
1800
+ payload['hostVmGroup']['permissions'] = perms['resourcePermissions'] if perms['resourcePermissions']
1801
+ payload['hostVmGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
1802
+
1803
+ if options[:options]
1804
+ payload.deep_merge!({'hostVmGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) || payload['hostVmGroup'].key?(k) }})
1805
+ end
1806
+ if payload['hostVmGroup'].empty?
1807
+ raise_command_error "Specify at least one option to update.\n\#{optparse}"
1808
+ end
1809
+ end
1810
+
1811
+ @clouds_interface.setopts(options)
1812
+ if options[:dry_run]
1813
+ print_dry_run @clouds_interface.dry.update_host_vm_group(cloud['id'], host_vm_group['id'], payload)
1814
+ return
1815
+ end
1816
+ json_response = @clouds_interface.update_host_vm_group(cloud['id'], host_vm_group['id'], payload)
1817
+ render_response(json_response, options, 'hostVmGroup') do
1818
+ updated = json_response['hostVmGroup'] || host_vm_group
1819
+ print_green_success "Updated host/VM group \#{updated['name']}"
1820
+ end
1821
+ return 0, nil
1822
+ rescue RestClient::Exception => e
1823
+ print_rest_exception(e, options)
1824
+ exit 1
1825
+ end
1826
+ end
1827
+
1828
+ def remove_host_vm_group(args)
1829
+ options = {}
1830
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1831
+ opts.banner = subcommand_usage("[cloud] [host-vm-group]")
1832
+ build_standard_remove_options(opts, options)
1833
+ opts.footer = "Delete a host/VM group from a cloud.\n" +
1834
+ "[cloud] is required. This is the name or id of an existing cloud.\n" +
1835
+ "[host-vm-group] is required. This is the name or id of an existing host/VM group."
1836
+ end
1837
+ optparse.parse!(args)
1838
+ verify_args!(args:args, optparse:optparse, count:2)
1839
+ connect(options)
1840
+
1841
+ cloud = find_cloud_by_name_or_id(args[0])
1842
+ return 1 if cloud.nil?
1843
+ host_vm_group = find_cloud_host_vm_group_by_name_or_id(cloud['id'], args[1])
1844
+ if host_vm_group.nil?
1845
+ print_red_alert "Host/VM Group not found for '\#{args[1]}'"
1846
+ return 1
1847
+ end
1848
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes.confirm("Are you sure you would like to remove the cloud host/VM group '\#{host_vm_group['name'] || host_vm_group['id']}'?", options)
1849
+ return 9, "aborted command"
1850
+ end
1851
+
1852
+ @clouds_interface.setopts(options)
1853
+ if options[:dry_run]
1854
+ print_dry_run @clouds_interface.dry.destroy_host_vm_group(cloud['id'], host_vm_group['id'])
1855
+ return
1856
+ end
1857
+ begin
1858
+ json_response = @clouds_interface.destroy_host_vm_group(cloud['id'], host_vm_group['id'])
1859
+ render_response(json_response, options) do
1860
+ print_green_success "Removed host/VM group \#{host_vm_group['name']}"
1861
+ end
1862
+ return 0, nil
1863
+ rescue RestClient::Exception => e
1864
+ print_rest_exception(e, options)
1865
+ exit 1
1866
+ end
1867
+ end
1868
+
1869
+
1505
1870
  private
1506
1871
 
1507
1872
  def cloud_list_column_definitions(options)
@@ -1624,22 +1989,49 @@ EOT
1624
1989
  def add_cloud_affinity_group_option_types
1625
1990
  [
1626
1991
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
1627
- {'fieldName' => 'affinityType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Keep Separate', 'value' => 'KEEP_SEPARATE'}, {'name' => 'Keep Together', 'value' => 'KEEP_TOGETHER'}], 'description' => 'Choose affinity type.', 'required' => true, 'defaultValue' => 'KEEP_SEPARATE'},
1992
+ affinity_type_option_type(required: true, default: 'KEEP_SEPARATE'),
1628
1993
  {'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'defaultValue' => true},
1629
- {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware', 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
1630
- {'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers', 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
1994
+ {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware',
1995
+ 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
1996
+ {'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers',
1997
+ 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
1631
1998
  ]
1632
1999
  end
1633
2000
 
1634
2001
  def update_cloud_affinity_group_option_types
1635
2002
  [
2003
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text'},
2004
+ affinity_type_option_type,
1636
2005
  {'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox'},
1637
- {'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers', 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
2006
+ {'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiTypeahead', 'optionSource' => 'searchServers',
2007
+ 'searchParameter' => 'phrase', 'description' => 'Select servers to be in the affinity group.'},
1638
2008
  ]
1639
2009
  end
1640
2010
 
1641
- def format_affinity_type(affinity_type)
1642
- affinity_type == "KEEP_SEPARATE" ? "Keep Separate" : "Keep Together"
2011
+ def find_cloud_host_vm_group_by_name_or_id(cloud_id, val)
2012
+ if val.to_s =~ /\A\d{1,}\Z/
2013
+ @clouds_interface.get_host_vm_group(cloud_id, val)['hostVmGroup'] rescue nil
2014
+ else
2015
+ @clouds_interface.list_host_vm_groups(cloud_id, {name: val})['hostVmGroups'] && @clouds_interface.list_host_vm_groups(cloud_id, {name: val})['hostVmGroups'][0]
2016
+ end
2017
+ end
2018
+
2019
+ def add_cloud_host_vm_group_option_types
2020
+ [
2021
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
2022
+ host_vm_group_type_option_type(required: true, default: 'HOST_GROUP'),
2023
+ # A cloud-scoped host/VM group must belong to a single cluster --
2024
+ # HostVmGroupsController#save rejects servers that span clusters. Same
2025
+ # picker the cloud-scoped affinity group form uses.
2026
+ {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware',
2027
+ 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the host/VM group.', 'required' => true},
2028
+ ]
2029
+ end
2030
+
2031
+ def update_cloud_host_vm_group_option_types
2032
+ [
2033
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text'},
2034
+ ]
1643
2035
  end
1644
-
2036
+
1645
2037
  end