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.
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
@@ -13,6 +13,8 @@ module Morpheus::Cli::LibraryHelper
13
13
  @api_client
14
14
  end
15
15
 
16
+ ## Instance Types
17
+
16
18
  def find_instance_type_by_name_or_id(val)
17
19
  if val.to_s =~ /\A\d{1,}\Z/
18
20
  return find_instance_type_by_id(val)
@@ -120,6 +122,217 @@ module Morpheus::Cli::LibraryHelper
120
122
  return ports
121
123
  end
122
124
 
125
+
126
+ ## Container Types (Node Types)
127
+
128
+ def find_container_type_by_name_or_id(layout_id, val)
129
+ if val.to_s =~ /\A\d{1,}\Z/
130
+ return find_container_type_by_id(layout_id, val)
131
+ else
132
+ return find_container_type_by_name(layout_id, val)
133
+ end
134
+ end
135
+
136
+ def find_container_type_by_id(layout_id, id)
137
+ begin
138
+ json_response = @library_container_types_interface.get(layout_id, id.to_i)
139
+ return json_response['containerType']
140
+ rescue RestClient::Exception => e
141
+ if e.response && e.response.code == 404
142
+ print_red_alert "Instance Type not found by id #{id}"
143
+ else
144
+ raise e
145
+ end
146
+ end
147
+ end
148
+
149
+ def find_container_type_by_name(layout_id, name)
150
+ container_types = @library_container_types_interface.list(layout_id, {name: name.to_s})['containerTypes']
151
+ if container_types.empty?
152
+ print_red_alert "Node Type not found by name #{name}"
153
+ return nil
154
+ elsif container_types.size > 1
155
+ print_red_alert "#{container_types.size} node types found by name #{name}"
156
+ print_container_types_table(container_types, {color: red})
157
+ print_red_alert "Try using ID instead"
158
+ print reset,"\n"
159
+ return nil
160
+ else
161
+ return container_types[0]
162
+ end
163
+ end
164
+
165
+ def print_container_types_table(container_types, opts={})
166
+ columns = [
167
+ {"ID" => lambda {|it| it['id'] } },
168
+ {"TECHNOLOGY" => lambda {|it| format_container_type_technology(it) } },
169
+ {"NAME" => lambda {|it| it['name'] } },
170
+ {"SHORT NAME" => lambda {|it| it['shortName'] } },
171
+ {"VERSION" => lambda {|it| it['containerVersion'] } },
172
+ {"CATEGORY" => lambda {|it| it['category'] } },
173
+ {"OWNER" => lambda {|it| it['account'] ? it['account']['name'] : '' } }
174
+ ]
175
+ if opts[:include_fields]
176
+ columns = opts[:include_fields]
177
+ end
178
+ print as_pretty_table(container_types, columns, opts)
179
+ end
180
+
181
+ def format_container_type_technology(container_type)
182
+ if container_type
183
+ container_type['provisionType'] ? container_type['provisionType']['name'] : ''
184
+ else
185
+ ""
186
+ end
187
+ end
188
+
189
+ def prompt_for_container_types(params, options={}, api_client=nil, api_params={})
190
+ # container_types
191
+ container_type_list = nil
192
+ container_type_ids = nil
193
+ still_prompting = true
194
+ if params['containerTypes'].nil?
195
+ still_prompting = true
196
+ while still_prompting do
197
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'containerTypes', 'type' => 'text', 'fieldLabel' => 'Node Types', 'required' => false, 'description' => 'Node Types (Container Types) to include, comma separated list of names or IDs.'}], options[:options])
198
+ unless v_prompt['containerTypes'].to_s.empty?
199
+ container_type_list = v_prompt['containerTypes'].split(",").collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
200
+ end
201
+ container_type_ids = []
202
+ bad_ids = []
203
+ if container_type_list && container_type_list.size > 0
204
+ container_type_list.each do |it|
205
+ found_container_type = nil
206
+ begin
207
+ found_container_type = find_container_type_by_name_or_id(nil, it)
208
+ rescue SystemExit => cmdexit
209
+ end
210
+ if found_container_type
211
+ container_type_ids << found_container_type['id']
212
+ else
213
+ bad_ids << it
214
+ end
215
+ end
216
+ end
217
+ still_prompting = bad_ids.empty? ? false : true
218
+ end
219
+ else
220
+ container_type_list = params['containerTypes']
221
+ still_prompting = false
222
+ container_type_ids = []
223
+ bad_ids = []
224
+ if container_type_list && container_type_list.size > 0
225
+ container_type_list.each do |it|
226
+ found_container_type = nil
227
+ begin
228
+ found_container_type = find_container_type_by_name_or_id(nil, it)
229
+ rescue SystemExit => cmdexit
230
+ end
231
+ if found_container_type
232
+ container_type_ids << found_container_type['id']
233
+ else
234
+ bad_ids << it
235
+ end
236
+ end
237
+ end
238
+ if !bad_ids.empty?
239
+ return {success:false, msg:"Node Types not found: #{bad_ids}"}
240
+ end
241
+ end
242
+ return {success:true, data: container_type_ids}
243
+ end
244
+
245
+ ## Option Types
246
+
247
+ def find_option_type_by_name_or_id(val)
248
+ if val.to_s =~ /\A\d{1,}\Z/
249
+ return find_option_type_by_id(val)
250
+ else
251
+ return find_option_type_by_name(val)
252
+ end
253
+ end
254
+
255
+ def find_option_type_by_id(id)
256
+ begin
257
+ json_response = @option_types_interface.get(id.to_i)
258
+ return json_response['optionType']
259
+ rescue RestClient::Exception => e
260
+ if e.response && e.response.code == 404
261
+ print_red_alert "Option Type not found by id #{id}"
262
+ exit 1
263
+ else
264
+ raise e
265
+ end
266
+ end
267
+ end
268
+
269
+ def find_option_type_by_name(name)
270
+ json_results = @option_types_interface.list({name: name.to_s})
271
+ if json_results['optionTypes'].empty?
272
+ print_red_alert "Option Type not found by name #{name}"
273
+ exit 1
274
+ end
275
+ option_type = json_results['optionTypes'][0]
276
+ return option_type
277
+ end
278
+
279
+ def prompt_for_option_types(params, options={}, api_client=nil, api_params={})
280
+ # option_types
281
+ option_type_list = nil
282
+ option_type_ids = nil
283
+ still_prompting = true
284
+ if params['optionTypes'].nil?
285
+ still_prompting = true
286
+ while still_prompting do
287
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'optionTypes', 'type' => 'text', 'fieldLabel' => 'Option Types', 'required' => false, 'description' => 'Option Types to include, comma separated list of names or IDs.'}], options[:options])
288
+ unless v_prompt['optionTypes'].to_s.empty?
289
+ option_type_list = v_prompt['optionTypes'].split(",").collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
290
+ end
291
+ option_type_ids = []
292
+ bad_ids = []
293
+ if option_type_list && option_type_list.size > 0
294
+ option_type_list.each do |it|
295
+ found_option_type = nil
296
+ begin
297
+ found_option_type = find_option_type_by_name_or_id(it)
298
+ rescue SystemExit => cmdexit
299
+ end
300
+ if found_option_type
301
+ option_type_ids << found_option_type['id']
302
+ else
303
+ bad_ids << it
304
+ end
305
+ end
306
+ end
307
+ still_prompting = bad_ids.empty? ? false : true
308
+ end
309
+ else
310
+ option_type_list = params['optionTypes']
311
+ still_prompting = false
312
+ option_type_ids = []
313
+ bad_ids = []
314
+ if option_type_list && option_type_list.size > 0
315
+ option_type_list.each do |it|
316
+ found_option_type = nil
317
+ begin
318
+ found_option_type = find_option_type_by_name_or_id(it)
319
+ rescue SystemExit => cmdexit
320
+ end
321
+ if found_option_type
322
+ option_type_ids << found_option_type['id']
323
+ else
324
+ bad_ids << it
325
+ end
326
+ end
327
+ end
328
+ if !bad_ids.empty?
329
+ return {success:false, msg:"Option Types not found: #{bad_ids}"}
330
+ end
331
+ end
332
+ return {success:true, data: option_type_ids}
333
+ end
334
+
335
+
123
336
  ## Spec Template helper methods
124
337
 
125
338
  def find_spec_template_by_name_or_id(val)
@@ -14,49 +14,35 @@ module Morpheus::Cli::ProvisioningHelper
14
14
  end
15
15
 
16
16
  def instances_interface
17
- # @api_client.instances
18
- raise "#{self.class} has not defined @instances_interface" if @instances_interface.nil?
19
- @instances_interface
17
+ @api_client.instances
20
18
  end
21
19
 
22
20
  def options_interface
23
- # @api_client.options
24
- raise "#{self.class} has not defined @options_interface" if @options_interface.nil?
25
- @options_interface
21
+ @api_client.options
26
22
  end
27
23
 
28
24
  def instance_types_interface
29
- # @api_client.instance_types
30
- raise "#{self.class} has not defined @instance_types_interface" if @instance_types_interface.nil?
31
- @instance_types_interface
25
+ @api_client.instance_types
32
26
  end
33
27
 
34
28
  def instance_type_layouts_interface
35
- # @api_client.instance_types
36
- raise "#{self.class} has not defined @library_layouts" if @library_layouts_interface.nil?
37
- @library_layouts_interface
29
+ @api_client.library_layouts
38
30
  end
39
31
 
40
32
  def provision_types_interface
41
- # api_client.provision_types
42
- raise "#{self.class} has not defined @provision_types_interface" if @provision_types_interface.nil?
43
- @provision_types_interface
33
+ api_client.provision_types
44
34
  end
45
35
 
46
36
  def clouds_interface
47
- # @api_client.instance_types
48
- raise "#{self.class} has not defined @clouds_interface" if @clouds_interface.nil?
49
- @clouds_interface
37
+ @api_client.clouds
50
38
  end
51
39
 
52
40
  def cloud_datastores_interface
53
- raise "#{self.class} has not defined @clouds_datastores_interface" if @clouds_datastores_interface.nil?
54
- @clouds_datastores_interface
41
+ @api_client.cloud_datastores
55
42
  end
56
43
 
57
44
  def accounts_interface
58
- raise "#{self.class} has not defined @accounts_interface" if @accounts_interface.nil?
59
- @accounts_interface
45
+ @api_client.accounts
60
46
  end
61
47
 
62
48
  def get_available_groups(refresh=false)
@@ -429,6 +415,7 @@ module Morpheus::Cli::ProvisioningHelper
429
415
  arbitrary_options.delete('environment')
430
416
  arbitrary_options.delete('instanceContext')
431
417
  arbitrary_options.delete('tags')
418
+ # arbitrary_options.delete('ports')
432
419
  payload.deep_merge!(arbitrary_options)
433
420
  end
434
421
 
@@ -511,17 +498,14 @@ module Morpheus::Cli::ProvisioningHelper
511
498
  payload['instance']['layout'] = {'id' => layout['id']}
512
499
 
513
500
  # need to GET provision type for optionTypes, and other settings...
514
- #provision_type = (layout && provision_type ? provision_type : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
501
+ provision_type_code = layout['provisionTypeCode'] || layout['provisionType']['code']
515
502
  provision_type = nil
516
- if layout && layout['provisionTypeCode']
517
- provision_type = provision_types_interface.list({code:layout['provisionTypeCode']})['provisionTypes'][0]
503
+ if provision_type_code
504
+ provision_type = provision_types_interface.list({code:provision_type_code})['provisionTypes'][0]
518
505
  if provision_type.nil?
519
- print_red_alert "Provision Type not found by code #{layout['provisionTypeCode']}"
506
+ print_red_alert "Provision Type not found by code #{provision_type_code}"
520
507
  exit 1
521
508
  end
522
- elsif layout && layout['provisionType']
523
- # api used to return entire record under layout.provisionType
524
- provision_type = layout['provisionType']
525
509
  else
526
510
  provision_type = get_provision_type_for_zone_type(cloud['zoneType']['id'])
527
511
  end
@@ -579,7 +563,7 @@ module Morpheus::Cli::ProvisioningHelper
579
563
  # prompt for resource pool
580
564
  pool_id = nil
581
565
  resource_pool = nil
582
- has_zone_pools = layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasZonePools"]
566
+ has_zone_pools = provision_type && provision_type["id"] && provision_type["hasZonePools"]
583
567
  if has_zone_pools
584
568
  # pluck out the resourcePoolId option type to prompt for
585
569
  resource_pool_option_type = option_type_list.find {|opt| ['resourcePool','resourcePoolId','azureResourceGroupId'].include?(opt['fieldName']) }
@@ -637,7 +621,7 @@ module Morpheus::Cli::ProvisioningHelper
637
621
  end
638
622
 
639
623
  # plan_info has this property already..
640
- # has_datastore = layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasDatastore"]
624
+ # has_datastore = provision_type && provision_type["id"] && provision_type["hasDatastore"]
641
625
  # service_plan['hasDatastore'] = has_datastore
642
626
 
643
627
  # set root volume name if has mounts
@@ -653,10 +637,10 @@ module Morpheus::Cli::ProvisioningHelper
653
637
  end
654
638
 
655
639
  # prompt networks
656
- if layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasNetworks"] # && layout["provisionType"]["supportsNetworkSelection"]
640
+ if provision_type && provision_type["hasNetworks"]
657
641
  # prompt for network interfaces (if supported)
658
642
  begin
659
- network_interfaces = prompt_network_interfaces(cloud_id, layout["provisionType"]["id"], pool_id, options)
643
+ network_interfaces = prompt_network_interfaces(cloud_id, provision_type["id"], pool_id, options)
660
644
  if !network_interfaces.empty?
661
645
  payload['networkInterfaces'] = network_interfaces
662
646
  end
@@ -673,7 +657,7 @@ module Morpheus::Cli::ProvisioningHelper
673
657
  option_type_list = option_type_list.reject {|opt| ((opt['code'] == 'provisionType.amazon.securityId') || (opt['name'] == 'securityId')) }
674
658
  # ok.. seed data has changed and serverTypes do not have this optionType anymore...
675
659
  if sg_option_type.nil?
676
- if layout["provisionType"] && (layout["provisionType"]["code"] == 'amazon')
660
+ if provision_type && (provision_type["code"] == 'amazon')
677
661
  sg_option_type = {'fieldContext' => 'config', 'fieldName' => 'securityId', 'type' => 'select', 'fieldLabel' => 'Security Group', 'optionSource' => 'amazonSecurityGroup', 'required' => true, 'description' => 'Select security group.', 'defaultValue' => options[:default_security_group]}
678
662
  end
679
663
  end
@@ -705,6 +689,18 @@ module Morpheus::Cli::ProvisioningHelper
705
689
  instance_config_payload = Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, api_params)
706
690
  payload.deep_merge!(instance_config_payload)
707
691
 
692
+ ## Network Options
693
+
694
+ # prompt for exposed ports
695
+ if payload['ports'].nil?
696
+ # need a way to know if the instanceType even supports this.
697
+ # the default ports come from the node type, under layout['containerTypes']
698
+ ports = prompt_exposed_ports(options)
699
+ if !ports.empty?
700
+ payload['ports'] = ports
701
+ end
702
+ end
703
+
708
704
  ## Advanced Options
709
705
 
710
706
  # scale factor
@@ -1663,7 +1659,7 @@ module Morpheus::Cli::ProvisioningHelper
1663
1659
  if plan_id == 'all'
1664
1660
  all_plans = true
1665
1661
  else
1666
- plan_access = [{'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['id'] == plan_id}['name']}' as default?", {:default => false})}]
1662
+ plan_access = [{'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['value'] == plan_id}['name']}' as default?", {:default => false})}]
1667
1663
  end
1668
1664
 
1669
1665
  available_plans = available_plans.reject {|it| it['value'] == plan_id}
@@ -1712,13 +1708,13 @@ module Morpheus::Cli::ProvisioningHelper
1712
1708
  if !options[:tenants].nil?
1713
1709
  accounts = options[:tenants].collect {|id| id.to_i}
1714
1710
  elsif !options[:no_prompt]
1715
- account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id']
1711
+ account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account', 'type' => 'select', 'fieldLabel' => 'Add Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account']
1716
1712
 
1717
1713
  if !account_id.nil?
1718
1714
  accounts << account_id
1719
1715
  available_accounts = available_accounts.reject {|it| it['value'] == account_id}
1720
1716
 
1721
- while !available_accounts.empty? && (account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Another Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id'])
1717
+ while !available_accounts.empty? && (account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account', 'type' => 'select', 'fieldLabel' => 'Add Another Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account'])
1722
1718
  if !account_id.nil?
1723
1719
  accounts << account_id
1724
1720
  available_accounts = available_accounts.reject {|it| it['value'] == account_id}
@@ -1812,4 +1808,60 @@ module Morpheus::Cli::ProvisioningHelper
1812
1808
  end
1813
1809
  end
1814
1810
 
1811
+
1812
+ ## Exposed Ports component
1813
+
1814
+ def load_balance_protocols_dropdown
1815
+ [
1816
+ {'name' => 'None', 'value' => ''},
1817
+ {'name' => 'HTTP', 'value' => 'HTTP'},
1818
+ {'name' => 'HTTPS', 'value' => 'HTTPS'},
1819
+ {'name' => 'TCP', 'value' => 'TCP'}
1820
+ ]
1821
+ end
1822
+
1823
+ # Prompts user for ports array
1824
+ # returns array of port objects
1825
+ def prompt_exposed_ports(options={}, api_client=nil, api_params={})
1826
+ #puts "Configure ports:"
1827
+ passed_ports = ((options[:options] && options[:options]["ports"]) ? options[:options]["ports"] : nil)
1828
+ no_prompt = (options[:no_prompt] || (options[:options] && options[:options][:no_prompt]))
1829
+ # skip prompting?
1830
+ if no_prompt
1831
+ return passed_ports
1832
+ end
1833
+ # value already given
1834
+ if passed_ports.is_a?(Array)
1835
+ return passed_ports
1836
+ end
1837
+
1838
+ # prompt for ports
1839
+ ports = []
1840
+ port_index = 0
1841
+ has_another_port = options[:options] && options[:options]["ports"]
1842
+ add_another_port = has_another_port || (!no_prompt && Morpheus::Cli::OptionTypes.confirm("Add an exposed port?", {default:false}))
1843
+ while add_another_port do
1844
+ field_context = port_index == 0 ? "ports" : "ports#{port_index}"
1845
+
1846
+ port = {}
1847
+ port_label = port_index == 0 ? "Port" : "Port [#{port_index+1}]"
1848
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => "#{port_label} Name", 'required' => false, 'description' => 'Choose a name for this port.', 'defaultValue' => port['name']}], options[:options])
1849
+ port['name'] = v_prompt[field_context]['name']
1850
+
1851
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'port', 'type' => 'number', 'fieldLabel' => "#{port_label} Number", 'required' => true, 'description' => 'A port number. eg. 8001', 'defaultValue' => (port['port'] ? port['port'].to_i : nil)}], options[:options])
1852
+ port['port'] = v_prompt[field_context]['port']
1853
+
1854
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'lb', 'type' => 'select', 'fieldLabel' => "#{port_label} LB", 'required' => false, 'selectOptions' => load_balance_protocols_dropdown, 'description' => 'Choose a load balance protocol.', 'defaultValue' => port['lb']}], options[:options])
1855
+ # port['loadBalanceProtocol'] = v_prompt[field_context]['lb']
1856
+ port['lb'] = v_prompt[field_context]['lb']
1857
+
1858
+ ports << port
1859
+ port_index += 1
1860
+ has_another_port = options[:options] && options[:options]["ports#{port_index}"]
1861
+ add_another_port = has_another_port || (!no_prompt && Morpheus::Cli::OptionTypes.confirm("Add another exposed port?", {default:false}))
1862
+ end
1863
+
1864
+
1865
+ return ports
1866
+ end
1815
1867
  end