morpheus-cli 4.1.14 → 4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +4 -0
  4. data/lib/morpheus/api/library_container_types_interface.rb +1 -1
  5. data/lib/morpheus/api/library_instance_types_interface.rb +7 -7
  6. data/lib/morpheus/api/library_layouts_interface.rb +1 -1
  7. data/lib/morpheus/api/network_routers_interface.rb +101 -0
  8. data/lib/morpheus/api/tasks_interface.rb +12 -14
  9. data/lib/morpheus/cli.rb +1 -0
  10. data/lib/morpheus/cli/apps.rb +15 -12
  11. data/lib/morpheus/cli/cli_command.rb +40 -2
  12. data/lib/morpheus/cli/clusters.rb +13 -7
  13. data/lib/morpheus/cli/cypher_command.rb +5 -2
  14. data/lib/morpheus/cli/hosts.rb +1 -1
  15. data/lib/morpheus/cli/instances.rb +21 -5
  16. data/lib/morpheus/cli/jobs_command.rb +83 -27
  17. data/lib/morpheus/cli/library_cluster_layouts_command.rb +12 -12
  18. data/lib/morpheus/cli/library_container_scripts_command.rb +52 -40
  19. data/lib/morpheus/cli/library_container_types_command.rb +2 -60
  20. data/lib/morpheus/cli/library_instance_types_command.rb +22 -1
  21. data/lib/morpheus/cli/library_layouts_command.rb +65 -65
  22. data/lib/morpheus/cli/library_option_lists_command.rb +72 -59
  23. data/lib/morpheus/cli/library_option_types_command.rb +30 -186
  24. data/lib/morpheus/cli/library_spec_templates_command.rb +39 -64
  25. data/lib/morpheus/cli/mixins/library_helper.rb +213 -0
  26. data/lib/morpheus/cli/mixins/provisioning_helper.rb +89 -37
  27. data/lib/morpheus/cli/mixins/whoami_helper.rb +16 -1
  28. data/lib/morpheus/cli/network_routers_command.rb +1281 -0
  29. data/lib/morpheus/cli/networks_command.rb +164 -72
  30. data/lib/morpheus/cli/option_types.rb +187 -73
  31. data/lib/morpheus/cli/price_sets_command.rb +4 -4
  32. data/lib/morpheus/cli/prices_command.rb +15 -15
  33. data/lib/morpheus/cli/remote.rb +3 -3
  34. data/lib/morpheus/cli/service_plans_command.rb +17 -8
  35. data/lib/morpheus/cli/tasks.rb +437 -169
  36. data/lib/morpheus/cli/version.rb +1 -1
  37. data/lib/morpheus/formatters.rb +8 -0
  38. 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['resourcePermission'].nil?
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 options[:cloud]
417
- cloud = find_cloud_by_name_or_id(options[:cloud])
418
- # meh, should validate cloud is in the cloudsForNetworks dropdown..
419
- return 1 if cloud.nil?
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
- # print_red_alert "Cloud not specified!"
422
- # exit 1
423
- cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'cloudsForNetworks', 'required' => true, 'description' => 'Select Cloud.'}],options,@api_client,{})
424
- cloud_id = cloud_prompt['cloud']
425
- cloud = find_cloud_by_name_or_id(cloud_id) if cloud_id
426
- return 1 if cloud.nil?
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
- # DEFAULT INPUTS
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
- # Gateway
471
- if options['gateway']
472
- payload['network']['gateway'] = options['gateway']
473
- else
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
- # DNS Primary
479
- if options['dnsPrimary']
480
- payload['network']['dnsPrimary'] = options['dnsPrimary']
481
- else
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
- # DNS Secondary
487
- if options['dnsSecondary']
488
- payload['network']['dnsSecondary'] = options['dnsSecondary']
489
- else
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
- # CIDR
495
- if options['cidr']
496
- payload['network']['cidr'] = options['cidr']
497
- else
498
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cidr', 'fieldLabel' => 'CIDR', 'type' => 'text', 'required' => false, 'description' => ''}], options)
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
- # DHCP Server
511
- if options['dhcpServer'] != nil
512
- payload['network']['dhcpServer'] = options['dhcpServer']
513
- else
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
- # Allow IP Override
519
- if options['allowStaticOverride'] != nil
520
- payload['network']['allowStaticOverride'] = options['allowStaticOverride']
521
- else
522
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'allowStaticOverride', 'fieldLabel' => 'Allow IP Override', 'type' => 'checkbox', 'required' => false, 'description' => ''}], options)
523
- payload['network']['allowStaticOverride'] = v_prompt['allowStaticOverride']
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
- # todo: select dropdown
535
- # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'select', 'optionSource' => 'networkPools', 'required' => false, 'description' => ''}], options, @api_client, {zoneId: cloud['id']})
536
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'pool', 'fieldLabel' => 'Network Pool', 'type' => 'text', 'required' => false, 'description' => ''}], options)
537
- payload['network']['pool'] = v_prompt['pool'].to_i if v_prompt['pool']
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'] == 'select'
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: options[fieldContext.fieldName]
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'] == 'select'
123
- value = select_prompt(option_type.merge({'defaultValue' => value}), api_client, (api_params || {}).merge(results), true)
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 #{field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
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'] == 'select'
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
- elsif option_type['type'] == 'hidden'
162
- value = option_type['defaultValue']
163
- input = value
164
- elsif option_type['type'] == 'file'
165
- value = file_prompt(option_type)
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
- value = generic_prompt(option_type)
200
+ context_map[field_name] = value
168
201
  end
169
202
  end
170
- context_map[field_name] = value
203
+ results
171
204
  end
172
205
 
173
- return results
174
- end
175
-
176
- def self.grails_params(data, context=nil)
177
- params = {}
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
- params[k.to_s] = v
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
- def self.radio_prompt(option_type)
193
- value_found = false
194
- value = nil
195
- options = []
196
- if option_type['config'] and option_type['config']['radioOptions']
197
- option_type['config']['radioOptions'].each do |radio_option|
198
- options << {key: radio_option['key'], checked: radio_option['checked']}
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
- if selectedOption
214
- value = selectedOption[:key]
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
- puts "Invalid Option. Please select from #{optionString}."
217
- end
218
- if !value.nil? || option_type['required'] != true
219
- value_found = true
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 #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
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
- return value
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
- full_field_name = option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''
533
- full_field_name << option_type['fieldName'].to_s
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']} [--#{full_field_name}=] ", Term::ANSIColor.reset , "#{option_type['description']}\n"
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 #{full_field_name}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
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:\nNone\n\n"
671
+ "#{opts[:color]}#{opts[:title] || "Available Options:"}\nNone\n\n"
565
672
  else
566
- option_lines = option_types.collect {|it| " -O #{it['fieldName']}=\"value\"" }.join("\n")
567
- "Available Options:\n#{option_lines}\n\n"
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)