morpheus-cli 5.5.1.4 → 5.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +25 -0
  4. data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
  5. data/lib/morpheus/api/body_io.rb +22 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +5 -1
  7. data/lib/morpheus/api/clients_interface.rb +41 -0
  8. data/lib/morpheus/api/clouds_interface.rb +21 -0
  9. data/lib/morpheus/api/instances_interface.rb +8 -1
  10. data/lib/morpheus/api/integrations_interface.rb +30 -0
  11. data/lib/morpheus/api/library_instance_types_interface.rb +15 -3
  12. data/lib/morpheus/api/network_pool_server_types_interface.rb +9 -0
  13. data/lib/morpheus/api/plugins_interface.rb +22 -0
  14. data/lib/morpheus/api/roles_interface.rb +20 -1
  15. data/lib/morpheus/api/security_package_types_interface.rb +9 -0
  16. data/lib/morpheus/api/security_packages_interface.rb +9 -0
  17. data/lib/morpheus/api/security_scans_interface.rb +9 -0
  18. data/lib/morpheus/api/servers_interface.rb +17 -17
  19. data/lib/morpheus/api/storage_providers_interface.rb +1 -1
  20. data/lib/morpheus/api/virtual_images_interface.rb +1 -23
  21. data/lib/morpheus/cli/cli_command.rb +81 -7
  22. data/lib/morpheus/cli/commands/apps.rb +28 -2
  23. data/lib/morpheus/cli/commands/archives_command.rb +2 -2
  24. data/lib/morpheus/cli/commands/blueprints_command.rb +16 -0
  25. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +34 -2
  26. data/lib/morpheus/cli/commands/clients_command.rb +338 -0
  27. data/lib/morpheus/cli/commands/clouds.rb +127 -1
  28. data/lib/morpheus/cli/commands/clusters.rb +42 -12
  29. data/lib/morpheus/cli/commands/curl_command.rb +114 -135
  30. data/lib/morpheus/cli/commands/hosts.rb +108 -11
  31. data/lib/morpheus/cli/commands/instances.rb +115 -14
  32. data/lib/morpheus/cli/commands/integrations_command.rb +215 -4
  33. data/lib/morpheus/cli/commands/invoices_command.rb +20 -11
  34. data/lib/morpheus/cli/commands/jobs_command.rb +299 -190
  35. data/lib/morpheus/cli/commands/library_cluster_layouts_command.rb +16 -2
  36. data/lib/morpheus/cli/commands/library_container_scripts_command.rb +14 -0
  37. data/lib/morpheus/cli/commands/library_container_templates_command.rb +131 -48
  38. data/lib/morpheus/cli/commands/library_container_types_command.rb +17 -4
  39. data/lib/morpheus/cli/commands/library_instance_types_command.rb +85 -7
  40. data/lib/morpheus/cli/commands/library_layouts_command.rb +32 -1
  41. data/lib/morpheus/cli/commands/library_option_lists_command.rb +30 -18
  42. data/lib/morpheus/cli/commands/library_option_types_command.rb +31 -14
  43. data/lib/morpheus/cli/commands/library_spec_templates_command.rb +14 -0
  44. data/lib/morpheus/cli/commands/library_upgrades_command.rb +2 -2
  45. data/lib/morpheus/cli/commands/network_pool_server_types.rb +20 -0
  46. data/lib/morpheus/cli/commands/network_pool_servers_command.rb +55 -158
  47. data/lib/morpheus/cli/commands/network_pools_command.rb +49 -23
  48. data/lib/morpheus/cli/commands/networks_command.rb +262 -45
  49. data/lib/morpheus/cli/commands/plugins.rb +213 -0
  50. data/lib/morpheus/cli/commands/price_sets_command.rb +40 -10
  51. data/lib/morpheus/cli/commands/prices_command.rb +17 -5
  52. data/lib/morpheus/cli/commands/processes_command.rb +2 -1
  53. data/lib/morpheus/cli/commands/remote.rb +7 -10
  54. data/lib/morpheus/cli/commands/roles.rb +924 -335
  55. data/lib/morpheus/cli/commands/search_command.rb +2 -0
  56. data/lib/morpheus/cli/commands/security_groups.rb +72 -84
  57. data/lib/morpheus/cli/commands/security_package_types.rb +32 -0
  58. data/lib/morpheus/cli/commands/security_packages.rb +84 -0
  59. data/lib/morpheus/cli/commands/security_scans.rb +107 -0
  60. data/lib/morpheus/cli/commands/service_plans_command.rb +16 -14
  61. data/lib/morpheus/cli/commands/subnets_command.rb +15 -1
  62. data/lib/morpheus/cli/commands/tasks.rb +34 -1
  63. data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
  64. data/lib/morpheus/cli/commands/user_settings_command.rb +11 -2
  65. data/lib/morpheus/cli/commands/users.rb +50 -9
  66. data/lib/morpheus/cli/commands/virtual_images.rb +14 -0
  67. data/lib/morpheus/cli/commands/workflows.rb +14 -0
  68. data/lib/morpheus/cli/mixins/accounts_helper.rb +6 -5
  69. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +79 -0
  70. data/lib/morpheus/cli/mixins/jobs_helper.rb +4 -5
  71. data/lib/morpheus/cli/mixins/library_helper.rb +2 -0
  72. data/lib/morpheus/cli/mixins/logs_helper.rb +3 -0
  73. data/lib/morpheus/cli/mixins/monitoring_helper.rb +1 -1
  74. data/lib/morpheus/cli/mixins/print_helper.rb +29 -4
  75. data/lib/morpheus/cli/mixins/provisioning_helper.rb +38 -9
  76. data/lib/morpheus/cli/mixins/rest_command.rb +106 -8
  77. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +6 -2
  78. data/lib/morpheus/cli/option_types.rb +94 -25
  79. data/lib/morpheus/cli/version.rb +1 -1
  80. data/lib/morpheus/formatters.rb +10 -1
  81. metadata +15 -2
@@ -8,7 +8,7 @@ class Morpheus::Cli::Hosts
8
8
  set_command_name :hosts
9
9
  set_command_description "View and manage hosts (servers)."
10
10
  register_subcommands :list, :count, :get, :view, :stats, :add, :update, :remove, :logs, :start, :stop, :resize,
11
- :run_workflow, :make_managed, :upgrade_agent, :snapshots, :software, :software_sync,
11
+ :run_workflow, :make_managed, :upgrade_agent, :snapshots, :software, :software_sync, :update_network_label,
12
12
  {:'types' => :list_types},
13
13
  {:exec => :execution_request},
14
14
  :wiki, :update_wiki
@@ -109,11 +109,11 @@ class Morpheus::Cli::Hosts
109
109
  opts.on( '--tenant TENANT', "Tenant Name or ID" ) do |val|
110
110
  options[:account] = val
111
111
  end
112
- opts.on('--labels label',String, "Filter by labels (keywords).") do |val|
113
- val.split(",").each do |k|
114
- options[:labels] ||= []
115
- options[:labels] << k.strip
116
- end
112
+ opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
113
+ add_query_parameter(params, 'labels', parse_labels(val))
114
+ end
115
+ opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
116
+ add_query_parameter(params, 'allLabels', parse_labels(val))
117
117
  end
118
118
  opts.on('--tags Name=Value',String, "Filter by tags.") do |val|
119
119
  val.split(",").each do |value_pair|
@@ -519,7 +519,7 @@ class Morpheus::Cli::Hosts
519
519
  "Name" => 'name',
520
520
  "Hostname" => 'hostname',
521
521
  "Description" => 'description',
522
- "Labels" => lambda {|it| it['labels'] ? it['labels'].join(',') : '' },
522
+ "Labels" => lambda {|it| format_list(it['labels']) rescue '' },
523
523
  "Tags" => lambda {|it| tags ? format_metadata(tags) : '' },
524
524
  "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
525
525
  "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
@@ -751,6 +751,16 @@ class Morpheus::Cli::Hosts
751
751
  opts.on("--security-groups LIST", Integer, "Security Groups, comma separated list of security group IDs") do |val|
752
752
  options[:security_groups] = val.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
753
753
  end
754
+ opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
755
+ options[:metadata] = val
756
+ end
757
+ opts.on('--metadata [LIST]', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
758
+ options[:metadata] = val
759
+ end
760
+ opts.add_hidden_option('--metadata')
761
+ opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
762
+ options[:options]['labels'] = parse_labels(val)
763
+ end
754
764
  opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
755
765
  options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
756
766
  end
@@ -933,7 +943,18 @@ class Morpheus::Cli::Hosts
933
943
  end
934
944
  end
935
945
  end
936
-
946
+
947
+ # Metadata Tags
948
+ if metadata_option_type
949
+ if options[:metadata]
950
+ metadata = parse_metadata(options[:metadata])
951
+ payload['tags'] = metadata if !metadata.empty?
952
+ else
953
+ metadata = prompt_metadata(options)
954
+ payload['tags'] = metadata if !metadata.empty?
955
+ end
956
+ end
957
+
937
958
  api_params = {}
938
959
  api_params['zoneId'] = cloud['id']
939
960
  api_params['poolId'] = payload['config']['resourcePool'] if (payload['config'] && payload['config']['resourcePool'])
@@ -988,8 +1009,8 @@ class Morpheus::Cli::Hosts
988
1009
  opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
989
1010
  params['powerScheduleType'] = val == "null" ? nil : val
990
1011
  end
991
- opts.on('--labels [LIST]', String, "Labels (keywords) in the format 'foo, bar'") do |val|
992
- params['labels'] = val.to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
1012
+ opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
1013
+ params['labels'] = parse_labels(val)
993
1014
  end
994
1015
  opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
995
1016
  options[:tags] = val
@@ -1276,7 +1297,10 @@ class Morpheus::Cli::Hosts
1276
1297
  current_volumes = volumes_response['volumes'].sort {|x,y| x['displayOrder'] <=> y['displayOrder'] }
1277
1298
 
1278
1299
  # prompt for volumes
1279
- volumes = prompt_resize_volumes(current_volumes, service_plan, provision_type, options)
1300
+ vol_options = options
1301
+ vol_options['siteId'] = group_id
1302
+ vol_options['zoneId'] = cloud_id
1303
+ volumes = prompt_resize_volumes(current_volumes, service_plan, provision_type, vol_options)
1280
1304
  if !volumes.empty?
1281
1305
  payload[:volumes] = volumes
1282
1306
  end
@@ -2219,6 +2243,79 @@ EOT
2219
2243
  end
2220
2244
  out
2221
2245
  end
2246
+
2247
+ def update_network_label(args)
2248
+ options = {}
2249
+ params = {}
2250
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2251
+ opts.banner = subcommand_usage("[server] [options]")
2252
+ opts.on('--network NETWORK', "Network Interface ID" ) do |val|
2253
+ options[:network] = val
2254
+ end
2255
+ opts.on('--label LABEL', "label") do |val|
2256
+ options[:label] = val
2257
+ end
2258
+ opts.footer = "Change the label of a Network Interface.\n" +
2259
+ "Editing an Interface will not apply changes to the physical hardware. The purpose is for a manual override or data correction (mostly for self managed or baremetal servers where cloud sync is not available)\n" +
2260
+ "[name or id] is required. The name or the id of the server.\n" +
2261
+ "[network] ID of the Network Interface. (optional).\n" +
2262
+ "[label] New Label name for the Network Interface (optional)"
2263
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
2264
+ end
2265
+ optparse.parse!(args)
2266
+ if args.count != 1
2267
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
2268
+ return 1
2269
+ end
2270
+ connect(options)
2271
+
2272
+ begin
2273
+ host = find_host_by_name_or_id(args[0])
2274
+ return 1 if host.nil?
2275
+
2276
+ network_id = options[:network]
2277
+ if network_id != nil && network_id.to_i == 0
2278
+ print_red_alert "network must be an ID/integer above 0, not a name/string value."
2279
+ network_id = nil
2280
+ end
2281
+
2282
+
2283
+ if !network_id
2284
+ available_networks = get_available_networks(host)
2285
+ network_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'network', 'fieldLabel' => 'Network', 'type' => 'select', 'selectOptions' => available_networks, 'required' => true, 'defaultValue' => available_networks[0], 'description' => "The networks available for relabeling"}], options[:options])
2286
+ network_id = network_prompt['network']
2287
+ end
2288
+
2289
+ label = options[:label]
2290
+ while label.nil? do
2291
+ label_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'label', 'fieldLabel' => 'Label', 'type' => 'text', 'required' => true}], options[:options])
2292
+ label = label_prompt['label']
2293
+ end
2294
+ payload = { "name" => label }
2295
+ if options[:dry_run]
2296
+ print_dry_run @servers_interface.dry.update_network_label(network_id, host["id"], payload)
2297
+ return
2298
+ end
2299
+ json_response = @servers_interface.update_network_label(network_id, host["id"], payload)
2300
+ if options[:json]
2301
+ puts as_json(json_response, options)
2302
+ else
2303
+ print_green_success "Updated label for host #{host['name']} network #{network_id} to #{label}"
2304
+ end
2305
+ return 0
2306
+ rescue RestClient::Exception => e
2307
+ print_rest_exception(e, options)
2308
+ exit 1
2309
+ end
2310
+ end
2311
+
2312
+ def get_available_networks(host)
2313
+ results = @options_interface.options_for_source('availableNetworksForHost',{serverId: host['id'].to_i})
2314
+ available_networks = results['data'].collect {|it|
2315
+ {"id" => it["value"], "name" => it["name"], "value" => it["value"]}
2316
+ }
2317
+ return available_networks
2318
+ end
2222
2319
 
2223
2320
 
2224
2321
  def make_managed_option_types(connected=true)
@@ -23,6 +23,7 @@ class Morpheus::Cli::Instances
23
23
  :console, :status_check, {:containers => :list_containers},
24
24
  :scaling, {:'scaling-update' => :scaling_update},
25
25
  :wiki, :update_wiki,
26
+ :update_network_label,
26
27
  {:exec => :execution_request},
27
28
  :deploys,
28
29
  :refresh, :prepare_apply, :apply, :state
@@ -121,11 +122,11 @@ class Morpheus::Cli::Instances
121
122
  opts.on( '--plan-code CODE', String, "Filter by Plan code(s)" ) do |val|
122
123
  params['planCode'] = parse_id_list(val)
123
124
  end
124
- opts.on('--labels label',String, "Filter by labels (keywords).") do |val|
125
- val.split(",").each do |k|
126
- options[:labels] ||= []
127
- options[:labels] << k.strip
128
- end
125
+ opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
126
+ add_query_parameter(params, 'labels', parse_labels(val))
127
+ end
128
+ opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
129
+ add_query_parameter(params, 'allLabels', parse_labels(val))
129
130
  end
130
131
  opts.on('--tags Name=Value',String, "Filter by tags (metadata name value pairs).") do |val|
131
132
  val.split(",").each do |value_pair|
@@ -182,7 +183,6 @@ class Morpheus::Cli::Instances
182
183
 
183
184
  params['showDeleted'] = true if options[:showDeleted]
184
185
  params['deleted'] = true if options[:deleted]
185
- params['labels'] = options[:labels] if options[:labels]
186
186
  if options[:tags]
187
187
  options[:tags].each do |k,v|
188
188
  params['tags.' + k] = v
@@ -257,6 +257,7 @@ class Morpheus::Cli::Instances
257
257
  row = {
258
258
  id: instance['id'],
259
259
  name: instance['name'],
260
+ labels: format_list(instance['labels'], '', 3),
260
261
  connection: format_instance_connection_string(instance),
261
262
  environment: instance['instanceContext'],
262
263
  user: (instance['owner'] ? (instance['owner']['username'] || instance['owner']['id']) : (instance['createdBy'].is_a?(Hash) ? instance['createdBy']['username'] : instance['createdBy'])),
@@ -275,16 +276,16 @@ class Morpheus::Cli::Instances
275
276
  }
276
277
  row
277
278
  }
278
- columns = [:id, {:name => {:max_width => 50}}, :group, :cloud,
279
- :type, :version, :environment,
279
+ columns = [:id, {:name => {:max_width => 50}}, :labels, :group, :cloud,
280
+ :type, :version, :environment, :plan,
280
281
  {:created => {:display_name => "CREATED"}},
281
282
  # {:tenant => {:display_name => "TENANT"}},
282
283
  {:user => {:display_name => "OWNER", :max_width => 20}},
283
- :plan,
284
284
  :nodes, {:connection => {:max_width => 30}}, :status, :cpu, :memory, :storage]
285
285
  # custom pretty table columns ... this is handled in as_pretty_table now(),
286
286
  # todo: remove all these.. and try to always pass rows as the json data itself..
287
287
  if options[:details] != true
288
+ columns.delete(:labels)
288
289
  columns.delete(:plan)
289
290
  end
290
291
  print cyan
@@ -404,7 +405,7 @@ class Morpheus::Cli::Instances
404
405
  end
405
406
  opts.add_hidden_option('--metadata')
406
407
  opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
407
- options[:labels] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
408
+ options[:labels] = parse_labels(val)
408
409
  end
409
410
  opts.on("--copies NUMBER", Integer, "Number of copies to provision") do |val|
410
411
  options[:copies] = val.to_i
@@ -608,7 +609,7 @@ class Morpheus::Cli::Instances
608
609
  options[:group] = val
609
610
  end
610
611
  opts.on('--labels [LIST]', String, "Labels (keywords) in the format 'foo, bar'") do |val|
611
- params['labels'] = val.to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
612
+ params['labels'] = parse_labels(val)
612
613
  end
613
614
  opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
614
615
  options[:tags] = val
@@ -902,6 +903,74 @@ class Morpheus::Cli::Instances
902
903
  end
903
904
  end
904
905
 
906
+ def update_network_label(args)
907
+ options = {}
908
+ params = {}
909
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
910
+ opts.banner = subcommand_usage("[instance] [options]")
911
+ opts.on('--network NETWORK', "Network Interface ID" ) do |val|
912
+ options[:network] = val
913
+ end
914
+ opts.on('--label LABEL', "label") do |val|
915
+ options[:label] = val
916
+ end
917
+ opts.footer = "Change the label of a Network Interface.\n" +
918
+ "Editing an Interface will not apply changes to the physical hardware. The purpose is for a manual override or data correction (mostly for self managed or baremetal servers where cloud sync is not available)\n" +
919
+ "[name or id] is required. The name or the id of the instance.\n" +
920
+ "[network] ID of the Network Interface. (optional).\n" +
921
+ "[label] New Label name for the Network Interface (optional)"
922
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
923
+ end
924
+ optparse.parse!(args)
925
+ if args.count != 1
926
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
927
+ return 1
928
+ end
929
+ connect(options)
930
+
931
+ begin
932
+ instance = find_instance_by_name_or_id(args[0])
933
+ return 1 if instance.nil?
934
+
935
+ network_id = options[:network]
936
+ if network_id != nil && network_id.to_i == 0
937
+ print_red_alert "network must be an ID/integer above 0, not a name/string value."
938
+ network_id = nil
939
+ end
940
+
941
+ if !network_id
942
+ available_networks = get_available_networks(instance)
943
+ network_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'network', 'fieldLabel' => 'Network', 'type' => 'select', 'selectOptions' => available_networks, 'required' => true, 'defaultValue' => available_networks[0], 'description' => "The networks available for relabeling"}], options[:options])
944
+ network_id = network_prompt['network']
945
+ end
946
+
947
+
948
+
949
+ label = options[:label]
950
+ while label.nil? do
951
+ label_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'label', 'fieldLabel' => 'Label', 'type' => 'text', 'required' => true}], options[:options])
952
+ label = label_prompt['label']
953
+ end
954
+ payload = { "name" => label }
955
+ if options[:dry_run]
956
+ print_dry_run @instances_interface.dry.update_network_label(network_id, instance["id"], payload)
957
+ return
958
+ end
959
+ json_response = @instances_interface.update_network_label(network_id, instance["id"], payload)
960
+ if options[:json]
961
+ puts as_json(json_response, options)
962
+ else
963
+ print_green_success "Updated label for instance #{instance['name']} network #{network_id} to #{label}"
964
+ end
965
+ return 0
966
+ rescue RestClient::Exception => e
967
+ print_rest_exception(e, options)
968
+ exit 1
969
+ end
970
+ end
971
+
972
+
973
+
905
974
  def status_check(args)
906
975
  out = ""
907
976
  options = {}
@@ -1725,7 +1794,7 @@ class Morpheus::Cli::Instances
1725
1794
  end
1726
1795
  opts.add_hidden_option('--metadata')
1727
1796
  opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
1728
- options[:labels] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
1797
+ options[:labels] = parse_labels(val)
1729
1798
  end
1730
1799
  # opts.on("--copies NUMBER", Integer, "Number of copies to provision") do |val|
1731
1800
  # options[:copies] = val.to_i
@@ -2697,13 +2766,19 @@ class Morpheus::Cli::Instances
2697
2766
  cloud_id = instance['cloud']['id']
2698
2767
  layout_id = instance['layout']['id']
2699
2768
  plan_id = instance['plan']['id']
2769
+ resource_pool_id = instance['config']['resourcePoolId'] if instance['config']
2700
2770
  current_plan_name = instance['plan']['name']
2771
+ current_interfaces = get_instance_interfaces(instance)
2772
+ if current_interfaces != false
2773
+ payload['networkInterfaces'] = current_interfaces
2774
+ end
2775
+
2701
2776
 
2702
2777
  # need to GET provision type for some settings...
2703
2778
  provision_type = @provision_types_interface.get(instance['layout']['provisionTypeId'])['provisionType']
2704
2779
 
2705
2780
  # prompt for service plan
2706
- service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, siteId: group_id, layoutId: layout_id})
2781
+ service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, siteId: group_id, layoutId: layout_id, resourcePoolId: resource_pool_id})
2707
2782
  service_plans = service_plans_json["plans"]
2708
2783
  service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
2709
2784
  service_plans_dropdown.each do |plan|
@@ -2723,7 +2798,10 @@ class Morpheus::Cli::Instances
2723
2798
  current_volumes = volumes_response['volumes'].sort {|x,y| x['displayOrder'] <=> y['displayOrder'] }
2724
2799
 
2725
2800
  # prompt for volumes
2726
- volumes = prompt_resize_volumes(current_volumes, service_plan, provision_type, options)
2801
+ vol_options = options
2802
+ vol_options['siteId'] = group_id
2803
+ vol_options['zoneId'] = cloud_id
2804
+ volumes = prompt_resize_volumes(current_volumes, service_plan, provision_type, vol_options)
2727
2805
  if !volumes.empty?
2728
2806
  payload["volumes"] = volumes
2729
2807
  end
@@ -5184,4 +5262,27 @@ private
5184
5262
  }
5185
5263
  end
5186
5264
 
5265
+ def get_available_networks(instance)
5266
+ results = @options_interface.options_for_source('availableNetworksForInstance',{instanceId: instance['id'].to_i})
5267
+ available_networks = results['data'].collect {|it|
5268
+ {"id" => it["value"], "name" => it["name"], "value" => it["value"]}
5269
+ }
5270
+ return available_networks
5271
+ end
5272
+
5273
+ def get_instance_interfaces(instance)
5274
+ begin
5275
+ servers = instance['servers']
5276
+ interfaces = []
5277
+ servers.each do |server|
5278
+ details = @servers_interface.get(server.to_i)['server']
5279
+ details['interfaces'].each do |inter|
5280
+ interfaces.push(inter)
5281
+ end
5282
+ end
5283
+ return interfaces
5284
+ rescue
5285
+ return false
5286
+ end
5287
+ end
5187
5288
  end
@@ -8,6 +8,7 @@ class Morpheus::Cli::IntegrationsCommand
8
8
 
9
9
  register_subcommands :list, :get, :add, :update, :remove, :refresh
10
10
  register_subcommands :list_objects, :get_object, :add_object, :remove_object
11
+ register_subcommands :list_inventory, :get_inventory, :update_inventory
11
12
  register_subcommands :list_types, :get_type
12
13
 
13
14
  def connect(opts)
@@ -936,6 +937,170 @@ EOT
936
937
  return 0, nil
937
938
  end
938
939
 
940
+ def list_inventory(args)
941
+ options = {}
942
+ params = {}
943
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
944
+ opts.banner = subcommand_usage("[integration] [search]")
945
+ build_standard_list_options(opts, options)
946
+ opts.footer = <<-EOT
947
+ List integration inventory.
948
+ [integration] is required. This is the name or id of an integration.
949
+ Only certain types of integrations support this operation, such as Ansible Tower.
950
+ EOT
951
+ end
952
+ optparse.parse!(args)
953
+ verify_args!(args:args, optparse:optparse, min:1)
954
+ connect(options)
955
+
956
+ integration = find_integration_by_name_or_id(args[0])
957
+ return 1, "integration not found for #{args[0]}" if integration.nil?
958
+
959
+ if args.count > 1
960
+ options[:phrase] = args[1..-1].join(" ")
961
+ end
962
+ params.merge!(parse_list_options(options))
963
+ @integrations_interface.setopts(options)
964
+ if options[:dry_run]
965
+ print_dry_run @integrations_interface.dry.list_inventory(integration['id'], params)
966
+ return 0, nil
967
+ end
968
+ json_response = @integrations_interface.list_inventory(integration['id'], params)
969
+ render_response(json_response, options, integration_inventory_list_key) do
970
+ integration_inventory = json_response[integration_inventory_list_key]
971
+ print_h1 "Integration Inventory [#{integration['name']}]", parse_list_subtitles(options), options
972
+ if integration_inventory.empty?
973
+ print cyan,"No inventory found.",reset,"\n"
974
+ else
975
+ list_columns = {
976
+ "ID" => 'id',
977
+ "Name" => 'name',
978
+ "Description" => 'description',
979
+ "Tenant Default" => lambda {|it| (format_list(it['tenants'].collect {|t| t['name'] }) rescue "") },
980
+ }
981
+ print as_pretty_table(integration_inventory, list_columns.upcase_keys!, options)
982
+ print_results_pagination(json_response)
983
+ end
984
+ print reset,"\n"
985
+ end
986
+ return 0, nil
987
+ end
988
+
989
+ def get_inventory(args)
990
+ params = {}
991
+ options = {}
992
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
993
+ opts.banner = subcommand_usage("[integration] [inventory]")
994
+ build_standard_get_options(opts, options)
995
+ opts.footer = <<-EOT
996
+ Get details about a specific integration inventory item.
997
+ [integration] is required. This is the name or id of an integration.
998
+ [inventory] is required. This is the name or id of an integration inventory item.
999
+ Only certain types of integrations support this operation, such as Ansible Tower.
1000
+ EOT
1001
+ end
1002
+ optparse.parse!(args)
1003
+ verify_args!(args:args, optparse:optparse, min:2)
1004
+ connect(options)
1005
+ integration = find_integration_by_name_or_id(args[0])
1006
+ return 1, "integration not found for #{args[0]}" if integration.nil?
1007
+ params.merge!(parse_query_options(options))
1008
+ id_list = parse_id_list(args[1..-1])
1009
+ return run_command_for_each_arg(id_list) do |arg|
1010
+ _get_inventory(integration, arg, params, options)
1011
+ end
1012
+ end
1013
+
1014
+ def _get_inventory(integration, id, params, options)
1015
+ integration_object = nil
1016
+ if id.to_s !~ /\A\d{1,}\Z/
1017
+ integration_inventory = find_integration_inventory_by_name_or_id(integration['id'], id)
1018
+ return 1, "integration inventory not found for #{id}" if integration_inventory.nil?
1019
+ id = integration_inventory['id']
1020
+ end
1021
+ @integrations_interface.setopts(options)
1022
+ if options[:dry_run]
1023
+ print_dry_run @integrations_interface.dry.get_inventory(integration['id'], id, params)
1024
+ return
1025
+ end
1026
+ json_response = @integrations_interface.get_inventory(integration['id'], id, params)
1027
+ integration_inventory = json_response[integration_inventory_object_key]
1028
+ render_response(json_response, options, integration_inventory_object_key) do
1029
+ print_h1 "Integration Inventory Details", [], options
1030
+ print cyan
1031
+ show_columns = {
1032
+ "ID" => 'id',
1033
+ "Name" => 'name',
1034
+ "Description" => 'description',
1035
+ "Tenant Default" => lambda {|it| (format_list(it['tenants'].collect {|t| t['name'] }) rescue "") },
1036
+ }
1037
+ print_description_list(show_columns, integration_inventory, options)
1038
+ print reset,"\n"
1039
+ end
1040
+ return 0, nil
1041
+ end
1042
+
1043
+ def update_inventory(args)
1044
+ options = {}
1045
+ params = {}
1046
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1047
+ opts.banner = subcommand_usage("[integration] [inventory] [options]")
1048
+ opts.on('--tenants [LIST]', String, "Tenant Default, comma separated list of account IDs") do |val|
1049
+ options[:tenants] = parse_array(val)
1050
+ end
1051
+ build_standard_update_options(opts, options)
1052
+ opts.footer = <<-EOT
1053
+ Update an integration inventory item.
1054
+ [integration] is required. This is the name or id of an integration.
1055
+ [inventory] is required. This is the name or id of an integration inventory item.
1056
+ Only certain types of integrations support this operation, such as Ansible Tower.
1057
+ EOT
1058
+ end
1059
+ optparse.parse!(args)
1060
+ verify_args!(args:args, optparse:optparse, count:2)
1061
+ connect(options)
1062
+ integration = find_integration_by_name_or_id(args[0])
1063
+ return 1, "integration not found for #{args[0]}" if integration.nil?
1064
+ integration_inventory = find_integration_inventory_by_name_or_id(integration['id'], args[1])
1065
+ return 1, "integration inventory not found for #{args[1]}" if integration_inventory.nil?
1066
+ # construct payload
1067
+ object_key = integration_inventory_object_key
1068
+ payload = build_payload(options, object_key)
1069
+ if options[:tenants]
1070
+ #params['tenants'] = options[:tenants]
1071
+ params['tenants'] = options[:tenants].collect do |val|
1072
+ if val.to_s =~ /\A\d{1,}\Z/
1073
+ val.to_i
1074
+ else
1075
+ # todo: use /api/options/allTenants to avoid permission errors here..
1076
+ record = find_by_name_or_id(:account, val)
1077
+ if record.nil?
1078
+ exit 1 #return 1, "Tenant not found by '#{val}'"
1079
+ else
1080
+ record['id']
1081
+ end
1082
+ end
1083
+ end
1084
+ end
1085
+ payload.deep_merge!({object_key => params})
1086
+ if payload.empty? || payload[object_key].empty?
1087
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
1088
+ end
1089
+ # make request
1090
+ @integrations_interface.setopts(options)
1091
+ if options[:dry_run]
1092
+ print_dry_run @integrations_interface.dry.update_inventory(integration['id'], integration_inventory['id'], payload)
1093
+ return
1094
+ end
1095
+ json_response = @integrations_interface.update_inventory(integration['id'], integration_inventory['id'], payload)
1096
+ integration_inventory = json_response[object_key]
1097
+ render_response(json_response, options, object_key) do
1098
+ print_green_success "Updated integration inventory #{integration_inventory['name']}"
1099
+ # return _get_inventory(integration, integration_inventory["id"], {}, options)
1100
+ end
1101
+ return 0, nil
1102
+ end
1103
+
939
1104
  private
940
1105
 
941
1106
  def format_integration_type(integration)
@@ -999,7 +1164,7 @@ EOT
999
1164
  return json_response[integration_object_key]
1000
1165
  rescue RestClient::Exception => e
1001
1166
  if e.response && e.response.code == 404
1002
- print_red_alert "integration not found by id '#{id}'"
1167
+ print_red_alert "Integration not found by id '#{id}'"
1003
1168
  else
1004
1169
  raise e
1005
1170
  end
@@ -1010,7 +1175,7 @@ EOT
1010
1175
  json_response = @integrations_interface.list({name: name.to_s})
1011
1176
  integrations = json_response[integration_list_key]
1012
1177
  if integrations.empty?
1013
- print_red_alert "integration not found by name '#{name}'"
1178
+ print_red_alert "Integration not found by name '#{name}'"
1014
1179
  return nil
1015
1180
  elsif integrations.size > 1
1016
1181
  print_red_alert "#{integrations.size} integrations found by name '#{name}'"
@@ -1075,7 +1240,7 @@ EOT
1075
1240
  return json_response[integration_object_key]
1076
1241
  rescue RestClient::Exception => e
1077
1242
  if e.response && e.response.code == 404
1078
- print_red_alert "integration not found by id '#{id}'"
1243
+ print_red_alert "Integration not found by id '#{id}'"
1079
1244
  else
1080
1245
  raise e
1081
1246
  end
@@ -1086,7 +1251,7 @@ EOT
1086
1251
  json_response = @integration_types_interface.list(params.merge({name: name.to_s}))
1087
1252
  integration_types = json_response[integration_type_list_key]
1088
1253
  if integration_types.empty?
1089
- print_red_alert "integration type not found by name '#{name}'"
1254
+ print_red_alert "Integration type not found by name '#{name}'"
1090
1255
  return nil
1091
1256
  elsif integration_types.size > 1
1092
1257
  print_red_alert "#{integration_types.size} integration types found by name '#{name}'"
@@ -1196,4 +1361,50 @@ EOT
1196
1361
  end
1197
1362
  end
1198
1363
 
1364
+ def integration_inventory_object_key
1365
+ 'inventory'
1366
+ end
1367
+
1368
+ def integration_inventory_list_key
1369
+ 'inventory'
1370
+ end
1371
+
1372
+ def find_integration_inventory_by_name_or_id(integration_id, val)
1373
+ if val.to_s =~ /\A\d{1,}\Z/
1374
+ return find_integration_inventory_by_id(integration_id, val)
1375
+ else
1376
+ return find_integration_inventory_by_name(integration_id, val)
1377
+ end
1378
+ end
1379
+
1380
+ def find_integration_inventory_by_id(integration_id, id)
1381
+ begin
1382
+ json_response = @integrations_interface.get_inventory(integration_id, id.to_i)
1383
+ return json_response[integration_inventory_object_key]
1384
+ rescue RestClient::Exception => e
1385
+ if e.response && e.response.code == 404
1386
+ print_red_alert "Inventory not found by id '#{id}'"
1387
+ else
1388
+ raise e
1389
+ end
1390
+ end
1391
+ end
1392
+
1393
+ def find_integration_inventory_by_name(integration_id, name)
1394
+ json_response = @integrations_interface.list_inventory(integration_id, {name: name.to_s})
1395
+ integration_inventory = json_response[integration_inventory_list_key]
1396
+ if integration_inventory.empty?
1397
+ print_red_alert "Inventory not found by name '#{name}'"
1398
+ return nil
1399
+ elsif integration_inventory.size > 1
1400
+ print_red_alert "#{integration_inventory.size} inventory found by name '#{name}'"
1401
+ puts_error as_pretty_table(integration_inventory, [:id, :name], {color:red})
1402
+ print_red_alert "Try using ID instead"
1403
+ print reset,"\n"
1404
+ return nil
1405
+ else
1406
+ return integration_inventory[0]
1407
+ end
1408
+ end
1409
+
1199
1410
  end