morpheus-cli 7.0.7 → 8.0.0

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.
@@ -13,18 +13,20 @@ class Morpheus::Cli::Clusters
13
13
  register_subcommands :upgrade_cluster
14
14
  register_subcommands :list_volumes, :remove_volume
15
15
  register_subcommands :list_namespaces, :get_namespace, :add_namespace, :update_namespace, :remove_namespace
16
- register_subcommands :list_containers, :remove_container, :restart_container
16
+ register_subcommands :list_containers, :remove_container, :restart_container, :get_container
17
17
  register_subcommands :list_deployments, :remove_deployment, :restart_deployment
18
18
  register_subcommands :list_stateful_sets, :remove_stateful_set, :restart_stateful_set
19
19
  register_subcommands :list_pods, :remove_pod, :restart_pod
20
20
  register_subcommands :list_jobs, :remove_job
21
21
  register_subcommands :list_services, :remove_service
22
- register_subcommands :list_datastores, :get_datastore, :update_datastore
22
+ register_subcommands :list_datastores, :get_datastore, :update_datastore, :add_datastore, :remove_datastore
23
23
  register_subcommands :update_permissions
24
24
  register_subcommands :api_config, :view_api_token, :view_kube_config
25
25
  register_subcommands :wiki, :update_wiki
26
26
  register_subcommands :apply_template
27
27
  register_subcommands :refresh
28
+ register_subcommands :list_replicasets, :list_daemonsets, :list_endpoints, :list_ingresses, :list_policies, :list_volumes, :list_volume_claims, :list_config_maps, :list_secrets
29
+ register_subcommands :get_pod, :get_deployment, :get_replicaset, :get_daemonset, :get_endpoint, :get_ingress, :get_policy, :get_volume_claim, :get_volume, :get_config_map, :get_secret, :get_stateful_set, :get_job, :get_service
28
30
 
29
31
  def connect(opts)
30
32
  @api_client = establish_remote_appliance_connection(opts)
@@ -32,6 +34,7 @@ class Morpheus::Cli::Clusters
32
34
  @groups_interface = @api_client.groups
33
35
  @cluster_layouts_interface = @api_client.library_cluster_layouts
34
36
  @security_groups_interface = @api_client.security_groups
37
+ @datastores_interface = @api_client.datastores
35
38
  #@security_group_rules_interface = @api_client.security_group_rules
36
39
  @cloud_resource_pools_interface = @api_client.cloud_resource_pools
37
40
  @resource_pool_groups_interface = @api_client.resource_pool_groups
@@ -220,6 +223,7 @@ class Morpheus::Cli::Clusters
220
223
  "Cloud" => lambda { |it| it['zone']['name'] },
221
224
  "Location" => lambda { |it| it['location'] },
222
225
  "Layout" => lambda { |it| it['layout'] ? it['layout']['name'] : ''},
226
+ "Integrations" => lambda {|it| format_name_and_id(it['integrations']) },
223
227
  "API Url" => 'serviceUrl',
224
228
  "Visibility" => lambda { |it| it['visibility'].to_s.capitalize },
225
229
  #"Groups" => lambda {|it| it['groups'].collect {|g| g.instance_of?(Hash) ? g['name'] : g.to_s }.join(', ') },
@@ -228,6 +232,8 @@ class Morpheus::Cli::Clusters
228
232
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
229
233
  "Created By" => lambda {|it| it['createdBy'] ? it['createdBy']['username'] : '' },
230
234
  "Enabled" => lambda { |it| format_boolean(it['enabled']) },
235
+ "Managed" => lambda { |it| format_boolean(it['managed']) },
236
+ "Auto Power On VMs" => lambda { |it| format_boolean(it['autoRecoverPowerState']) },
231
237
  "Status" => lambda { |it| format_cluster_status(it) }
232
238
  }
233
239
  print_description_list(description_cols, cluster)
@@ -796,12 +802,18 @@ class Morpheus::Cli::Clusters
796
802
  opts.on('--managed [on|off]', String, "Can be used to enable / disable managed cluster. Default is on") do |val|
797
803
  options[:managed] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
798
804
  end
805
+ opts.on('--autoRecoverPowerState [on|off]', String, "Automatically Power On VMs") do |val|
806
+ options[:autoRecoverPowerState] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
807
+ end
799
808
  opts.on( nil, '--refresh', "Refresh cluster" ) do
800
809
  options[:refresh] = true
801
810
  end
802
811
  opts.on("--tenant ACCOUNT", String, "Account ID or Name" ) do |val|
803
812
  options[:tenant] = val
804
813
  end
814
+ opts.on('--integrations [LIST]', Array, "Updates Cluster Integration(s), comma separated list of integration IDs") do |list|
815
+ options[:integrations] = list ? list.collect {|it| it.to_s.strip.to_i } : []
816
+ end
805
817
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
806
818
  opts.footer = "Update a cluster.\n" +
807
819
  "[cluster] is required. This is the name or id of an existing cluster."
@@ -831,15 +843,18 @@ class Morpheus::Cli::Clusters
831
843
  else
832
844
  cluster = find_cluster_by_name_or_id(args[0])
833
845
  cluster_payload = {}
846
+ cluster_payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
834
847
  cluster_payload['name'] = options[:name] if !options[:name].empty?
835
848
  cluster_payload['description'] = options[:description] if !options[:description].empty?
836
849
  cluster_payload['labels'] = options[:labels] if !options[:labels].nil?
837
850
  cluster_payload['enabled'] = options[:active] if !options[:active].nil?
838
851
  cluster_payload['managed'] = options[:managed] if !options[:managed].nil?
852
+ cluster_payload['autoRecoverPowerState'] = options[:autoRecoverPowerState] if !options[:autoRecoverPowerState].nil?
839
853
  cluster_payload['serviceUrl'] = options[:apiUrl] if !options[:apiUrl].nil?
840
854
  cluster_payload['serviceToken'] = options[:apiToken] if !options[:apiToken].nil?
841
855
  cluster_payload['refresh'] = options[:refresh] if options[:refresh] == true
842
856
  cluster_payload['tenant'] = options[:tenant] if !options[:tenant].nil?
857
+ cluster_payload['integrations'] = options[:integrations] if !options[:integrations].nil?
843
858
  payload = {"cluster" => cluster_payload}
844
859
  end
845
860
 
@@ -847,10 +862,9 @@ class Morpheus::Cli::Clusters
847
862
  print_red_alert "No clusters available for update"
848
863
  exit 1
849
864
  end
850
-
851
- if cluster_payload.empty?
852
- print_green_success "Nothing to update"
853
- exit 1
865
+
866
+ if payload['cluster'].empty?
867
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
854
868
  end
855
869
 
856
870
  @clusters_interface.setopts(options)
@@ -2205,6 +2219,7 @@ class Morpheus::Cli::Clusters
2205
2219
  :id, :status, :name, :cpu, :memory, :storage
2206
2220
  ]
2207
2221
  print as_pretty_table(rows, columns, options)
2222
+ print_results_pagination(json_response)
2208
2223
  end
2209
2224
  print reset,"\n"
2210
2225
  return 0
@@ -2214,6 +2229,43 @@ class Morpheus::Cli::Clusters
2214
2229
  end
2215
2230
  end
2216
2231
 
2232
+ def _get_container_group(args, options, resource_type)
2233
+ begin
2234
+ cluster = find_cluster_by_name_or_id(args[0])
2235
+ id = args[1]
2236
+ return 1 if cluster.nil?
2237
+
2238
+ params = {}
2239
+ params.merge!(parse_list_options(options))
2240
+ params['resourceLevel'] = options[:resourceLevel] if !options[:resourceLevel].nil?
2241
+ @clusters_interface.setopts(options)
2242
+ if options[:dry_run]
2243
+ print_dry_run @clusters_interface.dry.get_container_group(cluster['id'], resource_type, id, params)
2244
+ return
2245
+ end
2246
+ container_group = @clusters_interface.get_container_group(cluster['id'], resource_type, id, params)['resource']
2247
+
2248
+ render_result = render_with_format(container_group, options, 'containers')
2249
+ return 0 if render_result
2250
+ resource_is = options['title'] ? options['title'].capitalize : resource_type.capitalize
2251
+ title = "Morpheus Cluster #{cluster['name']}: #{resource_is}"
2252
+ print_h1 title
2253
+ print cyan
2254
+ description_cols = {
2255
+ "ID" => 'id',
2256
+ "Name" => 'name',
2257
+ "Status" => 'status',
2258
+ "metadata" => lambda { |it| as_json(it['metadata']) }
2259
+ }
2260
+ print_description_list(description_cols, container_group)
2261
+ print reset,"\n"
2262
+ return 0
2263
+ rescue RestClient::Exception => e
2264
+ print_rest_exception(e, options)
2265
+ exit 1
2266
+ end
2267
+ end
2268
+
2217
2269
  def _remove_container_group(args, options, resource_type)
2218
2270
  begin
2219
2271
  cluster = find_cluster_by_name_or_id(args[0])
@@ -2321,6 +2373,26 @@ class Morpheus::Cli::Clusters
2321
2373
  _list_container_groups(args, options,resource_type)
2322
2374
  end
2323
2375
 
2376
+ def get_deployment(args)
2377
+ resource_type = 'deployment'
2378
+ options = {}
2379
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2380
+ opts.banner = subcommand_usage( "[cluster]")
2381
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
2382
+ options[:resourceLevel] = val.to_s
2383
+ end
2384
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2385
+ opts.footer = "get #{resource_type} for a cluster.\n" +
2386
+ "[cluster] and [#{resource_type}] is required. This is the name or id of an existing cluster, and the id of the #{resource_type}"
2387
+ end
2388
+ optparse.parse!(args)
2389
+ if args.count != 2
2390
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2391
+ end
2392
+ connect(options)
2393
+ _get_container_group(args, options, resource_type)
2394
+ end
2395
+
2324
2396
  def remove_deployment(args)
2325
2397
  resource_type = 'deployment'
2326
2398
  options = {}
@@ -2439,6 +2511,26 @@ class Morpheus::Cli::Clusters
2439
2511
  _list_container_groups(args, options, resource_type)
2440
2512
  end
2441
2513
 
2514
+ def get_pod(args)
2515
+ resource_type = 'pod'
2516
+ options = {}
2517
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2518
+ opts.banner = subcommand_usage( "[cluster]")
2519
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
2520
+ options[:resourceLevel] = val.to_s
2521
+ end
2522
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2523
+ opts.footer = "get #{resource_type} for a cluster.\n" +
2524
+ "[cluster] and [#{resource_type}] is required. This is the name or id of an existing cluster, and the id of the #{resource_type}"
2525
+ end
2526
+ optparse.parse!(args)
2527
+ if args.count != 2
2528
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2529
+ end
2530
+ connect(options)
2531
+ _get_container_group(args, options, resource_type)
2532
+ end
2533
+
2442
2534
  def remove_pod(args)
2443
2535
  resource_type = 'pod'
2444
2536
  options = {}
@@ -2908,6 +3000,130 @@ class Morpheus::Cli::Clusters
2908
3000
  end
2909
3001
  end
2910
3002
 
3003
+ def add_datastore(args)
3004
+ options = {}
3005
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3006
+ opts.banner = subcommand_usage( "[cluster] [options]")
3007
+ build_option_type_options(opts, options, add_datastore_option_types)
3008
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
3009
+ opts.footer = "Add datastore to a cluster.\n" +
3010
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3011
+ "[name] is required. This is the name of the new datastore."
3012
+ end
3013
+
3014
+ optparse.parse!(args)
3015
+ if args.count != 1 and args.count != 2
3016
+ raise_command_error "wrong number of arguments, expected 1-2 and got (#{args.count}) #{args}\n#{optparse}"
3017
+ end
3018
+ connect(options)
3019
+
3020
+ begin
3021
+ cluster = find_cluster_by_name_or_id(args[0])
3022
+ return 1 if cluster.nil?
3023
+ if options[:payload]
3024
+ payload = options[:payload]
3025
+ # support -O OPTION switch on top of --payload
3026
+ if options[:options]
3027
+ payload ||= {}
3028
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
3029
+ end
3030
+ else
3031
+ options[:params] ||= {}
3032
+ options[:params].merge!({:serverGroupId => cluster['id']})
3033
+
3034
+ datastore = Morpheus::Cli::OptionTypes.prompt(add_datastore_option_types, options[:options], @api_client, options[:params])
3035
+
3036
+ datastore_type = find_datastore_type_by_code(datastore['datastoreType'])
3037
+ datastore['datastoreType'] = {id:datastore_type['id']}
3038
+
3039
+ # datastore type options
3040
+ unless datastore_type['optionTypes'].empty?
3041
+ datastore.merge!(Morpheus::Cli::OptionTypes.prompt(datastore_type['optionTypes'], options[:options].deep_merge({:context_map => {'domain' => ''}, :checkbox_as_boolean => true}), @api_client, options[:params]))
3042
+ end
3043
+
3044
+ # perms
3045
+ perms = prompt_permissions(options.merge({:for_datastore => true}), ['plans', 'groupDefaults'])
3046
+
3047
+ datastore['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
3048
+ datastore['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil?
3049
+ datastore['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
3050
+
3051
+ payload = {datastore:datastore}
3052
+ end
3053
+
3054
+ @clusters_interface.setopts(options)
3055
+ if options[:dry_run]
3056
+ print_dry_run @clusters_interface.dry.create_datastore(cluster['id'], payload)
3057
+ return
3058
+ end
3059
+ json_response = @clusters_interface.create_datastore(cluster['id'], payload)
3060
+ if options[:json]
3061
+ puts as_json(json_response)
3062
+ elsif json_response['success']
3063
+ if json_response['msg'] == nil
3064
+ print_green_success "Added datastore to cluster #{cluster['name']}"
3065
+ else
3066
+ print_green_success json_response['msg']
3067
+ end
3068
+ end
3069
+ return 0
3070
+ rescue RestClient::Exception => e
3071
+ print_rest_exception(e, options)
3072
+ exit 1
3073
+ end
3074
+ end
3075
+
3076
+ def remove_datastore(args)
3077
+ params = {}
3078
+ options = {}
3079
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3080
+ opts.banner = subcommand_usage("[cluster] [datastore]")
3081
+ opts.on( '-f', '--force', "Force Delete" ) do
3082
+ params[:force] = 'on'
3083
+ end
3084
+ build_standard_remove_options(opts, options)
3085
+ opts.footer = "Delete a datastore from a cluster.\n" +
3086
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3087
+ "[datastore] is required. This is the name or id of an existing datastore."
3088
+ end
3089
+ optparse.parse!(args)
3090
+ verify_args!(args:args, optparse:optparse, count:2)
3091
+ connect(options)
3092
+ params.merge!(parse_query_options(options))
3093
+
3094
+ cluster = find_cluster_by_name_or_id(args[0])
3095
+ return 1 if cluster.nil?
3096
+
3097
+ datastore_id = args[1]
3098
+ if datastore_id.empty?
3099
+ raise_command_error "missing required worker parameter"
3100
+ end
3101
+
3102
+ datastore = find_datastore_by_name_or_id(cluster['id'], datastore_id)
3103
+ if datastore.nil?
3104
+ print_red_alert "Datastore not found for '#{datastore_id}'"
3105
+ return 1
3106
+ end
3107
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster datastore '#{datastore['name'] || datastore['id']}'?", options)
3108
+ return 9, "aborted command"
3109
+ end
3110
+
3111
+ @clusters_interface.setopts(options)
3112
+ if options[:dry_run]
3113
+ print_dry_run @clusters_interface.dry.destroy_datastore(cluster['id'], datastore['id'], params)
3114
+ return
3115
+ end
3116
+ json_response = @clusters_interface.destroy_datastore(cluster['id'], datastore['id'], params)
3117
+ render_response(json_response, options) do
3118
+ msg = "Datastore #{datastore['name']} is being removed from cluster #{cluster['name']}..."
3119
+ if json_response['msg']
3120
+ msg = json_response['msg']
3121
+ end
3122
+ print_green_success msg
3123
+ end
3124
+ return 0, nil
3125
+ end
3126
+
2911
3127
  def update_datastore(args)
2912
3128
  options = {}
2913
3129
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -3970,6 +4186,25 @@ class Morpheus::Cli::Clusters
3970
4186
  json_results['datastores'].empty? ? nil : json_results['datastores'][0]
3971
4187
  end
3972
4188
 
4189
+ def find_datastore_type_by_code_or_id(val)
4190
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_datastore_type_by_id(val) : find_datastore_type_by_code(val)
4191
+ end
4192
+
4193
+ def find_datastore_type_by_id(id)
4194
+ get_datastore_types.find { |it| it['id'] == id }
4195
+ end
4196
+
4197
+ def find_datastore_type_by_code(code)
4198
+ get_datastore_types.find { |it| it['code'].downcase == code.downcase }
4199
+ end
4200
+
4201
+ def get_datastore_types(refresh=false)
4202
+ if !@datastore_types || refresh
4203
+ @datastore_types = @datastores_interface.types()['datastoreTypes']
4204
+ end
4205
+ @datastore_types
4206
+ end
4207
+
3973
4208
  def find_job_by_name_or_id(cluster_id, val)
3974
4209
  if val.to_s =~ /\A\d{1,}\Z/
3975
4210
  params = {jobId: val.to_i}
@@ -4275,6 +4510,14 @@ class Morpheus::Cli::Clusters
4275
4510
  end
4276
4511
  end
4277
4512
 
4513
+ def add_datastore_option_types
4514
+ [
4515
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
4516
+ {'fieldName' => 'datastoreType', 'fieldLabel' => 'Type', 'type' => 'select', 'optionSource' => 'datastoreTypes', 'description' => 'Choose a datastore type.', 'required' => true, 'displayOrder' => 2},
4517
+ {'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'required' => true, 'displayOrder' => 3, 'defaultValue' => true},
4518
+ ]
4519
+ end
4520
+
4278
4521
  def prompt_resource_pool(group, cloud, service_plan, provision_type, options)
4279
4522
  resource_pool = nil
4280
4523
 
@@ -4451,4 +4694,135 @@ class Morpheus::Cli::Clusters
4451
4694
  exit 1
4452
4695
  end
4453
4696
  end
4697
+
4698
+ def list_replicasets(args)
4699
+ _list_resources(args, 'replicaset')
4700
+ end
4701
+
4702
+ def get_replicaset(args)
4703
+ _get_resource(args, 'replicaset')
4704
+ end
4705
+
4706
+ def list_daemonsets(args)
4707
+ _list_resources(args, 'daemonset')
4708
+ end
4709
+
4710
+ def get_daemonset(args)
4711
+ _get_resource(args, 'daemonset')
4712
+ end
4713
+
4714
+
4715
+ def list_endpoints(args)
4716
+ _list_resources(args, 'endpoint')
4717
+ end
4718
+
4719
+ def get_endpoint(args)
4720
+ _get_resource(args, 'endpoint')
4721
+ end
4722
+
4723
+ def list_ingresses(args)
4724
+ _list_resources(args, 'ingresse')
4725
+ end
4726
+
4727
+ def get_ingress(args)
4728
+ _get_resource(args, 'ingresse', {'title' => 'ingress'})
4729
+ end
4730
+
4731
+ def list_policies(args)
4732
+ _list_resources(args, 'policie')
4733
+ end
4734
+
4735
+ def get_policy(args)
4736
+ _get_resource(args, 'policie', {'title' => 'policy'})
4737
+ end
4738
+
4739
+ def list_volume_claims(args)
4740
+ _list_resources(args, 'volumeclaim')
4741
+ end
4742
+
4743
+ def get_volume_claim(args)
4744
+ _get_resource(args, 'volumeclaim')
4745
+ end
4746
+
4747
+ def list_volumes(args)
4748
+ _list_resources(args, 'volume')
4749
+ end
4750
+
4751
+ def get_volume(args)
4752
+ _get_resource(args, 'volume')
4753
+ end
4754
+
4755
+
4756
+ def list_config_maps(args)
4757
+ _list_resources(args, 'configmap')
4758
+ end
4759
+
4760
+ def get_config_map(args)
4761
+ _get_resource(args, 'configmap')
4762
+ end
4763
+
4764
+ def list_secrets(args)
4765
+ _list_resources(args, 'secret')
4766
+ end
4767
+
4768
+ def get_secret(args)
4769
+ _get_resource(args, 'secret')
4770
+ end
4771
+
4772
+ def get_container(args)
4773
+ _get_resource(args, 'container')
4774
+ end
4775
+
4776
+ def get_stateful_set(args)
4777
+ _get_resource(args, 'statefulset')
4778
+
4779
+ end
4780
+
4781
+ def get_job(args)
4782
+ _get_resource(args, 'job')
4783
+ end
4784
+
4785
+ def get_service(args)
4786
+ _get_resource(args, 'service')
4787
+ end
4788
+
4789
+ private
4790
+
4791
+ def _get_resource(args, resource_type, options = {})
4792
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
4793
+ opts.banner = subcommand_usage("[cluster] [id]")
4794
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
4795
+ options[:resourceLevel] = val.to_s
4796
+ end
4797
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
4798
+ opts.footer = "get #{resource_type} for a cluster.\n" +
4799
+ "[cluster] and [#{resource_type}] is required. This is the name or id of an existing cluster, and the id of the #{resource_type}"
4800
+ end
4801
+ optparse.parse!(args)
4802
+
4803
+ if args.count != 2
4804
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
4805
+ end
4806
+
4807
+ connect(options)
4808
+ _get_container_group(args, options, resource_type)
4809
+ end
4810
+
4811
+ def _list_resources(args, resource_type, options = {})
4812
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
4813
+ opts.banner = subcommand_usage( "[cluster]")
4814
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
4815
+ options[:resourceLevel] = val.to_s
4816
+ end
4817
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
4818
+ opts.footer = "List #{resource_type}s for a cluster.\n" +
4819
+ "[cluster] is required. This is the name or id of an existing cluster."
4820
+ end
4821
+ optparse.parse!(args)
4822
+ if args.count != 1
4823
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
4824
+ end
4825
+ connect(options)
4826
+ _list_container_groups(args, options, resource_type)
4827
+ end
4454
4828
  end
@@ -1009,6 +1009,9 @@ class Morpheus::Cli::Hosts
1009
1009
  opts.on('--ssh-password VALUE', String, "SSH Password") do |val|
1010
1010
  params['sshPassword'] = val == "null" ? nil : val
1011
1011
  end
1012
+ opts.on('--ssh-key-pair ID', String, "SSH Key Pair ID") do |val|
1013
+ params['sshKeyPair'] = val == "null" ? nil : {"id" => val.to_i}
1014
+ end
1012
1015
  opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
1013
1016
  params['powerScheduleType'] = val == "null" ? nil : val
1014
1017
  end
@@ -2746,6 +2746,9 @@ class Morpheus::Cli::Instances
2746
2746
  options = {}
2747
2747
  optparse = Morpheus::Cli::OptionParser.new do |opts|
2748
2748
  opts.banner = subcommand_usage("[instance]")
2749
+ opts.on('--include-network-interfaces','--include-network-interfaces', "Populate payload networkInterfaces with current interfaces") do
2750
+ options[:include_nics] = true
2751
+ end
2749
2752
  build_standard_update_options(opts, options)
2750
2753
  end
2751
2754
  optparse.parse!(args)
@@ -2775,11 +2778,17 @@ class Morpheus::Cli::Instances
2775
2778
  plan_id = instance['plan']['id']
2776
2779
  resource_pool_id = instance['config']['resourcePoolId'] if instance['config']
2777
2780
  current_plan_name = instance['plan']['name']
2778
- current_interfaces = get_instance_interfaces(instance)
2779
- if current_interfaces != false
2780
- payload['networkInterfaces'] = current_interfaces
2781
- end
2782
2781
 
2782
+ # JD: networkInterfaces should not be needed but pre 7.0.8/8.0.0 the API does expect it to be passed
2783
+ # However if the instance has more than one server this creates duplicate nics and breaks things
2784
+ # so only continue to do it if the instance has just one server and remote version is pre 7.0.8
2785
+ # should also only do this if remote version < 7.0.8
2786
+ if options[:include_nics] || (!remote_version_gte("7.0.8") && instance['servers'] && instance['servers'].size == 1)
2787
+ current_interfaces = get_instance_interfaces(instance)
2788
+ if current_interfaces != false
2789
+ payload['networkInterfaces'] = current_interfaces
2790
+ end
2791
+ end
2783
2792
 
2784
2793
  # need to GET provision type for some settings...
2785
2794
  provision_type = @provision_types_interface.get(instance['layout']['provisionTypeId'])['provisionType']
@@ -5576,6 +5585,8 @@ private
5576
5585
  details['interfaces'].each do |inter|
5577
5586
  interfaces.push(inter)
5578
5587
  end
5588
+ # only include the first one or it will create duplicates
5589
+ break
5579
5590
  end
5580
5591
  return interfaces
5581
5592
  rescue
@@ -5583,4 +5594,22 @@ private
5583
5594
  end
5584
5595
  end
5585
5596
 
5597
+ def remote_version_gte(required_version)
5598
+ version = @remote_appliance[:build_version]
5599
+ return false if version.nil?
5600
+ version_numbers = version.split(".")
5601
+ required_version_numbers = required_version.split(".")
5602
+ result = true
5603
+ required_version_numbers.each_with_index do |v, i|
5604
+ if version_numbers[i].to_i > v.to_i
5605
+ break
5606
+ elsif version_numbers[i].to_i < v.to_i
5607
+ result = false
5608
+ break
5609
+ else
5610
+ # keep going
5611
+ end
5612
+ end
5613
+ return result
5614
+ end
5586
5615
  end
@@ -163,6 +163,9 @@ class Morpheus::Cli::LibraryContainerTypesCommand
163
163
  "Virtual Image" => lambda {|it|
164
164
  it['virtualImage'] ? it['virtualImage']['name'] : ''
165
165
  },
166
+ "OsType" => lambda {|it|
167
+ it['osType'] ? it['osType']['name'] : ''
168
+ },
166
169
  # "Category" => lambda {|it| it['category'].to_s.capitalize },
167
170
  # # "Logo" => lambda {|it| it['logo'].to_s },
168
171
  # "Visiblity" => lambda {|it| it['visibility'].to_s.capitalize },
@@ -474,6 +477,9 @@ class Morpheus::Cli::LibraryContainerTypesCommand
474
477
  opts.on('--version VALUE', String, "Version") do |val|
475
478
  params['containerVersion'] = val
476
479
  end
480
+ opts.on('--osType VALUE', Integer, "OsType") do |val|
481
+ params['osTypeId'] = val
482
+ end
477
483
  # opts.on('--technology CODE', String, "Technology") do |val|
478
484
  # params['provisionTypeCode'] = val
479
485
  # end