morpheus-cli 5.5.1.4 → 5.5.2

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 (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
@@ -7,6 +7,7 @@ class Morpheus::Cli::Clouds
7
7
 
8
8
  register_subcommands :list, :count, :get, :add, :update, :remove, :refresh, :security_groups, :apply_security_groups, :types => :list_cloud_types
9
9
  register_subcommands :wiki, :update_wiki
10
+ register_subcommands({:'update-logo' => :update_logo,:'update-dark-logo' => :update_dark_logo})
10
11
  #register_subcommands :firewall_disable, :firewall_enable
11
12
  alias_subcommand :details, :get
12
13
  set_default_subcommand :list
@@ -342,6 +343,10 @@ class Morpheus::Cli::Clouds
342
343
  opts.on('--credential VALUE', String, "Credential ID or \"local\"" ) do |val|
343
344
  options[:options]['credential'] = val
344
345
  end
346
+ opts.on('--default-cloud-logos', "Reset logos to default cloud logos, removing any custom logo and dark logo" ) do
347
+ options[:options]['defaultCloudLogos'] = true
348
+ end
349
+
345
350
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
346
351
  end
347
352
  optparse.parse!(args)
@@ -416,6 +421,9 @@ class Morpheus::Cli::Clouds
416
421
  query_params = {}
417
422
  optparse = Morpheus::Cli::OptionParser.new do |opts|
418
423
  opts.banner = subcommand_usage("[name]")
424
+ opts.on('--remove-resources [on|off]', ['on','off'], "Remove Associated Resources. Default is off.") do |val|
425
+ query_params[:removeResources] = val.nil? ? 'on' : val
426
+ end
419
427
  opts.on( '-f', '--force', "Force Remove" ) do
420
428
  query_params[:force] = 'on'
421
429
  end
@@ -953,6 +961,106 @@ class Morpheus::Cli::Clouds
953
961
  end
954
962
  end
955
963
 
964
+ def update_logo(args)
965
+ options = {}
966
+ params = {}
967
+ filename = nil
968
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
969
+ opts.banner = subcommand_usage("[name] [file]")
970
+ build_common_options(opts, options, [:json, :dry_run, :remote])
971
+ opts.footer = <<-EOT
972
+ Update the logo for a cloud.
973
+ [name] is required. This is the name or id of a cloud.
974
+ [file] is required. This is the path of the logo file
975
+ EOT
976
+ end
977
+ optparse.parse!(args)
978
+ verify_args!(args:args, optparse:optparse, count:2)
979
+ connect(options)
980
+ layout_id = args[0]
981
+ filename = args[1]
982
+ begin
983
+ cloud = find_cloud_by_name_or_id(args[0])
984
+ return 1 if cloud.nil?
985
+ logo_file = nil
986
+ if filename == 'null'
987
+ logo_file = 'null' # clear it
988
+ else
989
+ filename = File.expand_path(filename)
990
+ if !File.exists?(filename)
991
+ print_red_alert "File not found: #{filename}"
992
+ exit 1
993
+ end
994
+ logo_file = File.new(filename, 'rb')
995
+ end
996
+ @clouds_interface.setopts(options)
997
+ if options[:dry_run]
998
+ print_dry_run @clouds_interface.dry.update_logo(cloud['id'], logo_file)
999
+ return
1000
+ end
1001
+ json_response = @clouds_interface.update_logo(cloud['id'], logo_file)
1002
+ if options[:json]
1003
+ print JSON.pretty_generate(json_response), "\n"
1004
+ return 0
1005
+ end
1006
+ print_green_success "Updated cloud #{cloud['name']} logo"
1007
+ _get(cloud['id'], params, options)
1008
+ rescue RestClient::Exception => e
1009
+ print_rest_exception(e, options)
1010
+ return 1
1011
+ end
1012
+ end
1013
+
1014
+ def update_dark_logo(args)
1015
+ options = {}
1016
+ params = {}
1017
+ filename = nil
1018
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
1019
+ opts.banner = subcommand_usage("[name] [file]")
1020
+ build_common_options(opts, options, [:json, :dry_run, :remote])
1021
+ opts.footer = <<-EOT
1022
+ Update the logo for a cloud.
1023
+ [name] is required. This is the name or id of a cloud.
1024
+ [file] is required. This is the path of the dark logo file
1025
+ EOT
1026
+ end
1027
+ optparse.parse!(args)
1028
+ verify_args!(args:args, optparse:optparse, count:2)
1029
+ connect(options)
1030
+ layout_id = args[0]
1031
+ filename = args[1]
1032
+ begin
1033
+ cloud = find_cloud_by_name_or_id(args[0])
1034
+ return 1 if cloud.nil?
1035
+ dark_logo_file = nil
1036
+ if filename == 'null'
1037
+ dark_logo_file = 'null' # clear it
1038
+ else
1039
+ filename = File.expand_path(filename)
1040
+ if !File.exists?(filename)
1041
+ print_red_alert "File not found: #{filename}"
1042
+ exit 1
1043
+ end
1044
+ dark_logo_file = File.new(filename, 'rb')
1045
+ end
1046
+ @clouds_interface.setopts(options)
1047
+ if options[:dry_run]
1048
+ print_dry_run @clouds_interface.dry.update_logo(cloud['id'], nil, dark_logo_file)
1049
+ return
1050
+ end
1051
+ json_response = @clouds_interface.update_logo(cloud['id'], nil, dark_logo_file)
1052
+ if options[:json]
1053
+ print JSON.pretty_generate(json_response), "\n"
1054
+ return 0
1055
+ end
1056
+ print_green_success "Updated cloud #{cloud['name']} dark logo"
1057
+ _get(cloud['id'], params, options)
1058
+ rescue RestClient::Exception => e
1059
+ print_rest_exception(e, options)
1060
+ return 1
1061
+ end
1062
+ end
1063
+
956
1064
  private
957
1065
 
958
1066
  def cloud_list_column_definitions(options)
@@ -986,7 +1094,7 @@ class Morpheus::Cli::Clouds
986
1094
 
987
1095
  if cloud_type && cloud_type['optionTypes']
988
1096
  if !cloud_type['optionTypes'].find {|opt| opt['type'] == 'credential'}
989
- tmp_option_types << {'fieldName' => 'type', 'fieldLabel' => 'Credentials', 'type' => 'credential', 'optionSource' => 'credentials', 'required' => true, 'defaultValue' => 'local', 'config' => {'credentialTypes' => ['username-password']}, 'displayOrder' => 7}
1097
+ tmp_option_types << {'fieldName' => 'type', 'fieldLabel' => 'Credentials', 'type' => 'credential', 'optionSource' => 'credentials', 'required' => true, 'defaultValue' => 'local', 'config' => {'credentialTypes' => get_cloud_type_credential_types(cloud_type['code'])}, 'displayOrder' => 7}
990
1098
  cloud_type['optionTypes'].select {|opt| ['username', 'password', 'serviceUsername', 'servicePassword'].include?(opt['fieldName'])}.each {|opt| opt['localCredential'] = true}
991
1099
  end
992
1100
  # adjust displayOrder to put these at the end
@@ -1043,4 +1151,22 @@ class Morpheus::Cli::Clouds
1043
1151
  {'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'textarea', 'required' => false, 'displayOrder' => 3, 'description' => 'The content (markdown) of the wiki page.'}
1044
1152
  ]
1045
1153
  end
1154
+
1155
+ def get_cloud_type_credential_types(cloud_type_code)
1156
+ case cloud_type_code
1157
+ when "amazon", "alibaba"
1158
+ ['access-key-secret']
1159
+ when "azure","azurestack"
1160
+ ['client-id-secret']
1161
+ when "google"
1162
+ ['email-private-key']
1163
+ when "softlayer"
1164
+ ['username-api-key']
1165
+ when "digitalocean"
1166
+ ['username-api-key']
1167
+ else
1168
+ ['username-password']
1169
+ end
1170
+ end
1171
+
1046
1172
  end
@@ -51,8 +51,15 @@ class Morpheus::Cli::Clusters
51
51
 
52
52
  def list(args)
53
53
  options = {}
54
+ params = {}
54
55
  optparse = Morpheus::Cli::OptionParser.new do |opts|
55
56
  opts.banner = subcommand_usage()
57
+ opts.on('-l', '--labels LABEL', String, "Filter by labels, can match any of the values") do |val|
58
+ add_query_parameter(params, 'labels', parse_labels(val))
59
+ end
60
+ opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
61
+ add_query_parameter(params, 'allLabels', parse_labels(val))
62
+ end
56
63
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
57
64
  opts.footer = "List clusters."
58
65
  end
@@ -62,7 +69,6 @@ class Morpheus::Cli::Clusters
62
69
  end
63
70
  connect(options)
64
71
  begin
65
- params = {}
66
72
  params.merge!(parse_list_options(options))
67
73
  @clusters_interface.setopts(options)
68
74
  if options[:dry_run]
@@ -203,6 +209,8 @@ class Morpheus::Cli::Clusters
203
209
  description_cols = {
204
210
  "ID" => 'id',
205
211
  "Name" => 'name',
212
+ "Description" => 'description',
213
+ "Labels" => lambda {|it| format_list(it['labels']) rescue '' },
206
214
  "Type" => lambda { |it| it['type']['name'] },
207
215
  #"Group" => lambda { |it| it['site']['name'] },
208
216
  "Cloud" => lambda { |it| it['zone']['name'] },
@@ -373,15 +381,19 @@ class Morpheus::Cli::Clusters
373
381
  opts.on( '--resource-name NAME', "Resource Name" ) do |val|
374
382
  options[:resourceName] = val.to_s
375
383
  end
376
- opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
384
+ opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang' (sets server tags only)") do |val|
377
385
  options[:metadata] = val
378
386
  end
379
- opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
387
+ opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang' (sets server tags only)") do |val|
380
388
  options[:metadata] = val
381
389
  end
382
390
  opts.add_hidden_option('--metadata')
383
- opts.on('--labels LIST', String, "Tags") do |val|
384
- options[:labels] = val
391
+ opts.on('-l', '--labels [LIST]', String, "Labels (sets both cluster and server)") do |val|
392
+ options[:labels] = parse_labels(val)
393
+ options[:resource_labels] ||= options[:labels]
394
+ end
395
+ opts.on('--resource-labels [LIST]', String, "Resource Labels (override server labels)") do |val|
396
+ options[:resource_labels] = parse_labels(val)
385
397
  end
386
398
  opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
387
399
  options[:group] = val
@@ -439,6 +451,13 @@ class Morpheus::Cli::Clusters
439
451
  if options[:description]
440
452
  payload['cluster']['description'] = options[:description]
441
453
  end
454
+ if options[:labels]
455
+ payload['cluster']['labels'] = options[:labels]
456
+ end
457
+ if options[:resource_labels]
458
+ payload['cluster']['server'] ||= {}
459
+ payload['cluster']['server']['labels'] = options[:resource_labels]
460
+ end
442
461
  else
443
462
  cluster_payload = {}
444
463
  server_payload = {"config" => {}}
@@ -517,13 +536,20 @@ class Morpheus::Cli::Clusters
517
536
  description = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false, 'description' => 'Resource Description.'}],options[:options],@api_client,{})['description']
518
537
  cluster_payload['description'] = description if description
519
538
 
539
+ # Labels
520
540
  labels = options[:labels]
521
-
522
541
  if !labels && !options[:no_prompt]
523
- labels = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'type' => 'text', 'fieldLabel' => 'Resource Labels', 'required' => false, 'description' => 'Resource Labels.'}],options[:options],@api_client,{})['labels']
542
+ labels = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'type' => 'text', 'fieldLabel' => 'Labels', 'required' => false, 'description' => 'Resource Labels.'}],options[:options],@api_client,{})['labels']
543
+ end
544
+ if labels
545
+ cluster_payload['labels'] = labels
546
+ server_payload['labels'] = labels
524
547
  end
525
548
 
526
- server_payload['labels'] = labels if labels
549
+ # --resource-labels to override
550
+ if options[:resource_labels]
551
+ server_payload['labels'] = options[:resource_labels]
552
+ end
527
553
 
528
554
  # Cloud / Zone
529
555
  cloud_id = nil
@@ -733,6 +759,9 @@ class Morpheus::Cli::Clusters
733
759
  opts.on("--description [TEXT]", String, "Updates Cluster Description") do |val|
734
760
  options[:description] = val.to_s
735
761
  end
762
+ opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
763
+ options[:labels] = parse_labels(val)
764
+ end
736
765
  opts.on("--api-url [TEXT]", String, "Updates Cluster API Url") do |val|
737
766
  options[:apiUrl] = val.to_s
738
767
  end
@@ -782,6 +811,7 @@ class Morpheus::Cli::Clusters
782
811
  cluster_payload = {}
783
812
  cluster_payload['name'] = options[:name] if !options[:name].empty?
784
813
  cluster_payload['description'] = options[:description] if !options[:description].empty?
814
+ cluster_payload['labels'] = options[:labels] if !options[:labels].nil?
785
815
  cluster_payload['enabled'] = options[:active] if !options[:active].nil?
786
816
  cluster_payload['managed'] = options[:managed] if !options[:managed].nil?
787
817
  cluster_payload['serviceUrl'] = options[:apiUrl] if !options[:apiUrl].nil?
@@ -1135,8 +1165,8 @@ class Morpheus::Cli::Clusters
1135
1165
  options[:metadata] = val
1136
1166
  end
1137
1167
  opts.add_hidden_option('--metadata')
1138
- opts.on('--labels LIST', String, "Tags") do |val|
1139
- options[:labels] = val
1168
+ opts.on('-l', '--labels [LIST]', String, "Labels") do |val|
1169
+ options[:labels] = parse_labels(val)
1140
1170
  end
1141
1171
  add_server_options(opts, options)
1142
1172
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
@@ -3630,7 +3660,7 @@ class Morpheus::Cli::Clusters
3630
3660
  rows = clusters.collect do |cluster|
3631
3661
  {
3632
3662
  id: cluster['id'],
3633
- display_name: cluster['displayName'],
3663
+ labels: truncate_string(format_list(cluster['labels']), 30),
3634
3664
  name: cluster['name'],
3635
3665
  type: (cluster['type']['name'] rescue ''),
3636
3666
  layout: (cluster['layout']['name'] rescue ''),
@@ -3640,7 +3670,7 @@ class Morpheus::Cli::Clusters
3640
3670
  }
3641
3671
  end
3642
3672
  columns = [
3643
- :id, :name, :display_name, :type, :layout, :workers, :cloud, :status
3673
+ :id, :name, :labels, :type, :layout, :workers, :cloud, :status
3644
3674
  ]
3645
3675
  print as_pretty_table(rows, columns, opts)
3646
3676
  end
@@ -3,183 +3,162 @@ require 'morpheus/cli/cli_command'
3
3
  class Morpheus::Cli::CurlCommand
4
4
  include Morpheus::Cli::CliCommand
5
5
  set_command_name :curl
6
- set_command_hidden
7
6
 
8
7
  def handle(args)
9
- # support syntax for arbitrary curl args after " -- "
10
- # eg. curl /api/instances -- -ksv
11
- split_index = args.index("--")
12
- curl_args = []
13
- if split_index
14
- if args.length > (split_index + 1)
15
- curl_args = args[(split_index + 1)..-1]
16
- end
17
- args = args[0..(split_index - 1)]
18
- end
19
8
  curl_method = nil
20
9
  curl_data = nil
21
- curl_verbsose = false
22
10
  show_progress = false
23
11
  options = {}
24
12
  optparse = Morpheus::Cli::OptionParser.new do|opts|
25
- opts.banner = "Usage: morpheus curl [path] -- [*args]"
13
+ opts.banner = "Usage: morpheus curl [path]"
26
14
  opts.on( '-p', '--pretty', "Print result as parsed JSON. Alias for -j" ) do
27
15
  options[:json] = true
28
16
  end
29
17
  opts.on( '-X', '--request METHOD', "HTTP request method. Default is GET" ) do |val|
30
18
  curl_method = val
31
19
  end
32
- opts.on( '-v', '--verbose', "Print verbose output." ) do
33
- curl_verbsose = true
20
+ opts.on( '--post', "Set the HTTP request method to POST" ) do
21
+ curl_method = "POST"
22
+ end
23
+ opts.on( '--put', "Set the HTTP request method to PUT" ) do
24
+ curl_method = "POST"
25
+ end
26
+ opts.on( '--delete', "Set the HTTP request method to DELETE" ) do
27
+ curl_method = "DELETE"
34
28
  end
35
29
  opts.on( '--data DATA', String, "HTTP request body for use with POST and PUT, typically JSON." ) do |val|
36
30
  curl_data = val
37
31
  end
38
- opts.on( '--progress', '--progress', "Display progress output by excluding the -s option." ) do
39
- show_progress = true
32
+ opts.on('--absolute', "Absolute path, value can be used to prevent automatic using the automatic /api/ path prefix to the path by default.") do
33
+ options[:absolute_path] = true
34
+ end
35
+ opts.on('--inspect', "Inspect response, prints headers. By default only the body is printed.") do
36
+ options[:inspect_response] = true
40
37
  end
41
- build_common_options(opts, options, [:dry_run, :json, :remote])
42
- opts.add_hidden_option('--curl')
43
- #opts.add_hidden_option('--scrub')
38
+ build_standard_api_options(opts, options)
44
39
  opts.footer = <<-EOT
45
- This invokes the `curl` command with url "appliance_url/$0
46
- and includes the authorization header -H "Authorization: Bearer access_token"
47
- Arguments for the curl command should be passed after ' -- '
48
- Example: morpheus curl "/api/servers/1" -- -XGET -sv
40
+ Execute an HTTP request against the remote appliance api to an arbitrary path.
41
+ [path] is required. This is the path to path to request. By default
42
+ By default the "/api" prefix is included in the request path.
43
+ The --absolute option ban be used to supress this.
44
+
45
+ Examples:
46
+ morpheus curl "/api/whoami"
47
+ morpheus curl whoami
48
+ morpheus curl apps -r demo
49
49
 
50
50
  EOT
51
51
  end
52
+
52
53
  optparse.parse!(args)
53
- if args.count < 1
54
- puts optparse
55
- return false
56
- end
57
-
58
- if !command_available?("curl")
59
- print "#{red}The 'curl' command is not available on your system.#{reset}\n"
60
- return false
61
- end
54
+ verify_args!(args:args, optparse:optparse, count: 1)
62
55
 
56
+ # establish api client with connection, skips verification so check for appliance is done afterwards
63
57
  @api_client = establish_remote_appliance_connection(options.merge({:no_prompt => true, :skip_verify_access_token => true, :skip_login => true}))
64
-
65
58
  if !@appliance_name
66
59
  raise_command_error "#{command_name} requires a remote to be specified, use -r [remote] or set the active remote with `remote use`"
67
60
  end
68
61
 
69
- # curry --insecure to curl
70
- if options[:insecure] || !Morpheus::RestClient.ssl_verification_enabled?
71
- #curl_args.unshift "-k"
72
- curl_args.unshift "--insecure"
73
- end
74
-
75
- if !@appliance_url
76
- raise "Unable to determine remote appliance url"
77
- print "#{red}Unable to determine remote appliance url.#{reset}\n"
78
- return false
79
- end
80
-
81
- # determine curl url
82
- url = nil
62
+ # determine curl url, base_url is automatically applied
83
63
  api_path = args[0].to_s.strip
84
- # allow absolute path for the current appliance url only
85
- if api_path.match(/^#{Regexp.escape(@appliance_url)}/)
86
- url = api_path
64
+ # by default /api/ prefix is prepended
65
+ if options[:absolute_path] || api_path.start_with?("http:") || api_path.start_with?("https:")
66
+ api_path = api_path
87
67
  else
88
- api_path = api_path.sub(/^\//, "") # strip leading slash
89
- url = "#{@appliance_url.chomp('/')}/#{api_path}"
90
- end
91
- curl_cmd = "curl"
92
- if show_progress == false
93
- curl_cmd << " -s"
94
- end
95
- if curl_verbsose
96
- curl_cmd << " -v"
68
+ api_path = "/#{api_path}" unless api_path.start_with?("/")
69
+ api_path = "/api#{api_path}" unless api_path.start_with?("/api")
97
70
  end
98
- if curl_method
99
- curl_cmd << " -X#{curl_method}"
100
- end
101
- curl_cmd << " \"#{url}\""
102
- if @access_token
103
- if !(options[:headers] && options[:headers]['Authorization'])
104
- curl_cmd << " -H \"Authorization: Bearer #{@access_token}\""
105
- end
106
- end
107
- if curl_data
108
- #todo: curl_data.gsub("'","\\'")
109
- curl_cmd << " --data '#{curl_data}'"
110
- if api_path !~ /^\/?oauth/
111
- if !(options[:headers] && options[:headers]['Content-Type'])
112
- curl_cmd << " -H \"Content-Type: application/json\""
113
- end
114
- end
115
- end
116
- if options[:headers]
117
- options[:headers].each do |k,v|
118
- curl_cmd << " -H \"#{k}: #{v}\""
119
- end
120
- end
121
- if !curl_args.empty?
122
- curl_cmd << " " + curl_args.join(' ')
123
- end
124
- # Morpheus::Logging::DarkPrinter.puts "#{curl_cmd}" if Morpheus::Logging.debug?
125
- curl_cmd_str = options[:scrub] ? Morpheus::Logging.scrub_message(curl_cmd) : curl_cmd
71
+
72
+ # build query parameters from --query k=v
73
+ query_params = parse_query_options(options)
74
+
75
+ # build payload from --payload '{}' and --option k=v
76
+ payload = parse_payload(options)
126
77
 
78
+ request_opts = {}
79
+ request_opts[:method] = curl_method ? curl_method.to_s.downcase.to_sym : :get
80
+ request_opts[:url] = api_path
81
+ request_opts[:headers] = options[:headers] if options[:headers]
82
+ request_opts[:params] = query_params
83
+ request_opts[:payload] = payload # if [:post, :put].include?(request_opts[:method])
84
+ request_opts[:parse_json] = false
85
+ @api_client.setopts(options)
127
86
  if options[:dry_run]
128
- print cyan
129
- print "#{cyan}#{curl_cmd_str}#{reset}"
130
- print "\n\n"
131
- print reset
132
- return 0
87
+ print_dry_run @api_client.dry.execute(request_opts)
88
+ return
133
89
  end
134
- exit_code, err = 0, nil
135
- # print cyan
136
- # print "#{cyan}#{curl_cmd_str}#{reset}"
137
- # print "\n\n"
138
- print reset
139
- # print result
140
- curl_output = `#{curl_cmd}`
90
+ api_response = nil
91
+ json_response = nil
92
+ begin
93
+ api_response = @api_client.execute(request_opts)
94
+ rescue ::RestClient::Exception => e
141
95
 
142
- if $?.success? != true
143
- exit_code = $?.exitstatus
144
- err = "curl command exited non-zero"
145
- end
146
- json_response = {}
147
- other_output = nil
148
- if options[:json] || options[:yaml] || options[:csv]
149
- output_lines = curl_output.split("\n")
150
- last_line = output_lines.pop
151
- if output_lines.size > 0
152
- other_output = output_lines.join("\n")
96
+ exit_code = 1
97
+ err = e.message
98
+ #raise e
99
+ api_response = e.response
100
+ # did not get a response?
101
+ if api_response.nil?
102
+ print_rest_exception(e, options)
103
+ return 1, e.message
153
104
  end
105
+ end
106
+ if api_response.nil?
107
+ print_rest_exception(e, options)
108
+ return 1, e.message
109
+ end
110
+ response_is_ok = (api_response.code.to_i >= 200 && api_response.code.to_i < 400)
111
+ response_is_json = api_response.headers[:content_type].to_s.start_with?("application/json")
112
+ if response_is_json && options[:inspect_response] != true
113
+ # render as json by default, so -f just works
114
+ # options[:json] = true unless options[:csv] || options[:yaml]
154
115
  begin
155
- json_response = JSON.parse(last_line)
156
- rescue => ex
157
- puts_error curl_output
158
- print_red_alert "failed to parse curl result as JSON data Error: #{ex.message}"
159
-
160
- exit_code = 2
161
- err = "failed to parse curl result as JSON data Error: #{ex.message}"
162
- return exit_code, err
116
+ json_response = JSON.parse(api_response.body.to_s)
117
+ rescue => e
118
+ puts_error "Failed to parse response as JSON. Error: #{e}"
119
+ # json_response = {}
120
+ end
121
+ # this should be default behavior, but use the first key if it is a Hash or Array
122
+ object_key = nil
123
+ if json_response && json_response.keys.first && [Hash,Array].include?(json_response[json_response.keys.first].class)
124
+ object_key = json_response.keys.first
125
+ end
126
+ render_response(json_response, options, object_key) do
127
+ output = ""
128
+ output << red if !response_is_ok
129
+ # just render the json by default, non pretty..
130
+ output << JSON.fast_generate(json_response)
131
+ output << "\n"
132
+ output << reset
133
+ if exit_code == 1
134
+ print output
135
+ elsif
136
+ print_error output
137
+ end
163
138
  end
164
139
  else
165
- other_output = curl_output
166
- end
167
- curl_object_key = nil # json_response.keys.first
168
- render_response(json_response, options, curl_object_key) do
169
- puts other_output
140
+ output = ""
141
+ output << red if !response_is_ok
142
+ if options[:inspect_response]
143
+ # instead http response (version and headers)
144
+ output << "HTTP/#{api_response.net_http_res.http_version} #{api_response.code}\n"
145
+ api_response.net_http_res.each_capitalized.each do |k,v|
146
+ output << "#{k}: #{v}\n"
147
+ end
148
+ output << "\n"
149
+ output << api_response.body.to_s
150
+ else
151
+ output << api_response.body.to_s
152
+ end
153
+ output << "\n"
154
+ output << reset
155
+ if exit_code == 0
156
+ print output
157
+ elsif
158
+ print_error output
159
+ end
170
160
  end
171
161
  return exit_code, err
172
162
  end
173
163
 
174
- def command_available?(cmd)
175
- has_it = false
176
- begin
177
- system("which #{cmd} > /dev/null 2>&1")
178
- has_it = $?.success?
179
- rescue => e
180
- raise e
181
- end
182
- return has_it
183
- end
184
-
185
164
  end