morpheus-cli 8.0.5 → 8.0.6

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.
@@ -0,0 +1,457 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::StorageDatastores
4
+ include Morpheus::Cli::CliCommand
5
+
6
+ register_subcommands :list, :add, :update, :get
7
+
8
+ def connect(opts)
9
+ @api_client = establish_remote_appliance_connection(opts)
10
+ @storage_datastore_interface = @api_client.storage_datastores
11
+ @cloud_datastores_interface = @api_client.cloud_datastores
12
+ end
13
+
14
+ def handle(args)
15
+ handle_subcommand(args)
16
+ end
17
+
18
+ def list(args)
19
+ options = {}
20
+ params = {}
21
+
22
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
23
+ opts.banner = subcommand_usage()
24
+ build_standard_list_options(opts, options)
25
+ opts.footer = "List Datastores."
26
+ end
27
+ optparse.parse!(args)
28
+ connect(options)
29
+ # verify_args!(args:args, optparse:optparse, count:0)
30
+ if args.count > 0
31
+ options[:phrase] = args.join(" ")
32
+ end
33
+ begin
34
+ params.merge!(parse_list_options(options))
35
+
36
+ @storage_datastore_interface.setopts(options)
37
+ if options[:dry_run]
38
+ print_dry_run @storage_datastore_interface.dry.list(params)
39
+ return 0
40
+ end
41
+
42
+ json_response = @storage_datastore_interface.list(params)
43
+ render_response(json_response, options, 'datastores') do
44
+ datastores = json_response['datastores']
45
+ title = "Storage Datastores"
46
+ print_h1 title
47
+ if datastores.empty?
48
+ print cyan,"No datastores found.",reset,"\n"
49
+ else
50
+ columns = datastores_list_column_definitions(options).upcase_keys!
51
+ print as_pretty_table(datastores, columns, options)
52
+ print_results_pagination(json_response)
53
+ end
54
+ print reset,"\n"
55
+ end
56
+ return 0, nil
57
+ rescue RestClient::Exception => e
58
+ print_rest_exception(e, options)
59
+ exit 1
60
+ end
61
+ end
62
+
63
+ def datastores_list_column_definitions(options)
64
+ {
65
+ "ID" => 'id',
66
+ "Name" => 'name'
67
+ }
68
+ end
69
+
70
+ def add(args)
71
+ options = {}
72
+ params = {}
73
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
74
+ opts.banner = subcommand_usage("[name] [options]")
75
+ build_common_options(opts, options, [:options, :payload, :json, :yaml, :dry_run, :quiet])
76
+ opts.on( '-n', '--name NAME', "Name" ) do |val|
77
+ options['name'] = val
78
+ end
79
+ opts.on( '-t', '--type DATASTORE_TYPE', "Datastore Type" ) do |val|
80
+ options['datastoreType'] = val
81
+ end
82
+ opts.on( '-c', '--cloud DATASTORE_CLOUD', "Datastore Cloud" ) do |val|
83
+ options['cloud'] = val
84
+ end
85
+ opts.footer = "Create a new Datastore.\n" +
86
+ "[name] is required. This is the name of the new datastore. It may also be passed as --name or inside your config."
87
+ end
88
+ optparse.parse!(args)
89
+ if args.count > 1
90
+ print_error Morpheus::Terminal.angry_prompt
91
+ puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
92
+ return 1
93
+ end
94
+ # allow name as first argument
95
+ if args[0] # && !options[:name]
96
+ options[:name] = args[0]
97
+ end
98
+ connect(options)
99
+ begin
100
+ options[:options] ||= {}
101
+ passed_options = (options[:options] || {}).reject {|k,v| k.is_a?(Symbol) }
102
+ payload = {}
103
+ if options[:payload]
104
+ # payload is from parsed json|yaml files or arguments.
105
+ payload = options[:payload]
106
+ # merge -O options
107
+ payload.deep_merge!(passed_options) unless passed_options.empty?
108
+ # support some options on top of --payload
109
+ [:name, :description, :environment].each do |k|
110
+ if options.key?(k)
111
+ payload[k.to_s] = options[k]
112
+ end
113
+ end
114
+ else
115
+ # prompt for payload
116
+ payload = {}
117
+ # merge -O options
118
+ payload.deep_merge!(passed_options) unless passed_options.empty?
119
+
120
+ # Name
121
+ if passed_options['name']
122
+ payload['name'] = passed_options['name']
123
+ else
124
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this archive bucket.'}], options, @api_client)
125
+ payload['name'] = v_prompt['name']
126
+ end
127
+
128
+ #Datastore Type
129
+ if passed_options['datastoreType']
130
+ payload['datastoreType'] = passed_options['datastoreType']
131
+ else
132
+ payload['datastoreType'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'datastoreType', 'fieldLabel' => 'Type', 'type' => 'select', 'required' => true, 'optionSource' => 'datastoreTypes'}], options[:options], @api_client)['datastoreType']
133
+ end
134
+
135
+ if passed_options['cloud']
136
+ zone = passed_options['cloud']
137
+ else
138
+ zone = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zone', 'fieldLabel' => 'Cloud', 'type' => 'select', 'required' => true, 'optionSource' => 'cloudsForDatastores'}], options[:options], @api_client)['zone']
139
+ end
140
+
141
+ if zone
142
+ payload['refType'] = 'ComputeZone'
143
+ payload['refId'] = zone
144
+ end
145
+
146
+ option_types = load_option_types_for_datastore_type(payload['datastoreType'])
147
+
148
+ values = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client)
149
+ if values['domain']
150
+ payload.merge!(values['domain']) if values['domain'].is_a?(Hash)
151
+ end
152
+ if values['config']
153
+ payload['config'] = {}
154
+ payload['config'].merge!(values['config']) if values['config'].is_a?(Hash)
155
+ end
156
+
157
+ @storage_datastore_interface.setopts(options)
158
+ if options[:dry_run]
159
+ print_dry_run @storage_datastore_interface.dry.create({'datastore' => payload})
160
+ return
161
+ end
162
+ json_response = @storage_datastore_interface.create({'datastore' => payload})
163
+ datastore = json_response['datastore']
164
+ if options[:json]
165
+ print JSON.pretty_generate(json_response),"\n"
166
+ elsif !options[:quiet]
167
+ datastore = json_response['datastore']
168
+ print_green_success "Datastore #{datastore['name']} created"
169
+ #get([datastore['id']])
170
+ end
171
+ end
172
+ rescue RestClient::Exception => e
173
+ print_rest_exception(e, options)
174
+ exit 1
175
+ end
176
+ end
177
+
178
+ def get(args)
179
+ datastore_id = nil
180
+ options = {}
181
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
182
+ opts.banner = subcommand_usage("[datastore]")
183
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
184
+ opts.footer = "Get details about a datastore." + "\n" +
185
+ "[datastore] is required. This is the name or id of a datastore."
186
+ end
187
+ optparse.parse!(args)
188
+ if args.count == 1
189
+ datastore_id = args[0]
190
+ else
191
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
192
+ end
193
+ connect(options)
194
+ begin
195
+ @storage_datastore_interface.setopts(options)
196
+ if options[:dry_run]
197
+ if datastore_id.to_s =~ /\A\d{1,}\Z/
198
+ print_dry_run @storage_datastore_interface.dry.get(datastore_id.to_i)
199
+ else
200
+ print_dry_run @storage_datastore_interface.dry.list({name:datastore_id})
201
+ end
202
+ return
203
+ end
204
+ datastore = find_datastore_by_name_or_id(datastore_id)
205
+ return 1 if datastore.nil?
206
+ json_response = {'datastore' => datastore} # skip redundant request
207
+ # json_response = @datastores_interface.get(datastore['id'])
208
+ datastore = json_response['datastore']
209
+ if options[:json]
210
+ puts as_json(json_response, options, "datastore")
211
+ return 0
212
+ elsif options[:yaml]
213
+ puts as_yaml(json_response, options, "datastore")
214
+ return 0
215
+ elsif options[:csv]
216
+ puts records_as_csv([datastore], options)
217
+ return 0
218
+ end
219
+ print_h1 "Datastore Details"
220
+ print cyan
221
+ description_cols = {
222
+ "ID" => 'id',
223
+ "Name" => 'name',
224
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
225
+ "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
226
+ "Capacity" => lambda {|it| it['freeSpace'] ? Filesize.from("#{it['freeSpace']} B").pretty.strip : "Unknown" },
227
+ "Online" => lambda {|it| format_boolean(it['online']) },
228
+ "Active" => lambda {|it| format_boolean(it['active']) },
229
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
230
+ "Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|it| it['name'] }.uniq.join(', ') : '' },
231
+ # "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
232
+ }
233
+ print_description_list(description_cols, datastore)
234
+
235
+ if datastore['resourcePermission'].nil?
236
+ print "\n", "No group access found", "\n"
237
+ else
238
+ print_h2 "Group Access"
239
+ rows = []
240
+ if datastore['resourcePermission']['all']
241
+ rows.push({"name" => 'All'})
242
+ end
243
+ if datastore['resourcePermission']['sites']
244
+ datastore['resourcePermission']['sites'].each do |site|
245
+ rows.push(site)
246
+ end
247
+ end
248
+ rows = rows.collect do |site|
249
+ # {group: site['name'], default: site['default'] ? 'Yes' : ''}
250
+ {group: site['name']}
251
+ end
252
+ # columns = [:group, :default]
253
+ columns = [:group]
254
+ print cyan
255
+ print as_pretty_table(rows, columns)
256
+ end
257
+
258
+ if datastore['tenants'].nil? || datastore['tenants'].nil?
259
+ #print "\n", "No tenant permissions found", "\n"
260
+ else
261
+ print_h2 "Tenant Permissions"
262
+ rows = []
263
+ rows = datastore['tenants'] || []
264
+ tenant_columns = {
265
+ "TENANT" => 'name',
266
+ #"DEFAULT" => lambda {|it| format_boolean(it['defaultTarget']) },
267
+ "IMAGE TARGET" => lambda {|it| format_boolean(it['defaultStore']) }
268
+ }
269
+ print cyan
270
+ print as_pretty_table(rows, tenant_columns)
271
+ end
272
+
273
+ print reset,"\n"
274
+ return 0
275
+ rescue RestClient::Exception => e
276
+ print_rest_exception(e, options)
277
+ return 1
278
+ end
279
+ end
280
+
281
+ def update(args)
282
+ options = {}
283
+ datastore_id = nil
284
+ cloud_id = nil
285
+ tenants = nil
286
+ group_access_all = nil
287
+ group_access_list = nil
288
+ group_defaults_list = nil
289
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
290
+ opts.banner = subcommand_usage("[datastore] [options]")
291
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
292
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
293
+ group_access_all = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
294
+ end
295
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
296
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
297
+ group_access_list = []
298
+ else
299
+ group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
300
+ end
301
+ end
302
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
303
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
304
+ options['tenants'] = []
305
+ else
306
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
307
+ end
308
+ end
309
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
310
+ options['visibility'] = val
311
+ end
312
+ opts.on('--active [on|off]', String, "Can be used to disable a datastore") do |val|
313
+ options['active'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
314
+ end
315
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
316
+ opts.footer = "Update a datastore." + "\n" +
317
+ "[cloud] is required. This is the name or id of the cloud." + "\n" +
318
+ "[datastore] is required. This is the name or id of a datastore."
319
+ end
320
+ optparse.parse!(args)
321
+ if args.count == 1
322
+ datastore_id = args[0]
323
+ else
324
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
325
+ end
326
+ connect(options)
327
+
328
+ begin
329
+ datastore = find_datastore_by_name_or_id(datastore_id)
330
+ return 1 if datastore.nil?
331
+
332
+ # merge -O options into normally parsed options
333
+ options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
334
+
335
+ # construct payload
336
+ payload = nil
337
+ if options[:payload]
338
+ payload = options[:payload]
339
+ else
340
+ # prompt for datastore options
341
+ payload = {
342
+ 'datastore' => {
343
+ }
344
+ }
345
+
346
+ # allow arbitrary -O options
347
+ payload['datastore'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
348
+
349
+
350
+ # Group Access
351
+ if group_access_all != nil
352
+ payload['resourcePermissions'] ||= {}
353
+ payload['resourcePermissions']['all'] = group_access_all
354
+ end
355
+ if group_access_list != nil
356
+ payload['resourcePermissions'] ||= {}
357
+ payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
358
+ site = {"id" => site_id.to_i}
359
+ if group_defaults_list && group_defaults_list.include?(site_id)
360
+ site["default"] = true
361
+ end
362
+ site
363
+ end
364
+ end
365
+
366
+ # Tenants
367
+ if options['tenants']
368
+ payload['tenantPermissions'] = {}
369
+ payload['tenantPermissions']['accounts'] = options['tenants']
370
+ end
371
+
372
+ # Active
373
+ if options['active'] != nil
374
+ payload['datastore']['active'] = options['active']
375
+ end
376
+
377
+ # Visibility
378
+ if options['visibility'] != nil
379
+ payload['datastore']['visibility'] = options['visibility']
380
+ end
381
+
382
+ if payload['datastore'].empty? && payload['resourcePermissions'].nil? && payload['tenantPermissions'].nil?
383
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
384
+ end
385
+
386
+ end
387
+ @storage_datastore_interface.setopts(options)
388
+ if options[:dry_run]
389
+ print_dry_run @storage_datastore_interface.dry.update(datastore["id"], payload)
390
+ return
391
+ end
392
+ json_response = @storage_datastore_interface.update(datastore["id"], payload)
393
+ if options[:json]
394
+ puts as_json(json_response)
395
+ else
396
+ datastore = json_response['data']['datastore']
397
+ print_green_success "Updated datastore #{datastore['name']}"
398
+ get([datastore['id']])
399
+ end
400
+ return 0
401
+ rescue RestClient::Exception => e
402
+ print_rest_exception(e, options)
403
+ return 1
404
+ end
405
+ end
406
+
407
+ def load_option_types_for_datastore_type(datastore_type)
408
+ return @storage_datastore_interface.load_type_options(datastore_type)
409
+ end
410
+
411
+ def find_datastore_by_name_or_id(val)
412
+ if val.to_s =~ /\A\d{1,}\Z/
413
+ return find_datastore_by_id(val)
414
+ else
415
+ return find_datastore_by_name(val)
416
+ end
417
+ end
418
+
419
+ def find_datastore_by_id(id)
420
+ begin
421
+ json_response = @storage_datastore_interface.get(id.to_i)
422
+ return json_response['datastore']
423
+ rescue RestClient::Exception => e
424
+ if e.response && e.response.code == 404
425
+ print_red_alert "Datastore not found by id #{id}"
426
+ return nil
427
+ else
428
+ raise e
429
+ end
430
+ end
431
+ end
432
+
433
+ def find_datastore_by_name(name)
434
+ json_response = @storage_datastore_interface.list({name: name.to_s})
435
+ datastores = json_response['datastores']
436
+ if datastores.empty?
437
+ print_red_alert "Datastore not found by name #{name}"
438
+ return nil
439
+ elsif datastores.size > 1
440
+ print_red_alert "#{datastores.size} datastores found by name #{name}"
441
+ rows = datastores.collect do |it|
442
+ {id: it['id'], name: it['name']}
443
+ end
444
+ print "\n"
445
+ puts as_pretty_table(rows, [:id, :name], {color:red})
446
+ return nil
447
+ else
448
+ datastore = datastores[0]
449
+ # merge in tenants map
450
+ if json_response['tenants'] && json_response['tenants'][datastore['id']]
451
+ datastore['tenants'] = json_response['tenants'][datastore['id']]
452
+ end
453
+ return datastore
454
+ end
455
+ end
456
+
457
+ end
@@ -98,10 +98,11 @@ EOT
98
98
  if access_tokens && !access_tokens.empty?
99
99
  print_h2 "API Access Tokens"
100
100
  cols = {
101
- #"ID" => lambda {|it| it['id'] },
101
+ "ID" => lambda {|it| it['id'] },
102
102
  "CLIENT ID" => lambda {|it| it['clientId'] },
103
103
  "USERNAME" => lambda {|it| it['username'] },
104
104
  "ACCESS TOKEN" => lambda {|it| it['maskedAccessToken'] },
105
+ "SCOPE" => lambda {|it| it['scope'] },
105
106
  "REFRESH TOKEN" => lambda {|it| it['maskedRefreshToken'] },
106
107
  "ACCESS EXPIRATION" => lambda {|it| format_local_dt(it['expiration']) },
107
108
  "ACCESS TTL" => lambda {|it|
@@ -115,6 +116,8 @@ EOT
115
116
  end
116
117
  }
117
118
  }
119
+ cols.delete("ID") if access_tokens[0]['id'].nil?
120
+ cols.delete("SCOPE") if access_tokens[0]['scope'].nil?
118
121
  print cyan
119
122
  puts as_pretty_table(access_tokens, cols)
120
123
  else
@@ -579,21 +582,27 @@ EOT
579
582
  params['userId'] = val.to_s
580
583
  end
581
584
  #opts.add_hidden_option('--user-id')
585
+ opts.on("--id ID", String, "Token ID") do |val|
586
+ params['id'] = val.to_s
587
+ end
582
588
  build_common_options(opts, options, [:payload, :options, :json, :dry_run, :quiet, :remote])
583
589
  opts.footer = <<-EOT
584
590
  Regenerate API access token for a specific client.
585
591
  [client-id] is required. This is the id of an api client.
592
+ The --id [id] option can be used to clear a specific token by id.
586
593
  Done for the current user by default, unless a user is specified with the --user option.
587
594
  EOT
588
595
  end
589
596
  optparse.parse!(args)
590
597
  connect(options)
591
- if args.count != 1
598
+ if args.count != 1 && !params['id']
592
599
  print_error Morpheus::Terminal.angry_prompt
593
600
  puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
594
601
  return 1
595
602
  end
596
- params['clientId'] = args[0]
603
+ if args[0]
604
+ params['clientId'] = args[0]
605
+ end
597
606
  begin
598
607
  if options[:user]
599
608
  user = find_user_by_username_or_id(nil, options[:user], {global:true})
@@ -655,16 +664,21 @@ EOT
655
664
  params['userId'] = val.to_s
656
665
  end
657
666
  #opts.add_hidden_option('--user-id')
667
+ opts.on("--id ID", String, "Token ID") do |val|
668
+ params['id'] = val.to_s
669
+ end
658
670
  build_common_options(opts, options, [:payload, :options, :json, :dry_run, :quiet, :remote])
659
671
  opts.footer = <<-EOT
660
672
  Clear API access token for a specific client.
661
673
  [client-id] or --all is required. This is the id of an api client.
674
+ The --id [id] option can be used to clear a specific token by id.
662
675
  Done for the current user by default, unless a user is specified with the --user option.
663
676
  EOT
664
677
  end
665
678
  optparse.parse!(args)
666
679
  connect(options)
667
- if args.count > 1 || (args.count == 0 && all_clients == false)
680
+
681
+ if (args.count > 1 || (args.count == 0 && all_clients == false)) && !params['id']
668
682
  print_error Morpheus::Terminal.angry_prompt
669
683
  puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
670
684
  return 1
@@ -704,11 +718,13 @@ EOT
704
718
  success_msg = "Success"
705
719
  if all_clients
706
720
  success_msg = "Cleared all access tokens"
721
+ elsif params['id']
722
+ success_msg = "Cleared access token ID #{params['id']}"
707
723
  else
708
- success_msg = "Cleared #{params['clientId']} access token"
724
+ success_msg = "Cleared access tokens for client #{params['clientId']}"
709
725
  end
710
726
  if params['userId']
711
- success_msg << " for user #{params['userId']}"
727
+ success_msg << " (user #{params['userId']})"
712
728
  end
713
729
  print_green_success success_msg
714
730
  if params['clientId'] == Morpheus::APIClient::CLIENT_ID
@@ -802,6 +802,7 @@ module Morpheus::Cli::InfrastructureHelper
802
802
  "Firewall" => lambda {|it| format_boolean(it['hasFirewall']) },
803
803
  "Security Groups" => lambda {|it| format_boolean(it['hasSecurityGroups']) },
804
804
  "Load Balancers" => lambda {|it| format_boolean(it['hasLoadBalancers']) },
805
+ "Floating Ips" => lambda {|it| format_boolean it['hasFloatingIps']},
805
806
  # "Security Code" => lambda {|it| it['securityCode'] },
806
807
  # "User Visible" => lambda {|it| format_boolean(it['userVisible']) },
807
808
  }
@@ -1187,8 +1187,8 @@ module Morpheus::Cli::ProvisioningHelper
1187
1187
  #volume['size'] = plan_size
1188
1188
  #volume['sizeId'] = nil #volume.delete('sizeId')
1189
1189
  end
1190
-
1191
- if !datastore_options.empty?
1190
+
1191
+ if !datastore_options.empty? && !provision_type['disableRootDatastore']
1192
1192
  default_datastore = datastore_options.find {|ds| ds['value'].to_s == volume['datastoreId'].to_s}
1193
1193
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => 'Root Datastore', 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => default_datastore ? default_datastore['name'] : volume['datastoreId']}], options[:options])
1194
1194
  volume['datastoreId'] = v_prompt[field_context]['datastoreId']
@@ -2145,6 +2145,17 @@ module Morpheus::Cli::ProvisioningHelper
2145
2145
  return payload
2146
2146
  end
2147
2147
 
2148
+ def prompt_cluster_load_balancer(cluster, options)
2149
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'loadBalancerTypeId', 'type' => 'select', 'fieldLabel' => "Load Balancer", 'optionSource' => 'loadBalancerTypes', 'required' => false, 'description' => 'Select Load Balancer for Cluster', 'defaultValue' => '', 'excludeKubevip' => true}], options[:options], api_client, cluster)
2150
+ lb_type_id = v_prompt['loadBalancerTypeId']
2151
+
2152
+ if lb_type_id.empty?
2153
+ return false
2154
+ end
2155
+
2156
+ return lb_type_id
2157
+ end
2158
+
2148
2159
 
2149
2160
  # reject old option types that now come from the selected service plan
2150
2161
  # these will eventually get removed from the associated optionTypes
@@ -1,6 +1,6 @@
1
1
  require 'term/ansicolor'
2
2
  require 'readline'
3
- require 'csv'
3
+
4
4
  module Morpheus
5
5
  module Cli
6
6
  module OptionTypes
@@ -304,8 +304,8 @@ module Morpheus
304
304
  value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
305
305
  elsif option_type['type'] == 'multiSelect'
306
306
  # support value as csv like "thing1, thing2"
307
- value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
308
- input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
307
+ value_list = value.is_a?(String) ? value.split(",").collect {|v| v ? v.to_s.strip : v } : [value].flatten
308
+ input_value_list = input_value.is_a?(String) ? input_value.split(",").collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
309
309
  select_value_list = []
310
310
  value_list.each_with_index do |v, i|
311
311
  select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
@@ -315,8 +315,8 @@ module Morpheus
315
315
  value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
316
316
  elsif option_type['type'] == 'multiTypeahead'
317
317
  # support value as csv like "thing1, thing2"
318
- value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
319
- input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
318
+ value_list = value.is_a?(String) ? value.split(",").collect {|v| v ? v.to_s.strip : v } : [value].flatten
319
+ input_value_list = input_value.is_a?(String) ? input_value.split(",").collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
320
320
  select_value_list = []
321
321
  value_list.each_with_index do |v, i|
322
322
  select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "8.0.5"
4
+ VERSION = "8.0.6"
5
5
  end
6
6
  end
@@ -0,0 +1,23 @@
1
+ require 'morpheus_test'
2
+
3
+ # Tests for Morpheus::InstancesInterface
4
+ class MorpheusTest::ClientsInterfaceTest < MorpheusTest::TestCase
5
+
6
+ def test_clients_interface
7
+ @clients_interface = client.clients
8
+ response = @clients_interface.list()
9
+ records = response['clients']
10
+ assert records.is_a?(Array)
11
+ if !records.empty?
12
+ response = @clients_interface.get(records[0]['id'])
13
+ record = response['client']
14
+ assert record.is_a?(Hash)
15
+ assert_equal record['id'], records[0]['id']
16
+ assert_equal record['clientId'], records[0]['clientId']
17
+ else
18
+ #puts "No clients found in this environment"
19
+ end
20
+ #todo: create and delete
21
+ end
22
+
23
+ end