morpheus-cli 8.0.4.1 → 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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +8 -0
  4. data/lib/morpheus/api/backup_results_interface.rb +4 -0
  5. data/lib/morpheus/api/clusters_interface.rb +8 -0
  6. data/lib/morpheus/api/network_floating_ips_interface.rb +3 -0
  7. data/lib/morpheus/api/server_devices_interface.rb +30 -0
  8. data/lib/morpheus/api/storage_datastores_interface.rb +44 -0
  9. data/lib/morpheus/cli/commands/backups_command.rb +16 -2
  10. data/lib/morpheus/cli/commands/clients_command.rb +14 -7
  11. data/lib/morpheus/cli/commands/cloud_datastores_command.rb +113 -1
  12. data/lib/morpheus/cli/commands/clouds.rb +0 -1
  13. data/lib/morpheus/cli/commands/clouds_types.rb +0 -1
  14. data/lib/morpheus/cli/commands/clusters.rb +36 -1
  15. data/lib/morpheus/cli/commands/containers_command.rb +21 -19
  16. data/lib/morpheus/cli/commands/hosts.rb +25 -4
  17. data/lib/morpheus/cli/commands/license.rb +12 -2
  18. data/lib/morpheus/cli/commands/network_domains_command.rb +1 -1
  19. data/lib/morpheus/cli/commands/network_floating_ips.rb +39 -1
  20. data/lib/morpheus/cli/commands/network_routers_command.rb +14 -1
  21. data/lib/morpheus/cli/commands/server_devices_command.rb +207 -0
  22. data/lib/morpheus/cli/commands/storage_datastores.rb +457 -0
  23. data/lib/morpheus/cli/commands/user_settings_command.rb +22 -6
  24. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +1 -0
  25. data/lib/morpheus/cli/mixins/provisioning_helper.rb +16 -3
  26. data/lib/morpheus/cli/option_types.rb +5 -5
  27. data/lib/morpheus/cli/version.rb +1 -1
  28. data/test/api/clients_interface_test.rb +23 -0
  29. data/test/cli/clients_test.rb +45 -0
  30. metadata +10 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c318612d24c2d587b52f2a91c23ef6079e31959319c8488f336a2bfe8acb2a1
4
- data.tar.gz: a8b0ff7162a1504065e3e46479a54ef5bd77d6ad80bb5f7c4e5f1c056981c2c8
3
+ metadata.gz: 5bd49f7ba0c5284049c949b7eb9abab30586335cf7994071467ac2e40e5052f8
4
+ data.tar.gz: 8042138fa3aa60542f6a042a035172f15aac37483fd3fea25fd11ed550e1ba29
5
5
  SHA512:
6
- metadata.gz: 067e3e48442d8f00f86dc8923898008b410c5882ab3049c3423a7ae46bc3031812f74f5f0f33aab793417d231e568438d5826b921ef4d1fb6764f41537cd2209
7
- data.tar.gz: 1c5c68a81dd270f85f83d02602625883ad901bd304a78ba990dfa1ad05b5ecc85dfcb352b6a38fce0bd973db7f0063b9f54727aec8dfa5ce9d75adfb52533553
6
+ metadata.gz: 24dd7b12d6f4a3884844af6ad53736cfecf3c28102aae2f2b8a710bac97433955aae2d7771f367ae8241f721a701c5a8d375a95fe065af5162280ebbe737d342
7
+ data.tar.gz: 1dca804ca56f509dcf13d9a35312de4429b0163e74f4140952fa06d15b8928f8ebe3132537b77ae1939307a2fcb69aa9d9ea52e2293753a042fdf5a6cf8da2f2
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 8.0.4.1
3
+ RUN gem install morpheus-cli -v 8.0.6
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -412,6 +412,10 @@ class Morpheus::APIClient
412
412
  Morpheus::ServersInterface.new(common_interface_options).setopts(@options)
413
413
  end
414
414
 
415
+ def server_devices
416
+ Morpheus::ServerDevicesInterface.new(common_interface_options).setopts(@options)
417
+ end
418
+
415
419
  def instances
416
420
  Morpheus::InstancesInterface.new(common_interface_options).setopts(@options)
417
421
  end
@@ -742,6 +746,10 @@ class Morpheus::APIClient
742
746
  Morpheus::StorageVolumeTypesInterface.new(common_interface_options).setopts(@options)
743
747
  end
744
748
 
749
+ def storage_datastores
750
+ Morpheus::StorageDatastoresInterface.new(common_interface_options).setopts(@options)
751
+ end
752
+
745
753
  def library_instance_types
746
754
  Morpheus::LibraryInstanceTypesInterface.new(common_interface_options).setopts(@options)
747
755
  end
@@ -25,4 +25,8 @@ class Morpheus::BackupResultsInterface < Morpheus::APIClient
25
25
  execute(method: :delete, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
26
26
  end
27
27
 
28
+ def create_options(id, payload={}, params={}, headers={})
29
+ validate_id!(id)
30
+ execute(method: :post, url: "#{base_path}/#{CGI::escape(id.to_s)}/create-restore", params: params, payload: payload, headers: headers)
31
+ end
28
32
  end
@@ -345,4 +345,12 @@ class Morpheus::ClustersInterface < Morpheus::APIClient
345
345
  execute(method: :get, url: url, headers: headers)
346
346
  end
347
347
 
348
+ def load_balancer_port(params={})
349
+ url = "#{@base_url}/api/clusters/load-balancer-port"
350
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
351
+ headers[:params].merge!(params)
352
+
353
+ execute(method: :get, url: url, headers: headers)
354
+ end
355
+
348
356
  end
@@ -34,4 +34,7 @@ class Morpheus::NetworkFloatingIpsInterface < Morpheus::APIClient
34
34
  execute(method: :put, url: "#{base_path}/#{CGI::escape(id.to_s)}/release", params: params, payload: payload, headers: headers)
35
35
  end
36
36
 
37
+ def allocate(payload={}, params={}, headers={})
38
+ execute(method: :post, url: "#{base_path}/allocate", params: params, payload: payload, headers: headers)
39
+ end
37
40
  end
@@ -0,0 +1,30 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::ServerDevicesInterface < Morpheus::APIClient
4
+
5
+ def base_path(server_id)
6
+ "#{@base_url}/api/servers/#{server_id}"
7
+ end
8
+
9
+ # def get(server_id, id, params={})
10
+ # raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
11
+ # execute({method: :get, url: "#{base_path(server_id)}/devices/#{id}", params: params})
12
+ # end
13
+
14
+ def list(server_id, params={})
15
+ execute({method: :get, url: "#{base_path(server_id)}/devices", params: params})
16
+ end
17
+
18
+ def assign(server_id, id, payload)
19
+ execute({method: :put, url: "#{base_path(server_id)}/devices/#{id}/assign", payload: payload.to_json})
20
+ end
21
+
22
+ def attach(server_id, id, payload)
23
+ execute({method: :put, url: "#{base_path(server_id)}/devices/#{id}/attach", payload: payload.to_json})
24
+ end
25
+
26
+ def detach(server_id, id, payload)
27
+ execute({method: :put, url: "#{base_path(server_id)}/devices/#{id}/detach", payload: payload.to_json})
28
+ end
29
+
30
+ end
@@ -0,0 +1,44 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::StorageDatastoresInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "#{@base_url}/api/data-stores"
7
+ end
8
+
9
+ def get(id, params={})
10
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
11
+ url = "#{@base_url}/api/data-stores/#{id}"
12
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
13
+ opts = {method: :get, url: url, headers: headers}
14
+ execute(opts)
15
+ end
16
+
17
+ def list(params={})
18
+ url = base_path
19
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
20
+ opts = {method: :get, url: url, headers: headers}
21
+ execute(opts)
22
+ end
23
+
24
+ def load_type_options(datastore_type)
25
+ url = "/api/data-stores/#{datastore_type}/option-types"
26
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
27
+ opts = {method: :get, url: url, headers: headers}
28
+ execute(opts)
29
+ end
30
+
31
+ def create(options)
32
+ url = "#{@base_url}/api/data-stores"
33
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
34
+ payload = options
35
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json)
36
+ end
37
+
38
+ def update(id, payload)
39
+ url = "#{@base_url}/api/data-stores/#{id}"
40
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
41
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
42
+ execute(opts)
43
+ end
44
+ end
@@ -373,10 +373,21 @@ EOT
373
373
  # could actually fetch the instance.., only need name and id right now though.
374
374
  raise_command_error "Backup instance not found" if instance.nil?
375
375
  params['restoreInstance'] = prompt_value({'fieldName' => 'restoreInstance', 'fieldLabel' => 'Restore Instance', 'type' => 'select', 'selectOptions' => [{'name' => 'Current Instance', 'value' => 'existing'}, {'name' => 'New Instance', 'value' => 'new'}], 'defaultValue' => 'existing', 'required' => true, 'description' => 'Restore the current instance or a new instance?'}, options)
376
+ restore_options = @backup_results_interface.create_options(backup_result['id'])
376
377
  if params['restoreInstance'] == 'new'
377
378
  # new instance
378
- config_map = prompt_restore_instance_config(options)
379
- params['instanceConfig'] = config_map
379
+ if restore_options['vmRestore']
380
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => get_available_groups(), 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{'serverTypeId' => restore_options['servierTypeId']})['group']
381
+ name = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Instance Name', 'defaultValue' => "#{backup['name']}-restore"}], options[:options], @api_client)['name']
382
+ params['groupId'] = group_id
383
+ params['name'] = name
384
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(restore_options['restoreNewOptionTypes'], options[:options], @api_client, {'zoneId' => backup_result['zoneId'], 'siteId' => group_id})
385
+ v_prompt.deep_compact!.booleanize! # remove empty values and convert checkbox "on" and "off" to true and false
386
+ params.deep_merge!(v_prompt)
387
+ else
388
+ config_map = prompt_restore_instance_config(options)
389
+ params['instanceConfig'] = config_map
390
+ end
380
391
  else
381
392
  # existing instance
382
393
  # confirm the instance
@@ -393,6 +404,9 @@ EOT
393
404
  print_red_alert "The value '#{instance_id}' does not match the existing instance #{instance['name']} [#{instance['id'] rescue ''}]. Please try again."
394
405
  end
395
406
  end
407
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(restore_options['restoreExistingOptionTypes'], options[:options], @api_client, {'zoneId' => backup_result['zoneId'], 'siteId' => group_id})
408
+ v_prompt.deep_compact!.booleanize! # remove empty values and convert checkbox "on" and "off" to true and false
409
+ params.deep_merge!(v_prompt)
396
410
  end
397
411
  elsif backup['locationType'] == 'server'
398
412
  # prompt for server type backup restore
@@ -102,9 +102,10 @@ class Morpheus::Cli::ClientsCommand
102
102
  "ID" => 'id',
103
103
  "Client ID" => 'clientId',
104
104
  "Access Token Validity Seconds" => 'accessTokenValiditySeconds',
105
- "Refresh Token Validity Seconds" => 'refreshTokenValiditySeconds'
105
+ "Refresh Token Validity Seconds" => 'refreshTokenValiditySeconds',
106
+ # "Scopes" => lambda {|client| client['scopes'].join(", ")},
107
+ "Redirect URI" => lambda {|client| client['redirectUris'].join(", ")}
106
108
  }
107
-
108
109
  print_description_list(client_columns, client)
109
110
  print reset,"\n"
110
111
 
@@ -122,7 +123,7 @@ class Morpheus::Cli::ClientsCommand
122
123
  optparse = Morpheus::Cli::OptionParser.new do |opts|
123
124
  opts.banner = subcommand_usage("[clientId] [options]")
124
125
  build_option_type_options(opts, options, add_client_option_types)
125
- build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
126
+ build_standard_add_options(opts, options)
126
127
  opts.footer = "Add New Oauth Client Record."
127
128
  end
128
129
  optparse.parse!(args)
@@ -150,6 +151,9 @@ class Morpheus::Cli::ClientsCommand
150
151
  payload.deep_merge!({'client' => passed_options}) unless passed_options.empty?
151
152
  # prompt for options
152
153
  params = Morpheus::Cli::OptionTypes.prompt(add_client_option_types, options[:options], @api_client, options[:params])
154
+ if params['redirectUris'] && params['redirectUris'].is_a?(String)
155
+ params['redirectUris'] = params['redirectUris'].split(',').collect {|it| it.strip}.reject {|it| it.empty?}
156
+ end
153
157
  payload.deep_merge!({'client' => params}) unless params.empty?
154
158
  end
155
159
 
@@ -179,7 +183,7 @@ class Morpheus::Cli::ClientsCommand
179
183
  optparse = Morpheus::Cli::OptionParser.new do |opts|
180
184
  opts.banner = subcommand_usage("[clientId] [options]")
181
185
  build_option_type_options(opts, options, client_option_types)
182
- build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
186
+ build_standard_update_options(opts, options)
183
187
  opts.footer = "Update Oauth Client Record."
184
188
  end
185
189
  optparse.parse!(args)
@@ -206,11 +210,13 @@ class Morpheus::Cli::ClientsCommand
206
210
  }
207
211
  }
208
212
  # allow arbitrary -O options
209
- payload.deep_merge!({'page' => passed_options}) unless passed_options.empty?
213
+ payload.deep_merge!({'client' => passed_options}) unless passed_options.empty?
210
214
  # prompt for options
211
215
  #params = Morpheus::Cli::OptionTypes.prompt(update_wiki_page_option_types, options[:options], @api_client, options[:params])
212
216
  params = passed_options
213
-
217
+ if params['redirectUris'] && params['redirectUris'].is_a?(String)
218
+ params['redirectUris'] = params['redirectUris'].split(',').collect {|it| it.strip}.reject {|it| it.empty?}
219
+ end
214
220
  if params.empty?
215
221
  raise_command_error "Specify at least one option to update.\n#{optparse}"
216
222
  end
@@ -332,7 +338,8 @@ class Morpheus::Cli::ClientsCommand
332
338
  {'fieldName' => 'clientId', 'fieldLabel' => 'Client Id', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
333
339
  {'fieldName' => 'clientSecret', 'fieldLabel' => 'Client Secret', 'type' => 'text', 'displayOrder' => 2},
334
340
  {'fieldName' => 'accessTokenValiditySeconds', 'fieldLabel' => 'Access Token Validity Length (Seconds)', 'type' => 'number', 'required' => true,'defaultValue' => 43200, 'displayOrder' => 3},
335
- {'fieldName' => 'refreshTokenValiditySeconds', 'fieldLabel' => 'Refresh Token Validity Length (Seconds)', 'type' => 'number', 'required' => true,'defaultValue' => 43200, 'displayOrder' => 4}
341
+ {'fieldName' => 'refreshTokenValiditySeconds', 'fieldLabel' => 'Refresh Token Validity Length (Seconds)', 'type' => 'number', 'required' => true,'defaultValue' => 43200, 'displayOrder' => 4},
342
+ {'fieldName' => 'redirectUris', 'fieldLabel' => 'Redirect URI', 'type' => 'text', 'displayOrder' => 5}
336
343
  ]
337
344
  end
338
345
  end
@@ -7,7 +7,7 @@ class Morpheus::Cli::CloudDatastoresCommand
7
7
  # set_command_name :'cloud-datastores'
8
8
  set_command_name :'datastores'
9
9
 
10
- register_subcommands :list, :get, :update
10
+ register_subcommands :list, :get, :update, :add
11
11
 
12
12
  def initialize()
13
13
  # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
@@ -18,6 +18,7 @@ class Morpheus::Cli::CloudDatastoresCommand
18
18
  @cloud_datastores_interface = @api_client.cloud_datastores
19
19
  @clouds_interface = @api_client.clouds
20
20
  @options_interface = @api_client.options
21
+ @storage_datastore_interface = @api_client.storage_datastores
21
22
  end
22
23
 
23
24
  def handle(args)
@@ -379,6 +380,114 @@ class Morpheus::Cli::CloudDatastoresCommand
379
380
  end
380
381
  end
381
382
 
383
+ def add(args)
384
+ options = {}
385
+ params = {}
386
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
387
+ opts.banner = subcommand_usage("[name] [options]")
388
+ build_common_options(opts, options, [:options, :payload, :json, :yaml, :dry_run, :quiet])
389
+ opts.on( '-n', '--name NAME', "Name" ) do |val|
390
+ options['name'] = val
391
+ end
392
+ opts.on( '-t', '--type DATASTORE_TYPE', "Datastore Type" ) do |val|
393
+ options['datastoreType'] = val
394
+ end
395
+ opts.on( '-c', '--cloud DATASTORE_CLOUD', "Datastore Cloud" ) do |val|
396
+ options['cloud'] = val
397
+ end
398
+ opts.footer = "Create a new Datastore.\n" +
399
+ "[name] is required. This is the name of the new datastore. It may also be passed as --name or inside your config."
400
+ end
401
+ optparse.parse!(args)
402
+ if args.count > 1
403
+ print_error Morpheus::Terminal.angry_prompt
404
+ puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
405
+ return 1
406
+ end
407
+ # allow name as first argument
408
+ if args[0] # && !options[:name]
409
+ options[:name] = args[0]
410
+ end
411
+ connect(options)
412
+ begin
413
+ options[:options] ||= {}
414
+ passed_options = (options[:options] || {}).reject {|k,v| k.is_a?(Symbol) }
415
+ payload = {}
416
+ if options[:payload]
417
+ # payload is from parsed json|yaml files or arguments.
418
+ payload = options[:payload]
419
+ # merge -O options
420
+ payload.deep_merge!(passed_options) unless passed_options.empty?
421
+ # support some options on top of --payload
422
+ [:name, :description, :environment].each do |k|
423
+ if options.key?(k)
424
+ payload[k.to_s] = options[k]
425
+ end
426
+ end
427
+ else
428
+ # prompt for payload
429
+ payload = {}
430
+ # merge -O options
431
+ payload.deep_merge!(passed_options) unless passed_options.empty?
432
+
433
+ # Name
434
+ if passed_options['name']
435
+ payload['name'] = passed_options['name']
436
+ else
437
+ 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)
438
+ payload['name'] = v_prompt['name']
439
+ end
440
+
441
+ #Datastore Type
442
+ if passed_options['datastoreType']
443
+ payload['datastoreType'] = passed_options['datastoreType']
444
+ else
445
+ payload['datastoreType'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'datastoreType', 'fieldLabel' => 'Type', 'type' => 'select', 'required' => true, 'optionSource' => 'datastoreTypes'}], options[:options], @api_client)['datastoreType']
446
+ end
447
+
448
+ if passed_options['cloud']
449
+ zone = passed_options['cloud']
450
+ else
451
+ zone = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zone', 'fieldLabel' => 'Cloud', 'type' => 'select', 'required' => true, 'optionSource' => 'cloudsForDatastores'}], options[:options], @api_client)['zone']
452
+ end
453
+
454
+ if zone
455
+ payload['refType'] = 'ComputeZone'
456
+ payload['refId'] = zone
457
+ end
458
+
459
+ option_types = load_option_types_for_datastore_type(payload['datastoreType'])
460
+
461
+ values = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client)
462
+ if values['domain']
463
+ payload.merge!(values['domain']) if values['domain'].is_a?(Hash)
464
+ end
465
+ if values['config']
466
+ payload['config'] = {}
467
+ payload['config'].merge!(values['config']) if values['config'].is_a?(Hash)
468
+ end
469
+
470
+ @storage_datastore_interface.setopts(options)
471
+ if options[:dry_run]
472
+ print_dry_run @storage_datastore_interface.dry.create({'datastore' => payload})
473
+ return
474
+ end
475
+ json_response = @storage_datastore_interface.create({'datastore' => payload})
476
+ datastore = json_response['datastore']
477
+ if options[:json]
478
+ print JSON.pretty_generate(json_response),"\n"
479
+ elsif !options[:quiet]
480
+ datastore = json_response['datastore']
481
+ print_green_success "Datastore #{datastore['name']} created"
482
+ #get([datastore['id']])
483
+ end
484
+ end
485
+ rescue RestClient::Exception => e
486
+ print_rest_exception(e, options)
487
+ exit 1
488
+ end
489
+ end
490
+
382
491
  private
383
492
 
384
493
 
@@ -428,4 +537,7 @@ class Morpheus::Cli::CloudDatastoresCommand
428
537
  end
429
538
  end
430
539
 
540
+ def load_option_types_for_datastore_type(datastore_type)
541
+ return @storage_datastore_interface.load_type_options(datastore_type)
542
+ end
431
543
  end
@@ -848,7 +848,6 @@ EOT
848
848
  "Discovery" => lambda {|it| format_boolean it['hasDiscovery'] },
849
849
  "Cloud Init" => lambda {|it| format_boolean it['hasCloudInit'] },
850
850
  "Folders" => lambda {|it| format_boolean it['hasFolders'] },
851
- "Floating Ips" => lambda {|it| format_boolean it['hasFloatingIps'] },
852
851
  # "Marketplace" => lambda {|it| format_boolean it['hasMarketplace'] },
853
852
  "Public Cloud" => lambda {|it| format_boolean(it['cloud'] == 'public') },
854
853
  }
@@ -123,7 +123,6 @@ EOT
123
123
  "Discovery" => lambda {|it| format_boolean it['hasDiscovery'] },
124
124
  "Cloud Init" => lambda {|it| format_boolean it['hasCloudInit'] },
125
125
  "Folders" => lambda {|it| format_boolean it['hasFolders'] },
126
- "Floating Ips" => lambda {|it| format_boolean it['hasFloatingIps'] },
127
126
  # "Marketplace" => lambda {|it| format_boolean it['hasMarketplace'] },
128
127
  "Public Cloud" => lambda {|it| format_boolean(it['cloud'] == 'public') },
129
128
  }
@@ -652,6 +652,10 @@ class Morpheus::Cli::Clusters
652
652
  metadata_option_type = option_type_list.find {|type| type['fieldName'] == 'metadata' }
653
653
  option_type_list = option_type_list.reject {|type| type['fieldName'] == 'metadata' }
654
654
 
655
+ # remove load balancer option_type, prompt manually
656
+ loadbalancer_option_type = option_type_list.find{|type| type['fieldName'] == 'loadBalancerId'}
657
+ option_type_list = option_type_list.reject { |type| type['fieldName'] == 'loadBalancerId' }
658
+
655
659
  server_count = layout['serverCount']
656
660
 
657
661
  # KLUDGE: google zone required for network selection
@@ -754,6 +758,37 @@ class Morpheus::Cli::Clusters
754
758
  end
755
759
  end
756
760
 
761
+ if loadbalancer_option_type
762
+ lb_payload = { computeTypeLayoutId: cluster_payload['layout']['id']}
763
+ load_balancer_id = prompt_cluster_load_balancer(cluster_payload, options)
764
+ if load_balancer_id != false
765
+ lb_payload['loadBalancerId'] = load_balancer_id
766
+ lb_payload['loadBalancerInstanceId'] = -1
767
+ lb_port_result = @clusters_interface.load_balancer_port(lb_payload)
768
+ lb_options = lb_port_result['optionTypes']
769
+ lb_option_results = Morpheus::Cli::OptionTypes.prompt(lb_options, options[:options], @api_client, api_params)
770
+
771
+ load_balancer_payload = {
772
+ port: lb_port_result.dig('loadBalancerPort', 'externalPort'),
773
+ enabled: lb_port_result.dig('loadBalancer', 'enabled'),
774
+ loadBalancerId: load_balancer_id,
775
+ backendPort: lb_port_result.dig('loadBalancerPort', 'internalPort'),
776
+ loadBalancer: { id: load_balancer_id },
777
+ vipPool: (lb_option_results.dig('domain', 'vipPool') == 'none') ? nil : lb_option_results.dig('domain', 'vipPool'),
778
+ vipAddress: lb_option_results.dig('domain', 'vipAddress'),
779
+ externalAddress: (lb_port_result.dig('loadBalancer', 'externalAddress') == true ) ? 'on': 'off',
780
+ vipShared: (lb_port_result.dig('loadBalancer', 'vipShared') == true ) ? 'on' : 'off',
781
+ vipProtocol: lb_port_result.dig('loadBalancerPort', 'loadBalanceProtocol'),
782
+ instanceId: lb_payload['loadBalancerInstanceId'],
783
+ name: lb_option_results['vipName']
784
+ }
785
+ if lb_option_results['domain']
786
+ load_balancer_payload.merge!(lb_option_results['domain']) { |key, old_val, new_val| old_val }
787
+ end
788
+ cluster_payload['lbInstances'] = [load_balancer_payload.compact!]
789
+ end
790
+ end
791
+
757
792
  cluster_payload['server'] = server_payload
758
793
  payload = {'cluster' => cluster_payload}
759
794
  end
@@ -4248,7 +4283,7 @@ class Morpheus::Cli::Clusters
4248
4283
  end
4249
4284
 
4250
4285
  def find_layout_by_name(name)
4251
- @cluster_layouts_interface.list({phrase:name}).find { it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
4286
+ @cluster_layouts_interface.list({phrase:name}).find { |it| it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
4252
4287
  end
4253
4288
 
4254
4289
  def layouts_for_dropdown(zone_id, group_type_id)
@@ -4,6 +4,7 @@ class Morpheus::Cli::ContainersCommand
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::ProvisioningHelper
6
6
  include Morpheus::Cli::LogsHelper
7
+ include Morpheus::Cli::InfrastructureHelper
7
8
 
8
9
  set_command_name :containers
9
10
  set_command_description "View and manage containers (nodes)."
@@ -18,6 +19,7 @@ class Morpheus::Cli::ContainersCommand
18
19
  @logs_interface = @api_client.logs
19
20
  @execution_request_interface = @api_client.execution_request
20
21
  @clouds_interface = @api_client.clouds
22
+ @network_server_types_interface = @api_client.network_server_types
21
23
  end
22
24
 
23
25
  def handle(args)
@@ -799,14 +801,14 @@ EOT
799
801
  connect(options)
800
802
  container = find_container_by_id(args[0])
801
803
  return 1 if container.nil?
802
- cloud_type = load_container_cloud_type(container)
803
- if !cloud_type['hasFloatingIps']
804
- raise_command_error "Cloud Type #{cloud_type['name']} does support floating IPs."
804
+ network_server_type = load_container_network_server_type(container)
805
+ if !network_server_type['hasFloatingIps']
806
+ raise_command_error "Network Server Type #{network_server_type['name']} does support floating IPs."
805
807
  end
806
808
  payload = parse_payload(options)
807
809
  if payload.nil?
808
810
  payload = parse_passed_options(options)
809
- attach_floating_ip_option_types = cloud_type['floatingIpTypes']
811
+ attach_floating_ip_option_types = network_server_type['floatingIpTypes']
810
812
  if attach_floating_ip_option_types && !attach_floating_ip_option_types.empty?
811
813
  if options[:ip]
812
814
  floating_ip = options[:ip].to_s.sub(/\Aip\-/i, '')
@@ -829,7 +831,7 @@ EOT
829
831
  # payload.deep_merge!({'container' => v_prompt})
830
832
  payload.deep_merge!(v_prompt)
831
833
  else
832
- # raise_command_error "Cloud Type #{cloud_type['name']} does not defined any floating IP inputs."
834
+ # raise_command_error "Network Server Type #{network_server_type['name']} does not defined any floating IP inputs."
833
835
  end
834
836
  end
835
837
  confirm!("Are you sure you would like to attach this floating IP to container #{container['id']}?", options)
@@ -861,9 +863,9 @@ EOT
861
863
  connect(options)
862
864
  container = find_container_by_id(args[0])
863
865
  return 1 if container.nil?
864
- cloud_type = load_container_cloud_type(container)
865
- if !cloud_type['hasFloatingIps']
866
- raise_command_error "Cloud Type #{cloud_type['name']} does support floating IPs."
866
+ network_server_type = load_container_network_server_type(container)
867
+ if !network_server_type['hasFloatingIps']
868
+ raise_command_error "Network Type #{network_server_type['name']} does support floating IPs."
867
869
  end
868
870
  payload = parse_payload(options)
869
871
  if payload.nil?
@@ -948,18 +950,18 @@ EOT
948
950
  return provision_type
949
951
  end
950
952
 
951
- def load_container_cloud_type(container)
952
- cloud_type_code = container['cloud']['type'] rescue nil
953
- cloud_type = nil
954
- if cloud_type_code
955
- cloud_type = @clouds_interface.cloud_types({code:cloud_type_code})['zoneTypes'][0]
956
- if cloud_type.nil?
957
- raise_command_error "Cloud Type not found by code #{cloud_type_code}"
958
- end
953
+ def load_container_network_server_type(container)
954
+ cloud = find_cloud_by_id(container['cloud']['id'])
955
+ network_server_type_code = cloud['networkServer']['type'] rescue nil
956
+ network_server_type = nil
957
+ if network_server_type_code
958
+ network_server_type = @network_server_types_interface.list({code:network_server_type_code})['networkServerTypes'][0]
959
+ if network_server_type.nil?
960
+ raise_command_error "Network Server Type not found by code #{network_server_type_code}"
961
+ end
959
962
  else
960
- raise_command_error "Unable to determine cloud type for container #{container['id']}"
963
+ raise_command_error "Unable to determine network server type for container #{container['id']}"
961
964
  end
962
- return cloud_type
965
+ return network_server_type
963
966
  end
964
-
965
967
  end
@@ -12,7 +12,8 @@ class Morpheus::Cli::Hosts
12
12
  {:'types' => :list_types},
13
13
  {:exec => :execution_request},
14
14
  :wiki, :update_wiki,
15
- :maintenance, :leave_maintenance, :placement
15
+ :maintenance, :leave_maintenance, :placement,
16
+ :list_devices, :assign_device, :detach_device, :attach_device
16
17
  alias_subcommand :details, :get
17
18
  set_default_subcommand :list
18
19
 
@@ -2281,7 +2282,7 @@ EOT
2281
2282
  end
2282
2283
  payload[:server][:deleteLocalData] = options.fetch(:deleteLocalData) do
2283
2284
  prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deleteLocalData', 'fieldLabel' => 'Delete Local Data', 'type' => 'checkbox', 'defaultValue' => false, 'required' => false}], options, @api_client, {})
2284
- prompt['force'] == 'on'
2285
+ prompt['deleteLocalData'] == 'on'
2285
2286
  end
2286
2287
  end
2287
2288
 
@@ -2354,7 +2355,7 @@ EOT
2354
2355
  end
2355
2356
  json_response = @servers_interface.leave_maintenance(server['id'], payload, params)
2356
2357
  render_response(json_response, options) do
2357
- print_green_success "Maintenance mode enabled for host #{server['name']}"
2358
+ print_green_success "Left Maintenance Mode for host #{server['name']}"
2358
2359
  #get([server['id']])
2359
2360
  end
2360
2361
  return 0, nil
@@ -2418,6 +2419,24 @@ EOT
2418
2419
  return 0, nil
2419
2420
  end
2420
2421
 
2422
+ ## Server Devices
2423
+
2424
+ def list_devices(args)
2425
+ Morpheus::Cli::ServerDevicesCommand.new.list(args)
2426
+ end
2427
+
2428
+ def assign_device(args)
2429
+ Morpheus::Cli::ServerDevicesCommand.new.assign(args)
2430
+ end
2431
+
2432
+ def attach_device(args)
2433
+ Morpheus::Cli::ServerDevicesCommand.new.attach(args)
2434
+ end
2435
+
2436
+ def detach_device(args)
2437
+ Morpheus::Cli::ServerDevicesCommand.new.detach(args)
2438
+ end
2439
+
2421
2440
  private
2422
2441
 
2423
2442
  def find_host_by_id(id)
@@ -2440,7 +2459,9 @@ EOT
2440
2459
  print_red_alert "Server not found by name #{name}"
2441
2460
  exit 1
2442
2461
  elsif results['servers'].size > 1
2443
- print_red_alert "Multiple Servers exist with the name #{name}. Try using id instead"
2462
+ print_red_alert "Multiple Servers exist with the name '#{name}'"
2463
+ puts_error as_pretty_table(results['servers'], [:id, :name], {color:red})
2464
+ print_red_alert "Try using ID instead"
2444
2465
  exit 1
2445
2466
  end
2446
2467
  return results['servers'][0]
@@ -316,7 +316,12 @@ class Morpheus::Cli::License
316
316
  "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
317
317
  "End Date" => lambda {|it|
318
318
  if it['endDate']
319
- format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
319
+ end_date = parse_time(it['endDate']) rescue nil
320
+ if end_date && Time.now > end_date
321
+ format_local_dt(it['endDate']).to_s + red + ' (expired)' + cyan
322
+ else
323
+ format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
324
+ end
320
325
  else
321
326
  'Never'
322
327
  end
@@ -424,7 +429,12 @@ class Morpheus::Cli::License
424
429
  "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
425
430
  "End Date" => lambda {|it|
426
431
  if it['endDate']
427
- format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
432
+ end_date = parse_time(it['endDate']) rescue nil
433
+ if end_date && Time.now > end_date
434
+ format_local_dt(it['endDate']).to_s + red + ' (expired)' + cyan
435
+ else
436
+ format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
437
+ end
428
438
  else
429
439
  'Never'
430
440
  end