morpheus-cli 5.5.1.5 → 5.5.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +25 -0
- data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
- data/lib/morpheus/api/body_io.rb +22 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +5 -1
- data/lib/morpheus/api/clients_interface.rb +41 -0
- data/lib/morpheus/api/clouds_interface.rb +21 -0
- data/lib/morpheus/api/instances_interface.rb +8 -1
- data/lib/morpheus/api/integrations_interface.rb +30 -0
- data/lib/morpheus/api/library_instance_types_interface.rb +15 -3
- data/lib/morpheus/api/network_pool_server_types_interface.rb +9 -0
- data/lib/morpheus/api/plugins_interface.rb +22 -0
- data/lib/morpheus/api/roles_interface.rb +20 -1
- data/lib/morpheus/api/security_package_types_interface.rb +9 -0
- data/lib/morpheus/api/security_packages_interface.rb +9 -0
- data/lib/morpheus/api/security_scans_interface.rb +9 -0
- data/lib/morpheus/api/servers_interface.rb +17 -17
- data/lib/morpheus/api/storage_providers_interface.rb +1 -1
- data/lib/morpheus/api/virtual_images_interface.rb +1 -23
- data/lib/morpheus/cli/cli_command.rb +81 -7
- data/lib/morpheus/cli/commands/apps.rb +28 -2
- data/lib/morpheus/cli/commands/archives_command.rb +2 -2
- data/lib/morpheus/cli/commands/blueprints_command.rb +16 -0
- data/lib/morpheus/cli/commands/catalog_item_types_command.rb +34 -2
- data/lib/morpheus/cli/commands/clients_command.rb +338 -0
- data/lib/morpheus/cli/commands/clouds.rb +127 -1
- data/lib/morpheus/cli/commands/clusters.rb +42 -12
- data/lib/morpheus/cli/commands/curl_command.rb +114 -135
- data/lib/morpheus/cli/commands/hosts.rb +108 -11
- data/lib/morpheus/cli/commands/instances.rb +115 -14
- data/lib/morpheus/cli/commands/integrations_command.rb +215 -4
- data/lib/morpheus/cli/commands/invoices_command.rb +20 -11
- data/lib/morpheus/cli/commands/jobs_command.rb +299 -190
- data/lib/morpheus/cli/commands/library_cluster_layouts_command.rb +16 -2
- data/lib/morpheus/cli/commands/library_container_scripts_command.rb +14 -0
- data/lib/morpheus/cli/commands/library_container_templates_command.rb +131 -48
- data/lib/morpheus/cli/commands/library_container_types_command.rb +17 -4
- data/lib/morpheus/cli/commands/library_instance_types_command.rb +85 -7
- data/lib/morpheus/cli/commands/library_layouts_command.rb +32 -1
- data/lib/morpheus/cli/commands/library_option_lists_command.rb +30 -18
- data/lib/morpheus/cli/commands/library_option_types_command.rb +31 -14
- data/lib/morpheus/cli/commands/library_spec_templates_command.rb +14 -0
- data/lib/morpheus/cli/commands/library_upgrades_command.rb +2 -2
- data/lib/morpheus/cli/commands/network_pool_server_types.rb +20 -0
- data/lib/morpheus/cli/commands/network_pool_servers_command.rb +55 -158
- data/lib/morpheus/cli/commands/network_pools_command.rb +49 -23
- data/lib/morpheus/cli/commands/networks_command.rb +262 -45
- data/lib/morpheus/cli/commands/plugins.rb +213 -0
- data/lib/morpheus/cli/commands/price_sets_command.rb +27 -8
- data/lib/morpheus/cli/commands/prices_command.rb +17 -5
- data/lib/morpheus/cli/commands/processes_command.rb +2 -1
- data/lib/morpheus/cli/commands/remote.rb +7 -10
- data/lib/morpheus/cli/commands/roles.rb +924 -335
- data/lib/morpheus/cli/commands/search_command.rb +2 -0
- data/lib/morpheus/cli/commands/security_groups.rb +72 -84
- data/lib/morpheus/cli/commands/security_package_types.rb +32 -0
- data/lib/morpheus/cli/commands/security_packages.rb +84 -0
- data/lib/morpheus/cli/commands/security_scans.rb +107 -0
- data/lib/morpheus/cli/commands/service_plans_command.rb +16 -14
- data/lib/morpheus/cli/commands/subnets_command.rb +15 -1
- data/lib/morpheus/cli/commands/tasks.rb +34 -1
- data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
- data/lib/morpheus/cli/commands/user_settings_command.rb +11 -2
- data/lib/morpheus/cli/commands/users.rb +50 -9
- data/lib/morpheus/cli/commands/virtual_images.rb +14 -0
- data/lib/morpheus/cli/commands/workflows.rb +14 -0
- data/lib/morpheus/cli/mixins/accounts_helper.rb +6 -5
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +79 -0
- data/lib/morpheus/cli/mixins/jobs_helper.rb +4 -5
- data/lib/morpheus/cli/mixins/library_helper.rb +2 -0
- data/lib/morpheus/cli/mixins/logs_helper.rb +3 -0
- data/lib/morpheus/cli/mixins/monitoring_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +29 -4
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +38 -9
- data/lib/morpheus/cli/mixins/rest_command.rb +106 -8
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +6 -2
- data/lib/morpheus/cli/option_types.rb +94 -25
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +10 -1
- 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' => ['
|
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, "
|
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' => '
|
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
|
-
|
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, "
|
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
|
-
|
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, :
|
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]
|
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( '
|
33
|
-
|
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(
|
39
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
85
|
-
if api_path.
|
86
|
-
|
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.
|
89
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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(
|
156
|
-
rescue =>
|
157
|
-
puts_error
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|