morpheus-cli 4.1.14 → 4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +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)
|