morpheus-cli 7.0.7 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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