morpheus-cli 4.1.14 → 4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/library_container_types_interface.rb +1 -1
- data/lib/morpheus/api/library_instance_types_interface.rb +7 -7
- data/lib/morpheus/api/library_layouts_interface.rb +1 -1
- data/lib/morpheus/api/network_routers_interface.rb +101 -0
- data/lib/morpheus/api/tasks_interface.rb +12 -14
- data/lib/morpheus/cli.rb +1 -0
- data/lib/morpheus/cli/apps.rb +15 -12
- data/lib/morpheus/cli/cli_command.rb +40 -2
- data/lib/morpheus/cli/clusters.rb +13 -7
- data/lib/morpheus/cli/cypher_command.rb +5 -2
- data/lib/morpheus/cli/hosts.rb +1 -1
- data/lib/morpheus/cli/instances.rb +21 -5
- data/lib/morpheus/cli/jobs_command.rb +83 -27
- data/lib/morpheus/cli/library_cluster_layouts_command.rb +12 -12
- data/lib/morpheus/cli/library_container_scripts_command.rb +52 -40
- data/lib/morpheus/cli/library_container_types_command.rb +2 -60
- data/lib/morpheus/cli/library_instance_types_command.rb +22 -1
- data/lib/morpheus/cli/library_layouts_command.rb +65 -65
- data/lib/morpheus/cli/library_option_lists_command.rb +72 -59
- data/lib/morpheus/cli/library_option_types_command.rb +30 -186
- data/lib/morpheus/cli/library_spec_templates_command.rb +39 -64
- data/lib/morpheus/cli/mixins/library_helper.rb +213 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +89 -37
- data/lib/morpheus/cli/mixins/whoami_helper.rb +16 -1
- data/lib/morpheus/cli/network_routers_command.rb +1281 -0
- data/lib/morpheus/cli/networks_command.rb +164 -72
- data/lib/morpheus/cli/option_types.rb +187 -73
- data/lib/morpheus/cli/price_sets_command.rb +4 -4
- data/lib/morpheus/cli/prices_command.rb +15 -15
- data/lib/morpheus/cli/remote.rb +3 -3
- data/lib/morpheus/cli/service_plans_command.rb +17 -8
- data/lib/morpheus/cli/tasks.rb +437 -169
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +8 -0
- metadata +6 -3
@@ -6,6 +6,7 @@ require 'morpheus/cli/mixins/infrastructure_helper'
|
|
6
6
|
|
7
7
|
class Morpheus::Cli::NetworksCommand
|
8
8
|
include Morpheus::Cli::CliCommand
|
9
|
+
include Morpheus::Cli::WhoamiHelper
|
9
10
|
include Morpheus::Cli::InfrastructureHelper
|
10
11
|
|
11
12
|
set_command_name :networks
|
@@ -26,6 +27,7 @@ class Morpheus::Cli::NetworksCommand
|
|
26
27
|
@network_types_interface = @api_client.network_types
|
27
28
|
@subnets_interface = @api_client.subnets
|
28
29
|
@subnet_types_interface = @api_client.subnet_types
|
30
|
+
@groups_interface = @api_client.groups
|
29
31
|
@clouds_interface = @api_client.clouds
|
30
32
|
@options_interface = @api_client.options
|
31
33
|
end
|
@@ -97,6 +99,7 @@ class Morpheus::Cli::NetworksCommand
|
|
97
99
|
id: network['id'],
|
98
100
|
name: network['name'],
|
99
101
|
type: network['type'] ? network['type']['name'] : '',
|
102
|
+
group: network['group'] ? network['group']['name'] : 'Shared',
|
100
103
|
cloud: network['zone'] ? network['zone']['name'] : '',
|
101
104
|
cidr: network['cidr'],
|
102
105
|
pool: network['pool'] ? network['pool']['name'] : '',
|
@@ -114,6 +117,7 @@ class Morpheus::Cli::NetworksCommand
|
|
114
117
|
name: " #{subnet['name']}",
|
115
118
|
# type: subnet['type'] ? subnet['type']['name'] : '',
|
116
119
|
type: "Subnet",
|
120
|
+
group: network['group'] ? network['group']['name'] : 'Shared',
|
117
121
|
cloud: network['zone'] ? network['zone']['name'] : '',
|
118
122
|
cidr: subnet['cidr'],
|
119
123
|
pool: subnet['pool'] ? subnet['pool']['name'] : '',
|
@@ -126,7 +130,7 @@ class Morpheus::Cli::NetworksCommand
|
|
126
130
|
end
|
127
131
|
end
|
128
132
|
end
|
129
|
-
columns = [:id, :name, :type, :cloud, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
|
133
|
+
columns = [:id, :name, :type, :group, :cloud, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
|
130
134
|
if options[:include_fields]
|
131
135
|
columns = options[:include_fields]
|
132
136
|
end
|
@@ -190,6 +194,7 @@ class Morpheus::Cli::NetworksCommand
|
|
190
194
|
"Name" => 'name',
|
191
195
|
"Description" => 'description',
|
192
196
|
"Type" => lambda {|it| it['type'] ? it['type']['name'] : '' },
|
197
|
+
"Group" => lambda {|it| it['group'] ? it['group']['name'] : 'Shared' },
|
193
198
|
"Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
|
194
199
|
"CIDR" => 'cidr',
|
195
200
|
"Gateway" => 'gateway',
|
@@ -207,7 +212,9 @@ class Morpheus::Cli::NetworksCommand
|
|
207
212
|
}
|
208
213
|
print_description_list(description_cols, network)
|
209
214
|
|
210
|
-
if network[
|
215
|
+
if network["group"]
|
216
|
+
# Group Access is n/a unless network is_a? Shared (no group)
|
217
|
+
elsif network['resourcePermission'].nil?
|
211
218
|
print "\n", "No group access found", "\n"
|
212
219
|
else
|
213
220
|
print_h2 "Group Access"
|
@@ -272,6 +279,9 @@ class Morpheus::Cli::NetworksCommand
|
|
272
279
|
group_defaults_list = nil
|
273
280
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
274
281
|
opts.banner = subcommand_usage("-t TYPE")
|
282
|
+
opts.on( '-g', '--group GROUP', "Group Name or ID. Default is Shared." ) do |val|
|
283
|
+
options[:group] = val
|
284
|
+
end
|
275
285
|
opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
|
276
286
|
options[:cloud] = val
|
277
287
|
end
|
@@ -411,19 +421,79 @@ class Morpheus::Cli::NetworksCommand
|
|
411
421
|
payload['network']['description'] = v_prompt['description']
|
412
422
|
end
|
413
423
|
|
424
|
+
# Group
|
425
|
+
# ok, networks list needs to know if they have full or groups permission
|
426
|
+
group = nil
|
427
|
+
groups_dropdown = nil
|
428
|
+
group_is_required = true
|
429
|
+
network_perm = (current_user_permissions || []).find {|perm| perm['code'] == 'infrastructure-networks'}
|
430
|
+
if network_perm && ['full','read'].include?(network_perm['access'])
|
431
|
+
group_is_required = false
|
432
|
+
groups_dropdown = get_available_groups_with_shared
|
433
|
+
else
|
434
|
+
# they have group access, shared cannot be selected.
|
435
|
+
groups_dropdown = get_available_groups
|
436
|
+
end
|
437
|
+
if options[:group]
|
438
|
+
group_id = options[:group]
|
439
|
+
group = groups_dropdown.find {|it| it["value"].to_s == group_id.to_s || it["name"].to_s == group_id}
|
440
|
+
if group.nil?
|
441
|
+
print_red_alert "Group not found by id #{group_id}"
|
442
|
+
return 1
|
443
|
+
end
|
444
|
+
if group_id.to_s == 'shared'
|
445
|
+
group_id = nil
|
446
|
+
group = nil
|
447
|
+
end
|
448
|
+
else
|
449
|
+
group_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => groups_dropdown, 'required' => group_is_required, 'description' => 'Select Group.'}],options,@api_client,{})
|
450
|
+
group_id = group_prompt['group']
|
451
|
+
if group_id.to_s == '' || group_id.to_s == 'shared'
|
452
|
+
group_id = nil
|
453
|
+
group = nil
|
454
|
+
else
|
455
|
+
group = groups_dropdown.find {|it| it["value"].to_s == group_id.to_s || it["name"].to_s == group_id}
|
456
|
+
if group.nil?
|
457
|
+
print_red_alert "Group not found by id #{group_id}"
|
458
|
+
return 1
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
if group
|
463
|
+
payload['network']['site'] = {'id' => group['id']}
|
464
|
+
else
|
465
|
+
# shared
|
466
|
+
end
|
467
|
+
|
414
468
|
# Cloud
|
415
469
|
cloud = nil
|
416
|
-
if
|
417
|
-
|
418
|
-
|
419
|
-
|
470
|
+
if group
|
471
|
+
if options[:cloud]
|
472
|
+
cloud_id = options[:cloud]
|
473
|
+
cloud = group["clouds"].find {|it| it["id"].to_s == cloud_id.to_s || it["name"].to_s == cloud_id}
|
474
|
+
if cloud.nil?
|
475
|
+
print_red_alert "Cloud not found by id #{cloud_id}"
|
476
|
+
return 1
|
477
|
+
end
|
478
|
+
else
|
479
|
+
api_params = {groupId:group['id']}
|
480
|
+
cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'cloudsForNetworks', 'required' => true, 'description' => 'Select Cloud.'}],options,@api_client,api_params)
|
481
|
+
cloud_id = cloud_prompt['cloud']
|
482
|
+
cloud = find_cloud_by_name_or_id(cloud_id) if cloud_id
|
483
|
+
return 1 if cloud.nil?
|
484
|
+
end
|
420
485
|
else
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
486
|
+
if options[:cloud]
|
487
|
+
cloud = find_cloud_by_name_or_id(options[:cloud])
|
488
|
+
# meh, should validate cloud is in the cloudsForNetworks dropdown..
|
489
|
+
return 1 if cloud.nil?
|
490
|
+
else
|
491
|
+
api_params = {}
|
492
|
+
cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'cloudsForNetworks', 'required' => true, 'description' => 'Select Cloud.'}],options,@api_client,api_params)
|
493
|
+
cloud_id = cloud_prompt['cloud']
|
494
|
+
cloud = find_cloud_by_name_or_id(cloud_id) if cloud_id
|
495
|
+
return 1 if cloud.nil?
|
496
|
+
end
|
427
497
|
end
|
428
498
|
payload['network']['zone'] = {'id' => cloud['id']}
|
429
499
|
|
@@ -446,83 +516,84 @@ class Morpheus::Cli::NetworksCommand
|
|
446
516
|
print_red_alert "Network Type not found by id '#{network_type_id}'"
|
447
517
|
return 1
|
448
518
|
end
|
449
|
-
network_type_option_types = network_type['optionTypes']
|
450
|
-
if network_type_option_types && network_type_option_types.size > 0
|
451
|
-
# prompt for option types
|
452
|
-
# JD: 3.6.2 has fieldContext: 'domain' , which is wrong
|
453
|
-
network_type_option_types.each do |option_type|
|
454
|
-
# if option_type['fieldContext'] == 'domain'
|
455
|
-
# option_type['fieldContext'] = 'network'
|
456
|
-
# end
|
457
|
-
#option_type['fieldContext'] = nil
|
458
|
-
end
|
459
|
-
network_type_params = Morpheus::Cli::OptionTypes.prompt(network_type_option_types,options[:options],@api_client, {zoneId: cloud['id']})
|
460
|
-
# network context options belong at network level and not network.network
|
461
|
-
network_context_params = network_type_params.delete('network')
|
462
|
-
payload['network'].deep_merge!(network_context_params) if network_context_params
|
463
|
-
payload['network'].deep_merge!(network_type_params)
|
464
|
-
|
465
|
-
#todo: special handling of type: 'aciVxlan'
|
466
519
|
|
520
|
+
# CIDR
|
521
|
+
if options['cidr']
|
522
|
+
payload['network']['cidr'] = options['cidr']
|
467
523
|
else
|
468
|
-
#
|
524
|
+
#if network_type['cidrEditable']
|
525
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cidr', 'fieldLabel' => 'CIDR', 'type' => 'text', 'required' => network_type['cidrRequired'], 'description' => ''}], options)
|
526
|
+
payload['network']['cidr'] = v_prompt['cidr']
|
527
|
+
#end
|
528
|
+
end
|
469
529
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
530
|
+
# Gateway
|
531
|
+
if options['gateway']
|
532
|
+
payload['network']['gateway'] = options['gateway']
|
533
|
+
else
|
534
|
+
if network_type['gatewayEditable']
|
474
535
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'gateway', 'fieldLabel' => 'Gateway', 'type' => 'text', 'required' => false, 'description' => ''}], options)
|
475
536
|
payload['network']['gateway'] = v_prompt['gateway']
|
476
537
|
end
|
538
|
+
end
|
477
539
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
540
|
+
# DNS Primary
|
541
|
+
if options['dnsPrimary']
|
542
|
+
payload['network']['dnsPrimary'] = options['dnsPrimary']
|
543
|
+
else
|
544
|
+
if network_type['dnsEditable']
|
482
545
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dnsPrimary', 'fieldLabel' => 'DNS Primary', 'type' => 'text', 'required' => false, 'description' => ''}], options)
|
483
546
|
payload['network']['dnsPrimary'] = v_prompt['dnsPrimary']
|
484
547
|
end
|
548
|
+
end
|
485
549
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
550
|
+
# DNS Secondary
|
551
|
+
if options['dnsSecondary']
|
552
|
+
payload['network']['dnsSecondary'] = options['dnsSecondary']
|
553
|
+
else
|
554
|
+
if network_type['dnsEditable']
|
490
555
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dnsSecondary', 'fieldLabel' => 'DNS Secondary', 'type' => 'text', 'required' => false, 'description' => ''}], options)
|
491
556
|
payload['network']['dnsSecondary'] = v_prompt['dnsSecondary']
|
492
557
|
end
|
558
|
+
end
|
493
559
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
payload['network']['cidr'] = v_prompt['cidr']
|
500
|
-
end
|
501
|
-
|
502
|
-
# VLAN ID
|
503
|
-
if options['vlanId']
|
504
|
-
payload['network']['vlanId'] = options['vlanId']
|
505
|
-
else
|
560
|
+
# VLAN ID
|
561
|
+
if options['vlanId']
|
562
|
+
payload['network']['vlanId'] = options['vlanId']
|
563
|
+
else
|
564
|
+
if network_type['vlanEditable']
|
506
565
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'vlanId', 'fieldLabel' => 'VLAN ID', 'type' => 'number', 'required' => false, 'description' => ''}], options)
|
507
566
|
payload['network']['vlanId'] = v_prompt['vlanId']
|
508
567
|
end
|
568
|
+
end
|
509
569
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
570
|
+
# prompt for option types
|
571
|
+
network_type_option_types = network_type['optionTypes']
|
572
|
+
if network_type_option_types && network_type_option_types.size > 0
|
573
|
+
network_type_params = Morpheus::Cli::OptionTypes.prompt(network_type_option_types,options[:options],@api_client, {zoneId: cloud['id']})
|
574
|
+
# network context options belong at network level and not network.network
|
575
|
+
network_context_params = network_type_params.delete('network')
|
576
|
+
payload['network'].deep_merge!(network_context_params) if network_context_params
|
577
|
+
payload['network'].deep_merge!(network_type_params)
|
578
|
+
|
579
|
+
end
|
580
|
+
|
581
|
+
# DHCP Server
|
582
|
+
if options['dhcpServer'] != nil
|
583
|
+
payload['network']['dhcpServer'] = options['dhcpServer']
|
584
|
+
else
|
585
|
+
if network_type['dhcpServerEditable']
|
514
586
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dhcpServer', 'fieldLabel' => 'DHCP Server', 'type' => 'checkbox', 'required' => false, 'description' => ''}], options)
|
515
587
|
payload['network']['dhcpServer'] = v_prompt['dhcpServer']
|
516
588
|
end
|
589
|
+
end
|
517
590
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
end
|
525
|
-
|
591
|
+
# Allow IP Override
|
592
|
+
if options['allowStaticOverride'] != nil
|
593
|
+
payload['network']['allowStaticOverride'] = options['allowStaticOverride']
|
594
|
+
else
|
595
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'allowStaticOverride', 'fieldLabel' => 'Allow IP Override', 'type' => 'checkbox', 'required' => false, 'description' => ''}], options)
|
596
|
+
payload['network']['allowStaticOverride'] = v_prompt['allowStaticOverride']
|
526
597
|
end
|
527
598
|
|
528
599
|
## IPAM Options
|
@@ -531,10 +602,12 @@ class Morpheus::Cli::NetworksCommand
|
|
531
602
|
if options['pool']
|
532
603
|
payload['network']['pool'] = options['pool'].to_i
|
533
604
|
else
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
605
|
+
if network_type['canAssignPool']
|
606
|
+
# todo: select dropdown
|
607
|
+
# v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'select', 'optionSource' => 'networkPools', 'required' => false, 'description' => ''}], options, @api_client, {zoneId: cloud['id']})
|
608
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'text', 'required' => false, 'description' => ''}], options)
|
609
|
+
payload['network']['pool'] = v_prompt['pool'].to_i if v_prompt['pool']
|
610
|
+
end
|
538
611
|
end
|
539
612
|
|
540
613
|
## Advanced Options
|
@@ -976,9 +1049,13 @@ class Morpheus::Cli::NetworksCommand
|
|
976
1049
|
|
977
1050
|
def remove(args)
|
978
1051
|
options = {}
|
1052
|
+
params = {}
|
979
1053
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
980
1054
|
opts.banner = subcommand_usage("[network]")
|
981
1055
|
build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :remote])
|
1056
|
+
opts.on( '-f', '--force', "Force Delete" ) do
|
1057
|
+
params[:force] = 'true'
|
1058
|
+
end
|
982
1059
|
opts.footer = "Delete a network." + "\n" +
|
983
1060
|
"[network] is required. This is the name or id of a network."
|
984
1061
|
end
|
@@ -1000,10 +1077,10 @@ class Morpheus::Cli::NetworksCommand
|
|
1000
1077
|
end
|
1001
1078
|
@networks_interface.setopts(options)
|
1002
1079
|
if options[:dry_run]
|
1003
|
-
print_dry_run @networks_interface.dry.destroy(network['id'])
|
1080
|
+
print_dry_run @networks_interface.dry.destroy(network['id'], params)
|
1004
1081
|
return 0
|
1005
1082
|
end
|
1006
|
-
json_response = @networks_interface.destroy(network['id'])
|
1083
|
+
json_response = @networks_interface.destroy(network['id'], params)
|
1007
1084
|
if options[:json]
|
1008
1085
|
print JSON.pretty_generate(json_response)
|
1009
1086
|
print "\n"
|
@@ -1223,4 +1300,19 @@ class Morpheus::Cli::NetworksCommand
|
|
1223
1300
|
|
1224
1301
|
private
|
1225
1302
|
|
1303
|
+
def get_available_groups(refresh=false)
|
1304
|
+
if !@available_groups || refresh
|
1305
|
+
option_results = @options_interface.options_for_source('groups',{})
|
1306
|
+
@available_groups = option_results['data'].collect {|it|
|
1307
|
+
{"id" => it["value"], "name" => it["name"], "value" => it["value"]}
|
1308
|
+
}
|
1309
|
+
end
|
1310
|
+
#puts "get_available_groups() rtn: #{@available_groups.inspect}"
|
1311
|
+
return @available_groups
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
def get_available_groups_with_shared(refresh=false)
|
1315
|
+
[{"id" => 'shared', "name" => 'Shared', "value" => 'shared'}] + get_available_groups(refresh)
|
1316
|
+
end
|
1317
|
+
|
1226
1318
|
end
|
@@ -39,13 +39,18 @@ module Morpheus
|
|
39
39
|
paging_enabled = false if Morpheus::Cli.windows?
|
40
40
|
results = {}
|
41
41
|
options = options || {}
|
42
|
+
# inject cli only stuff into option_types (should clone() here)
|
43
|
+
option_types.each do |option_type|
|
44
|
+
if options[:help_field_prefix]
|
45
|
+
option_type[:help_field_prefix] = options[:help_field_prefix]
|
46
|
+
end
|
47
|
+
end
|
42
48
|
# puts "Options Prompt #{options}"
|
43
49
|
option_types.sort { |x,y| x['displayOrder'].to_i <=> y['displayOrder'].to_i }.each do |option_type|
|
44
50
|
context_map = results
|
45
51
|
value = nil
|
46
52
|
value_found=false
|
47
53
|
|
48
|
-
|
49
54
|
# How about this instead?
|
50
55
|
# option_type = option_type.clone
|
51
56
|
# field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
@@ -56,7 +61,10 @@ module Morpheus
|
|
56
61
|
# end
|
57
62
|
# end
|
58
63
|
|
64
|
+
# allow for mapping of domain to relevant type: domain.zone => router.zone
|
65
|
+
option_type['fieldContext'] = (options[:context_map] || {})[option_type['fieldContext']] || option_type['fieldContext']
|
59
66
|
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
67
|
+
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
60
68
|
namespaces = field_key.split(".")
|
61
69
|
field_name = namespaces.pop
|
62
70
|
|
@@ -78,33 +86,38 @@ module Morpheus
|
|
78
86
|
end
|
79
87
|
end
|
80
88
|
|
81
|
-
|
82
89
|
cur_namespace = options
|
90
|
+
parent_context_map = context_map
|
91
|
+
parent_ns = field_name
|
83
92
|
|
84
93
|
namespaces.each do |ns|
|
85
94
|
next if ns.empty?
|
95
|
+
parent_context_map = context_map
|
96
|
+
parent_ns = ns
|
86
97
|
cur_namespace[ns.to_s] ||= {}
|
87
98
|
cur_namespace = cur_namespace[ns.to_s]
|
88
99
|
context_map[ns.to_s] ||= {}
|
89
100
|
context_map = context_map[ns.to_s]
|
90
101
|
end
|
102
|
+
|
91
103
|
# use the value passed in the options map
|
92
|
-
if cur_namespace.key?(field_name)
|
104
|
+
if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
|
93
105
|
value = cur_namespace[field_name]
|
106
|
+
input_value = ['select', 'multiSelect'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
|
94
107
|
if option_type['type'] == 'number'
|
95
108
|
value = value.to_s.include?('.') ? value.to_f : value.to_i
|
96
|
-
elsif option_type['type']
|
109
|
+
elsif ['select', 'multiSelect'].include?(option_type['type'])
|
97
110
|
# this should just fall down through below, with the extra params no_prompt, use_value
|
98
|
-
value = select_prompt(option_type.merge({'defaultValue' => value}), api_client, (api_params || {}).merge(results), true)
|
111
|
+
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (api_params || {}).merge(results), true)
|
99
112
|
end
|
100
113
|
if options[:always_prompt] != true
|
101
114
|
value_found = true
|
102
115
|
end
|
103
116
|
end
|
104
|
-
|
105
|
-
# set the value that has been passed to the option type default value
|
117
|
+
|
118
|
+
# set the value that has been passed to the option type default value
|
106
119
|
if value != nil # && value != ''
|
107
|
-
option_type = option_type.clone
|
120
|
+
option_type = option_type.clone
|
108
121
|
option_type['defaultValue'] = value
|
109
122
|
end
|
110
123
|
# no_prompt means skip prompting and instead
|
@@ -119,14 +132,14 @@ module Morpheus
|
|
119
132
|
if !value_found
|
120
133
|
# select type is special because it supports skipSingleOption
|
121
134
|
# and prints the available options on error
|
122
|
-
if option_type['type']
|
123
|
-
value = select_prompt(option_type
|
135
|
+
if ['select', 'multiSelect'].include?(option_type['type'])
|
136
|
+
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), true)
|
124
137
|
value_found = !!value
|
125
138
|
end
|
126
139
|
if !value_found
|
127
140
|
if option_type['required']
|
128
141
|
print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
|
129
|
-
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{
|
142
|
+
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
130
143
|
print "\n"
|
131
144
|
exit 1
|
132
145
|
else
|
@@ -150,7 +163,7 @@ module Morpheus
|
|
150
163
|
value = multiline_prompt(option_type)
|
151
164
|
elsif option_type['type'] == 'code-editor'
|
152
165
|
value = multiline_prompt(option_type)
|
153
|
-
elsif option_type['type']
|
166
|
+
elsif ['select', 'multiSelect'].include?(option_type['type'])
|
154
167
|
# so, the /api/options/source is may need ALL the previously
|
155
168
|
# selected values that are being accumulated in options
|
156
169
|
# api_params is just extra params to always send
|
@@ -158,70 +171,87 @@ module Morpheus
|
|
158
171
|
# api_params = api_params.merge(options) # this might be good enough
|
159
172
|
# dup it
|
160
173
|
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
174
|
+
if value && option_type['type'] == 'multiSelect'
|
175
|
+
value = [value]
|
176
|
+
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
|
177
|
+
if addn_value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
|
178
|
+
value << addn_value
|
179
|
+
else
|
180
|
+
break
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
elsif option_type['type'] == 'hidden'
|
185
|
+
value = option_type['defaultValue']
|
186
|
+
input = value
|
187
|
+
elsif option_type['type'] == 'file'
|
188
|
+
value = file_prompt(option_type)
|
189
|
+
elsif option_type['type'] == 'file-content'
|
190
|
+
value = file_content_prompt(option_type, options, api_client, {})
|
191
|
+
else
|
192
|
+
value = generic_prompt(option_type)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
if option_type['type'] == 'multiSelect'
|
197
|
+
value = [value] if !value.nil? && !value.is_a?(Array)
|
198
|
+
parent_context_map[parent_ns] = value
|
166
199
|
else
|
167
|
-
|
200
|
+
context_map[field_name] = value
|
168
201
|
end
|
169
202
|
end
|
170
|
-
|
203
|
+
results
|
171
204
|
end
|
172
205
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
data.each do |k,v|
|
179
|
-
if v.is_a?(Hash)
|
180
|
-
params.merge!(grails_params(v, context ? "#{context}.#{k.to_s}" : k))
|
181
|
-
else
|
182
|
-
if context
|
183
|
-
params["#{context}.#{k.to_s}"] = v
|
206
|
+
def self.grails_params(data, context=nil)
|
207
|
+
params = {}
|
208
|
+
data.each do |k,v|
|
209
|
+
if v.is_a?(Hash)
|
210
|
+
params.merge!(grails_params(v, context ? "#{context}.#{k.to_s}" : k))
|
184
211
|
else
|
185
|
-
|
212
|
+
if context
|
213
|
+
params["#{context}.#{k.to_s}"] = v
|
214
|
+
else
|
215
|
+
params[k.to_s] = v
|
216
|
+
end
|
186
217
|
end
|
187
218
|
end
|
219
|
+
return params
|
188
220
|
end
|
189
|
-
return params
|
190
|
-
end
|
191
221
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
end
|
201
|
-
optionString = options.collect{ |b| b[:checked] ? "(#{b[:key]})" : b[:key]}.join(', ')
|
202
|
-
while !value_found do
|
203
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
|
204
|
-
input = $stdin.gets.chomp!
|
205
|
-
if input == '?'
|
206
|
-
help_prompt(option_type)
|
207
|
-
else
|
208
|
-
if input.nil? || input.empty?
|
209
|
-
selectedOption = options.find{|o| o[:checked] == true}
|
210
|
-
else
|
211
|
-
selectedOption = options.find{|o| o[:key].downcase == input.downcase}
|
222
|
+
def self.radio_prompt(option_type)
|
223
|
+
value_found = false
|
224
|
+
value = nil
|
225
|
+
options = []
|
226
|
+
if option_type['config'] and option_type['config']['radioOptions']
|
227
|
+
option_type['config']['radioOptions'].each do |radio_option|
|
228
|
+
options << {key: radio_option['key'], checked: radio_option['checked']}
|
212
229
|
end
|
213
|
-
|
214
|
-
|
230
|
+
end
|
231
|
+
optionString = options.collect{ |b| b[:checked] ? "(#{b[:key]})" : b[:key]}.join(', ')
|
232
|
+
while !value_found do
|
233
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
|
234
|
+
input = $stdin.gets.chomp!
|
235
|
+
if input == '?'
|
236
|
+
help_prompt(option_type)
|
215
237
|
else
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
238
|
+
if input.nil? || input.empty?
|
239
|
+
selectedOption = options.find{|o| o[:checked] == true}
|
240
|
+
else
|
241
|
+
selectedOption = options.find{|o| o[:key].downcase == input.downcase}
|
242
|
+
end
|
243
|
+
if selectedOption
|
244
|
+
value = selectedOption[:key]
|
245
|
+
else
|
246
|
+
puts "Invalid Option. Please select from #{optionString}."
|
247
|
+
end
|
248
|
+
if !value.nil? || option_type['required'] != true
|
249
|
+
value_found = true
|
250
|
+
end
|
220
251
|
end
|
221
252
|
end
|
253
|
+
return value
|
222
254
|
end
|
223
|
-
return value
|
224
|
-
end
|
225
255
|
|
226
256
|
def self.number_prompt(option_type)
|
227
257
|
value_found = false
|
@@ -251,10 +281,13 @@ module Morpheus
|
|
251
281
|
|
252
282
|
def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
|
253
283
|
paging_enabled = false if Morpheus::Cli.windows?
|
284
|
+
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
285
|
+
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
254
286
|
value_found = false
|
255
287
|
value = nil
|
256
288
|
value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
|
257
289
|
default_value = option_type['defaultValue']
|
290
|
+
default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
|
258
291
|
# local array of options
|
259
292
|
if option_type['selectOptions']
|
260
293
|
# calculate from inline lambda
|
@@ -330,7 +363,7 @@ module Morpheus
|
|
330
363
|
value = select_options[0][value_field]
|
331
364
|
elsif option_type['required']
|
332
365
|
print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
|
333
|
-
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{
|
366
|
+
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
334
367
|
if select_options && select_options.size > 10
|
335
368
|
display_select_options(option_type, select_options.first(10))
|
336
369
|
puts " (#{select_options.size-1} more)"
|
@@ -395,7 +428,12 @@ module Morpheus
|
|
395
428
|
value_found = true
|
396
429
|
end
|
397
430
|
end
|
398
|
-
|
431
|
+
|
432
|
+
# wrap in object when using fieldInput
|
433
|
+
if value && !option_type['fieldInput'].nil?
|
434
|
+
value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
|
435
|
+
end
|
436
|
+
value
|
399
437
|
end
|
400
438
|
|
401
439
|
# this is a funky one, the user is prompted for yes/no
|
@@ -436,6 +474,27 @@ module Morpheus
|
|
436
474
|
return value
|
437
475
|
end
|
438
476
|
|
477
|
+
def self.field_input_prompt(option_type)
|
478
|
+
value_found = false
|
479
|
+
value = nil
|
480
|
+
|
481
|
+
input_field_label = option_type['fieldInput'].gsub(/[A-Z]/, ' \0').split(' ').collect {|it| it.capitalize}.join(' ')
|
482
|
+
input_field_name = option_type['fieldName'].split('.').reverse.drop(1).reverse.push(option_type['fieldInput']).join('.')
|
483
|
+
input_option_type = option_type.merge({'fieldName' => input_field_name, 'fieldLabel' => input_field_label, 'required' => true, 'type' => 'text'})
|
484
|
+
|
485
|
+
while !value_found do
|
486
|
+
print "#{input_field_label}#{option_type['defaultInputValue'] ? " [#{option_type['defaultInputValue']}]" : ''}: "
|
487
|
+
input = $stdin.gets.chomp!
|
488
|
+
value = input.empty? ? option_type['defaultInputValue'] : input
|
489
|
+
if input == '?'
|
490
|
+
help_prompt(input_option_type)
|
491
|
+
elsif !value.nil?
|
492
|
+
value_found = true
|
493
|
+
end
|
494
|
+
end
|
495
|
+
return value
|
496
|
+
end
|
497
|
+
|
439
498
|
def self.generic_prompt(option_type)
|
440
499
|
value_found = false
|
441
500
|
value = nil
|
@@ -528,14 +587,62 @@ module Morpheus
|
|
528
587
|
return value
|
529
588
|
end
|
530
589
|
|
590
|
+
# file_content_prompt() prompts for source (local,repository,url) and then content or repo or.
|
591
|
+
# returns a Hash like {sourceType:"local",content:"yadda",contentPath:null,contentRef:null}
|
592
|
+
def self.file_content_prompt(option_type, options={}, api_client=nil, api_params={})
|
593
|
+
file_params = {}
|
594
|
+
options ||= {}
|
595
|
+
full_field_key = option_type['fieldContext'] ? "#{option_type['fieldContext']}.#{option_type['fieldName']}" : "#{option_type['fieldName']}"
|
596
|
+
passed_file_params = get_object_value(options, full_field_key)
|
597
|
+
if passed_file_params.is_a?(Hash)
|
598
|
+
file_params = passed_file_params
|
599
|
+
end
|
600
|
+
is_required = option_type['required']
|
601
|
+
if file_params['source']
|
602
|
+
file_params['sourceType'] = file_params.delete('source')
|
603
|
+
end
|
604
|
+
source_type = file_params['sourceType']
|
605
|
+
# source
|
606
|
+
if source_type.nil?
|
607
|
+
source_type = select_prompt({'fieldContext' => full_field_key, 'fieldName' => 'source', 'fieldLabel' => 'Source', 'type' => 'select', 'optionSource' => 'fileContentSource', 'required' => is_required, 'defaultValue' => (is_required ? 'local' : nil)}, api_client, {}, options[:no_prompt])
|
608
|
+
file_params['sourceType'] = source_type
|
609
|
+
end
|
610
|
+
# source type options
|
611
|
+
if source_type == "local"
|
612
|
+
# prompt for content
|
613
|
+
if file_params['content'].nil?
|
614
|
+
file_params['content'] = multiline_prompt({'fieldContext' => full_field_key, 'fieldName' => 'content', 'type' => 'code-editor', 'fieldLabel' => 'Content', 'required' => true})
|
615
|
+
end
|
616
|
+
elsif source_type == "url"
|
617
|
+
if file_params['url']
|
618
|
+
file_params['contentPath'] = file_params.delete('url')
|
619
|
+
end
|
620
|
+
if file_params['contentPath'].nil?
|
621
|
+
file_params['contentPath'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'url', 'fieldLabel' => 'URL', 'type' => 'text', 'required' => true})
|
622
|
+
end
|
623
|
+
elsif source_type == "repository"
|
624
|
+
if file_params['repository'].nil?
|
625
|
+
repository_id = select_prompt({'fieldContext' => full_field_key, 'fieldName' => 'repositoryId', 'fieldLabel' => 'Repository', 'type' => 'select', 'optionSource' => 'codeRepositories', 'required' => true}, api_client, {}, options[:no_prompt])
|
626
|
+
file_params['repository'] = {'id' => repository_id}
|
627
|
+
end
|
628
|
+
if file_params['contentPath'].nil?
|
629
|
+
file_params['contentPath'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'path', 'fieldLabel' => 'File Path', 'type' => 'text', 'required' => true})
|
630
|
+
end
|
631
|
+
if file_params['contentRef'].nil?
|
632
|
+
file_params['contentRef'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'ref', 'fieldLabel' => 'Version Ref', 'type' => 'text'})
|
633
|
+
end
|
634
|
+
end
|
635
|
+
return file_params
|
636
|
+
end
|
637
|
+
|
531
638
|
def self.help_prompt(option_type)
|
532
|
-
|
533
|
-
|
639
|
+
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
640
|
+
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
534
641
|
# an attempt at prompting help for natural options without the -O switch
|
535
642
|
if option_type[:fmt] == :natural
|
536
|
-
print Term::ANSIColor.green," * #{option_type['fieldLabel']} [--#{
|
643
|
+
print Term::ANSIColor.green," * #{option_type['fieldLabel']} [--#{help_field_key}=] ", Term::ANSIColor.reset , "#{option_type['description']}\n"
|
537
644
|
else
|
538
|
-
print Term::ANSIColor.green," * #{option_type['fieldLabel']} [-O #{
|
645
|
+
print Term::ANSIColor.green," * #{option_type['fieldLabel']} [-O #{help_field_key}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
|
539
646
|
end
|
540
647
|
end
|
541
648
|
|
@@ -559,17 +666,24 @@ module Morpheus
|
|
559
666
|
end
|
560
667
|
end
|
561
668
|
|
562
|
-
def self.format_option_types_help(option_types)
|
669
|
+
def self.format_option_types_help(option_types, opts={})
|
563
670
|
if option_types.empty?
|
564
|
-
"Available Options
|
671
|
+
"#{opts[:color]}#{opts[:title] || "Available Options:"}\nNone\n\n"
|
565
672
|
else
|
566
|
-
|
567
|
-
|
673
|
+
if opts[:include_context]
|
674
|
+
option_lines = option_types.sort {|it| it['displayOrder']}.collect {|it|
|
675
|
+
field_context = (opts[:context_map] || {})[it['fieldContext']] || it['fieldContext']
|
676
|
+
" -O #{field_context && field_context != '' ? "#{field_context}." : ''}#{it['fieldName']}=\"value\""
|
677
|
+
}
|
678
|
+
else
|
679
|
+
option_lines = option_types.sort {|it| it['displayOrder']}.collect {|it| " -O #{it['fieldName']}=\"value\"" }
|
680
|
+
end
|
681
|
+
"#{opts[:color]}#{opts[:title] || "Available Options:"}\n#{option_lines.join("\n")}\n\n"
|
568
682
|
end
|
569
683
|
end
|
570
684
|
|
571
|
-
def self.display_option_types_help(option_types)
|
572
|
-
puts self.format_option_types_help(option_types)
|
685
|
+
def self.display_option_types_help(option_types, opts={})
|
686
|
+
puts self.format_option_types_help(option_types, opts)
|
573
687
|
end
|
574
688
|
|
575
689
|
def self.optional_label(option_type)
|