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
@@ -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