morpheus-cli 6.1.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +8 -4
- data/lib/morpheus/api/backup_jobs_interface.rb +4 -0
- data/lib/morpheus/api/backup_restores_interface.rb +23 -0
- data/lib/morpheus/api/backup_results_interface.rb +28 -0
- data/lib/morpheus/api/backups_interface.rb +5 -4
- data/lib/morpheus/cli/cli_command.rb +172 -45
- data/lib/morpheus/cli/commands/appliance_settings_command.rb +7 -19
- data/lib/morpheus/cli/commands/apps.rb +1 -1
- data/lib/morpheus/cli/commands/backup_jobs_command.rb +77 -20
- data/lib/morpheus/cli/commands/backup_restores_command.rb +144 -0
- data/lib/morpheus/cli/commands/backup_results_command.rb +149 -0
- data/lib/morpheus/cli/commands/backups_command.rb +214 -93
- data/lib/morpheus/cli/commands/hosts.rb +15 -2
- data/lib/morpheus/cli/commands/instances.rb +23 -3
- data/lib/morpheus/cli/commands/load_balancer_pools.rb +37 -1
- data/lib/morpheus/cli/commands/security_groups.rb +58 -37
- data/lib/morpheus/cli/commands/service_catalog_command.rb +50 -83
- data/lib/morpheus/cli/commands/view.rb +20 -20
- data/lib/morpheus/cli/mixins/backups_helper.rb +58 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +27 -4
- data/lib/morpheus/cli/option_types.rb +10 -7
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +1 -1
- data/lib/morpheus/routes.rb +18 -3
- metadata +6 -8
- data/lib/morpheus/api/doc_interface.rb +0 -50
- data/lib/morpheus/cli/commands/doc.rb +0 -182
- data/test/api/doc_interface_test.rb +0 -35
- data/test/cli/doc_test.rb +0 -35
@@ -210,6 +210,7 @@ class Morpheus::Cli::SecurityGroups
|
|
210
210
|
params = {}
|
211
211
|
options = {:options => {}}
|
212
212
|
cloud_id = nil
|
213
|
+
resource_pool_id = nil
|
213
214
|
tenants = nil
|
214
215
|
group_access_all = nil
|
215
216
|
group_access_list = nil
|
@@ -225,6 +226,9 @@ class Morpheus::Cli::SecurityGroups
|
|
225
226
|
opts.on( '-c', '--cloud CLOUD', "Scoped Cloud Name or ID" ) do |val|
|
226
227
|
cloud_id = val
|
227
228
|
end
|
229
|
+
opts.on( '--resource-pool ID', String, "ID of the Resource Pool for Amazon VPC and Azure Resource Group" ) do |val|
|
230
|
+
resource_pool_id = val
|
231
|
+
end
|
228
232
|
opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
|
229
233
|
group_access_all = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
230
234
|
end
|
@@ -321,17 +325,20 @@ class Morpheus::Cli::SecurityGroups
|
|
321
325
|
if !v_prompt['zoneId'].to_s.empty? && v_prompt['zoneId'].to_s != 'all' && v_prompt['zoneId'].to_s != '-1'
|
322
326
|
payload['securityGroup']['zoneId'] = v_prompt['zoneId']
|
323
327
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
328
|
+
cloud = find_cloud_by_id(payload['securityGroup']['zoneId'])
|
329
|
+
|
330
|
+
# parse --resource-pool
|
331
|
+
# if resource_pool_id
|
332
|
+
# resource_pool = find_resource_pool_by_name_or_id(cloud['id'], resource_pool_id)
|
333
|
+
# return 1 if resource_pool.nil?
|
334
|
+
# end
|
335
|
+
|
336
|
+
# prompt for zone specific settings that go under "securityGroup.customOptions" for some reason
|
337
|
+
custom_options_values = prompt_security_group_custom_options(options, cloud, resource_pool_id)
|
338
|
+
payload['securityGroup'].deep_merge!(custom_options_values)
|
333
339
|
end
|
334
340
|
rescue => ex
|
341
|
+
# raise ex
|
335
342
|
print yellow,"Failed to determine the available scoped clouds.",reset,"\n"
|
336
343
|
end
|
337
344
|
|
@@ -626,34 +633,8 @@ class Morpheus::Cli::SecurityGroups
|
|
626
633
|
# return 1 if resource_pool.nil?
|
627
634
|
# end
|
628
635
|
|
629
|
-
#
|
630
|
-
|
631
|
-
# default to the cloud type code, since it's the same...
|
632
|
-
# no optionTypes returned here, so hard coded by type
|
633
|
-
network_server_type = cloud['networkServer'] ? cloud['networkServer']['type'] : (cloud['securityServer'] ? cloud['securityServer']['type'] : cloud["zoneType"]["code"])
|
634
|
-
custom_options_values = {}
|
635
|
-
if network_server_type == 'amazon'
|
636
|
-
if cloud['config'] && !cloud['config']['vpc'].to_s.empty?
|
637
|
-
custom_options_values.deep_merge!({'customOptions' => {'vpc' => cloud['config']['vpc']} })
|
638
|
-
else
|
639
|
-
options[:options].deep_merge!({'customOptions' => {'vpc' => resource_pool_id} }) if resource_pool_id
|
640
|
-
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'vpc', 'fieldLabel' => 'VPC', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true, 'config' => {'valueField' => 'externalId'}}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
641
|
-
end
|
642
|
-
elsif network_server_type == 'azure' || network_server_type == 'azurestack'
|
643
|
-
if cloud['config'] && !cloud['config']['resourceGroup'].to_s.empty?
|
644
|
-
custom_options_values.deep_merge!({'customOptions' => {'resourceGroup' => cloud['config']['resourceGroup']} })
|
645
|
-
else
|
646
|
-
options[:options].deep_merge!({'customOptions' => {'resourceGroup' => resource_pool_id} }) if resource_pool_id
|
647
|
-
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'resourceGroup', 'fieldLabel' => 'Resource Group', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true, 'config' => {'valueField' => 'externalId'}}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
648
|
-
end
|
649
|
-
elsif network_server_type == 'openstack' || network_server_type == 'opentelekom' || network_server_type == 'huawei'
|
650
|
-
if cloud['config'] && !cloud['config']['resourcePoolId'].to_s.empty?
|
651
|
-
custom_options_values.deep_merge!({'customOptions' => {'resourcePoolId' => cloud['config']['resourcePoolId']} })
|
652
|
-
else
|
653
|
-
options[:options].deep_merge!({'customOptions' => {'resourcePoolId' => resource_pool_id} }) if resource_pool_id
|
654
|
-
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'resourcePoolId', 'fieldLabel' => 'Resource Pool', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
655
|
-
end
|
656
|
-
end
|
636
|
+
# prompt for zone specific settings that go under "securityGroup.customOptions" for some reason
|
637
|
+
custom_options_values = prompt_security_group_custom_options(options, cloud, resource_pool_id)
|
657
638
|
payload['securityGroupLocation'].deep_merge!(custom_options_values)
|
658
639
|
end
|
659
640
|
@security_groups_interface.setopts(options)
|
@@ -1270,4 +1251,44 @@ class Morpheus::Cli::SecurityGroups
|
|
1270
1251
|
end
|
1271
1252
|
end
|
1272
1253
|
|
1254
|
+
def prompt_security_group_custom_options(options, cloud, resource_pool_id=nil)
|
1255
|
+
custom_options_values = {}
|
1256
|
+
# Custom Options prompt
|
1257
|
+
# securityServer is no longer used, it has been replaced by networkServer,
|
1258
|
+
# default to the cloud type code, since it's the same...
|
1259
|
+
# no optionTypes returned here, so hard coded by type
|
1260
|
+
# The API used to return securityServer which could be fetched to get its optionTypes
|
1261
|
+
if cloud['securityServer']
|
1262
|
+
sec_server = @network_security_servers.get(cloud['securityServer']['id'])['networkSecurityServer']
|
1263
|
+
if sec_server['type']
|
1264
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(sec_server['type']['optionTypes'], options[:options], @api_client, {zoneId: cloud['id']})
|
1265
|
+
custom_options_values.deep_merge!(v_prompt)
|
1266
|
+
end
|
1267
|
+
else
|
1268
|
+
network_server_type = cloud['networkServer'] ? cloud['networkServer']['type'] : (cloud['securityServer'] ? cloud['securityServer']['type'] : cloud["zoneType"]["code"])
|
1269
|
+
if network_server_type == 'amazon'
|
1270
|
+
if cloud['config'] && !cloud['config']['vpc'].to_s.empty?
|
1271
|
+
custom_options_values.deep_merge!({'customOptions' => {'vpc' => cloud['config']['vpc']} })
|
1272
|
+
else
|
1273
|
+
options[:options].deep_merge!({'customOptions' => {'vpc' => resource_pool_id} }) if resource_pool_id
|
1274
|
+
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'vpc', 'fieldLabel' => 'VPC', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true, 'config' => {'valueField' => 'externalId'}}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
1275
|
+
end
|
1276
|
+
elsif network_server_type == 'azure' || network_server_type == 'azurestack'
|
1277
|
+
if cloud['config'] && !cloud['config']['resourceGroup'].to_s.empty?
|
1278
|
+
custom_options_values.deep_merge!({'customOptions' => {'resourceGroup' => cloud['config']['resourceGroup']} })
|
1279
|
+
else
|
1280
|
+
options[:options].deep_merge!({'customOptions' => {'resourceGroup' => resource_pool_id} }) if resource_pool_id
|
1281
|
+
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'resourceGroup', 'fieldLabel' => 'Resource Group', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true, 'config' => {'valueField' => 'externalId'}}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
1282
|
+
end
|
1283
|
+
elsif network_server_type == 'openstack' || network_server_type == 'opentelekom' || network_server_type == 'huawei'
|
1284
|
+
if cloud['config'] && !cloud['config']['resourcePoolId'].to_s.empty?
|
1285
|
+
custom_options_values.deep_merge!({'customOptions' => {'resourcePoolId' => cloud['config']['resourcePoolId']} })
|
1286
|
+
else
|
1287
|
+
options[:options].deep_merge!({'customOptions' => {'resourcePoolId' => resource_pool_id} }) if resource_pool_id
|
1288
|
+
custom_options_values = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'customOptions', 'fieldName' => 'resourcePoolId', 'fieldLabel' => 'Resource Pool', 'type' => 'select', 'optionSource' => 'zonePools', 'required' => true}], options[:options], @api_client, {zoneId: cloud['id'], ignoreDefaultPool: true})
|
1289
|
+
end
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
return custom_options_values
|
1293
|
+
end
|
1273
1294
|
end
|
@@ -560,27 +560,17 @@ EOT
|
|
560
560
|
# fetch current cart
|
561
561
|
# cart = @service_catalog_interface.get_cart()['cart']
|
562
562
|
update_cart_object_key = 'order'
|
563
|
-
|
563
|
+
parse_payload(options, update_cart_object_key) do |payload|
|
564
564
|
payload.deep_merge!({update_cart_object_key => parse_passed_options(options)})
|
565
565
|
if payload[update_cart_object_key].empty? # || options[:no_prompt]
|
566
566
|
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
567
567
|
end
|
568
568
|
end
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
next
|
574
|
-
end
|
575
|
-
json_response = @service_catalog_interface.update_cart(payload)
|
576
|
-
#cart = json_response['cart']
|
577
|
-
#cart = @service_catalog_interface.get_cart()['cart']
|
578
|
-
render_response(json_response, options, 'cart') do
|
579
|
-
print_green_success "Updated cart"
|
580
|
-
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
581
|
-
end
|
569
|
+
execute_api(@service_catalog_interface, :update_cart, [params], options, "cart") do |json_response|
|
570
|
+
#cart = json_response["cart"]
|
571
|
+
print_green_success "Updated cart"
|
572
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
582
573
|
end
|
583
|
-
return 0, nil
|
584
574
|
end
|
585
575
|
|
586
576
|
def add(args)
|
@@ -600,6 +590,7 @@ EOT
|
|
600
590
|
end
|
601
591
|
opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips adding the item.") do
|
602
592
|
options[:validate_only] = true
|
593
|
+
params['validate'] = true
|
603
594
|
end
|
604
595
|
opts.on('--context [instance|server]', String, "Context Type for operational workflow types") do |val|
|
605
596
|
workflow_context = val.to_s
|
@@ -608,7 +599,7 @@ EOT
|
|
608
599
|
workflow_target = val.to_s
|
609
600
|
end
|
610
601
|
opts.add_hidden_option('--sigdig')
|
611
|
-
|
602
|
+
build_standard_add_many_options(opts, options, [:sigdig])
|
612
603
|
opts.footer = <<-EOT
|
613
604
|
Add an item to your cart
|
614
605
|
[type] is required, this is name or id of a catalog item type.
|
@@ -622,7 +613,7 @@ EOT
|
|
622
613
|
type_id = args.join(" ")
|
623
614
|
end
|
624
615
|
add_item_object_key = 'item'
|
625
|
-
|
616
|
+
parse_payload(options, add_item_object_key) do |payload|
|
626
617
|
payload.deep_merge!({add_item_object_key => parse_passed_options(options)})
|
627
618
|
# prompt for Type
|
628
619
|
if type_id
|
@@ -712,46 +703,35 @@ EOT
|
|
712
703
|
end
|
713
704
|
end
|
714
705
|
end
|
715
|
-
|
716
|
-
params['validate'] = true
|
717
|
-
end
|
718
|
-
process_payloads(payloads, options) do |payload|
|
719
|
-
@service_catalog_interface.setopts(options)
|
720
|
-
if options[:dry_run]
|
721
|
-
print_dry_run @service_catalog_interface.dry.create_cart_item(payload, params)
|
722
|
-
next
|
723
|
-
end
|
724
|
-
json_response = @service_catalog_interface.create_cart_item(payload, params)
|
706
|
+
execute_api(@service_catalog_interface, :create_cart_item, [params], options, 'item') do |json_response|
|
725
707
|
cart_item = json_response['item']
|
726
|
-
|
727
|
-
if
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
print reset, "\n"
|
747
|
-
else
|
748
|
-
# not needed because it will be http 400
|
749
|
-
print_rest_errors(json_response, options)
|
750
|
-
end
|
708
|
+
if options[:validate_only]
|
709
|
+
if json_response['success']
|
710
|
+
print_h2 "Validated Cart Item", [], options
|
711
|
+
cart_item_columns = {
|
712
|
+
"Type" => lambda {|it| it['type']['name'] rescue '' },
|
713
|
+
#"Qty" => lambda {|it| it['quantity'] },
|
714
|
+
"Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" },
|
715
|
+
"Status" => lambda {|it|
|
716
|
+
status_string = format_catalog_item_status(it)
|
717
|
+
if it['errorMessage'].to_s != ""
|
718
|
+
status_string << " - #{it['errorMessage']}"
|
719
|
+
end
|
720
|
+
status_string
|
721
|
+
},
|
722
|
+
#"Config" => lambda {|it| truncate_string(format_name_values(it['config']), 50) }
|
723
|
+
}
|
724
|
+
print as_pretty_table([cart_item], cart_item_columns.upcase_keys!)
|
725
|
+
print reset, "\n"
|
726
|
+
print_green_success(json_response['msg'] || "Item is valid")
|
727
|
+
print reset, "\n"
|
751
728
|
else
|
752
|
-
|
753
|
-
|
729
|
+
# not needed because it will be http 400
|
730
|
+
print_rest_errors(json_response, options)
|
754
731
|
end
|
732
|
+
else
|
733
|
+
print_green_success "Added item to cart"
|
734
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
755
735
|
end
|
756
736
|
end
|
757
737
|
end
|
@@ -935,6 +915,7 @@ EOT
|
|
935
915
|
end
|
936
916
|
opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips creating the order.") do
|
937
917
|
options[:validate_only] = true
|
918
|
+
params['validate'] = true
|
938
919
|
end
|
939
920
|
opts.on('-a', '--details', "Display all details: item configuration." ) do
|
940
921
|
options[:details] = true
|
@@ -962,7 +943,7 @@ EOT
|
|
962
943
|
end
|
963
944
|
payload = {}
|
964
945
|
order_object_key = 'order'
|
965
|
-
|
946
|
+
parse_payload(options, order_object_key) do |payload|
|
966
947
|
payload.deep_merge!({order_object_key => {}})
|
967
948
|
# Prompt for 1-N Types
|
968
949
|
# still_prompting = options[:no_prompt] != true
|
@@ -1076,38 +1057,24 @@ EOT
|
|
1076
1057
|
end
|
1077
1058
|
end
|
1078
1059
|
|
1079
|
-
end
|
1080
|
-
|
1081
|
-
|
1060
|
+
end
|
1082
1061
|
end
|
1083
|
-
|
1084
|
-
params['validate'] = true
|
1085
|
-
#payload['validate'] = true
|
1086
|
-
end
|
1087
|
-
process_payloads(payloads, options) do |payload|
|
1088
|
-
@service_catalog_interface.setopts(options)
|
1089
|
-
if options[:dry_run]
|
1090
|
-
print_dry_run @service_catalog_interface.dry.create_order(payload, params)
|
1091
|
-
next
|
1092
|
-
end
|
1093
|
-
json_response = @service_catalog_interface.create_order(payload, params)
|
1062
|
+
execute_api(@service_catalog_interface, :create_order, [params], options, "order") do |json_response|
|
1094
1063
|
order = json_response['order'] || json_response['cart']
|
1095
|
-
|
1096
|
-
if
|
1097
|
-
|
1098
|
-
print_h2 "Review Order", [], options
|
1099
|
-
print_order_details(order, options)
|
1100
|
-
print_green_success(json_response['msg'] || "Order is valid")
|
1101
|
-
print reset, "\n"
|
1102
|
-
else
|
1103
|
-
# not needed because it will be http 400
|
1104
|
-
print_rest_errors(json_response, options)
|
1105
|
-
end
|
1106
|
-
else
|
1107
|
-
print_green_success "Order placed"
|
1108
|
-
print_h2 "Order Details", [], options
|
1064
|
+
if options[:validate_only]
|
1065
|
+
if json_response['success']
|
1066
|
+
print_h2 "Review Order", [], options
|
1109
1067
|
print_order_details(order, options)
|
1068
|
+
print_green_success(json_response['msg'] || "Order is valid")
|
1069
|
+
print reset, "\n"
|
1070
|
+
else
|
1071
|
+
# not needed because it will be http 400
|
1072
|
+
print_rest_errors(json_response, options)
|
1110
1073
|
end
|
1074
|
+
else
|
1075
|
+
print_green_success "Order placed"
|
1076
|
+
print_h2 "Order Details", [], options
|
1077
|
+
print_order_details(order, options)
|
1111
1078
|
end
|
1112
1079
|
end
|
1113
1080
|
end
|
@@ -46,20 +46,22 @@ Examples:
|
|
46
46
|
EOT
|
47
47
|
end
|
48
48
|
optparse.parse!(args)
|
49
|
-
|
49
|
+
verify_args!(args:args, optparse:optparse, min: 0, max: 2)
|
50
50
|
connect(options)
|
51
51
|
# todo: it would actually be cool to use the params and include them on the path..
|
52
52
|
# params.merge!(parse_query_options(options))
|
53
|
-
|
53
|
+
# input, *ids = args
|
54
|
+
input = args[0]
|
55
|
+
id = args[1]
|
54
56
|
# default to index page "/"
|
55
|
-
path =
|
57
|
+
path = input || "/"
|
56
58
|
if options[:absolute_path] != true
|
57
59
|
if path.start_with?("/")
|
58
60
|
# treat like absolute path, no lookup
|
59
61
|
else
|
60
62
|
# lookup best matching route from sitemap
|
61
63
|
# lookup plural routes first, so 'app' finds apps and not approvals
|
62
|
-
found_route = Morpheus::Routes.lookup(path)
|
64
|
+
found_route = Morpheus::Routes.lookup(path, id)
|
63
65
|
if found_route
|
64
66
|
# Morpheus::Logging::DarkPrinter.puts "Found matching route: '#{path}' => '#{found_route}'" if Morpheus::Logging.debug?
|
65
67
|
path = found_route
|
@@ -69,26 +71,24 @@ EOT
|
|
69
71
|
end
|
70
72
|
# always add a leading slash
|
71
73
|
path = path.start_with?("/") ? path : "/#{path}"
|
72
|
-
# append id
|
73
|
-
if
|
74
|
-
# convert
|
74
|
+
# append id to path if passed
|
75
|
+
if id
|
76
|
+
# convert name to id
|
75
77
|
# assume the last part of path is the type and use generic finder
|
76
78
|
# only lookup names, and allow any id
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
record['id'].to_s
|
87
|
-
else
|
88
|
-
id
|
79
|
+
if id.to_s !~ /\A\d{1,}\Z/
|
80
|
+
# record type is just args[0]
|
81
|
+
record_type = input
|
82
|
+
# assume the last part of path is the type
|
83
|
+
# record_type = path.split("/").last
|
84
|
+
# record_type.sub!('#!', '')
|
85
|
+
record = find_by_name(record_type, id)
|
86
|
+
if record.nil?
|
87
|
+
raise_command_error("[id] is invalid. No #{record_type} found for '#{id}'", args, optparse)
|
89
88
|
end
|
89
|
+
id = record['id'].to_s
|
90
90
|
end
|
91
|
-
path = "#{path}
|
91
|
+
path = "#{path}/#{id}"
|
92
92
|
end
|
93
93
|
end
|
94
94
|
# build the link to use, either our path or oauth-redirect to that path
|
@@ -110,4 +110,62 @@ module Morpheus::Cli::BackupsHelper
|
|
110
110
|
return backup_jobs[0]
|
111
111
|
end
|
112
112
|
end
|
113
|
+
|
114
|
+
## Backup Results
|
115
|
+
|
116
|
+
def backup_result_list_column_definitions()
|
117
|
+
{
|
118
|
+
"ID" => 'id',
|
119
|
+
"Backup" => lambda {|it| it['backup']['name'] rescue '' },
|
120
|
+
"Status" => lambda {|it| format_backup_result_status(it) },
|
121
|
+
#"Duration" => lambda {|it| format_duration(it['startDate'], it['endDate']) },
|
122
|
+
"Duration" => lambda {|it| format_duration_milliseconds(it['durationMillis']) },
|
123
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
124
|
+
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
125
|
+
"Size" => lambda {|it| format_bytes(it['sizeInMb'], 'MB') },
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def backup_result_column_definitions()
|
130
|
+
backup_result_list_column_definitions()
|
131
|
+
end
|
132
|
+
|
133
|
+
def format_backup_result_status(backup_result, return_color=cyan)
|
134
|
+
out = ""
|
135
|
+
status_string = backup_result['status'].to_s.upcase
|
136
|
+
if status_string == 'SUCCEEDED' || status_string == 'SUCCESS'
|
137
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
138
|
+
elsif status_string == 'FAILED'
|
139
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
140
|
+
elsif status_string
|
141
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
142
|
+
else
|
143
|
+
out << ""
|
144
|
+
end
|
145
|
+
out
|
146
|
+
end
|
147
|
+
|
148
|
+
## Backup Restores
|
149
|
+
|
150
|
+
def backup_restore_list_column_definitions()
|
151
|
+
{
|
152
|
+
"ID" => 'id',
|
153
|
+
"Backup" => lambda {|it| it['backup']['name'] rescue '' },
|
154
|
+
"Backup Result ID" => lambda {|it| it['backupResultId'] rescue '' },
|
155
|
+
"Target" => lambda {|it| it['instance']['name'] rescue '' },
|
156
|
+
"Status" => lambda {|it| format_backup_result_status(it) },
|
157
|
+
#"Duration" => lambda {|it| format_duration(it['startDate'], it['endDate']) },
|
158
|
+
"Duration" => lambda {|it| format_duration_milliseconds(it['durationMillis']) },
|
159
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
160
|
+
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def backup_restore_column_definitions()
|
165
|
+
backup_restore_list_column_definitions()
|
166
|
+
end
|
167
|
+
|
168
|
+
def format_backup_restore_status(backup_restore, return_color=cyan)
|
169
|
+
format_backup_result_status(backup_restore, return_color)
|
170
|
+
end
|
113
171
|
end
|
@@ -191,7 +191,24 @@ module Morpheus::Cli::PrintHelper
|
|
191
191
|
output = ""
|
192
192
|
if api_request[:curl] || options[:curl]
|
193
193
|
output = format_curl_command(http_method, url, headers, payload, options)
|
194
|
+
elsif options[:json]
|
195
|
+
# --dry --json should print the payload only
|
196
|
+
payload_object = payload.is_a?(String) ? JSON.parse(payload) : payload
|
197
|
+
output = as_json(payload_object, options)
|
198
|
+
elsif options[:yaml]
|
199
|
+
# --dry --yaml should print the payload only as yaml
|
200
|
+
payload_object = payload.is_a?(String) ? JSON.parse(payload) : payload
|
201
|
+
output = as_yaml(payload_object, options)
|
194
202
|
else
|
203
|
+
# default format is
|
204
|
+
# DRY RUN
|
205
|
+
# REQUEST
|
206
|
+
# GET https://server/api/things
|
207
|
+
#
|
208
|
+
# JSON
|
209
|
+
# {
|
210
|
+
# "thing": { ... }
|
211
|
+
# }
|
195
212
|
output = format_api_request(http_method, url, headers, payload, options)
|
196
213
|
end
|
197
214
|
# this is an extra scrub, should remove
|
@@ -210,14 +227,20 @@ module Morpheus::Cli::PrintHelper
|
|
210
227
|
if api_request[:curl] || options[:curl]
|
211
228
|
print "\n"
|
212
229
|
print "#{cyan}#{bold}#{dark}CURL COMMAND#{reset}\n"
|
230
|
+
print output
|
231
|
+
print reset, "\n"
|
232
|
+
print reset
|
233
|
+
elsif options[:json] || options[:yaml]
|
234
|
+
# print just the just payload
|
235
|
+
print output, "\n"
|
213
236
|
else
|
214
237
|
print "\n"
|
215
238
|
print "#{cyan}#{bold}#{dark}REQUEST#{reset}\n"
|
239
|
+
print output
|
240
|
+
print reset, "\n"
|
241
|
+
print reset
|
216
242
|
end
|
217
|
-
|
218
|
-
print reset, "\n"
|
219
|
-
print reset
|
220
|
-
return
|
243
|
+
return output
|
221
244
|
end
|
222
245
|
|
223
246
|
def print_system_command_dry_run(cmd, options={})
|
@@ -48,6 +48,7 @@ module Morpheus
|
|
48
48
|
|
49
49
|
# supresses prompting unless --prompt has been passed
|
50
50
|
def self.no_prompt(option_types, options={}, api_client=nil,api_params={})
|
51
|
+
options[:edit_mode] = true # hack used for updates to avoid default values being used
|
51
52
|
if options[:always_prompt]
|
52
53
|
prompt(option_types, options, api_client, api_params)
|
53
54
|
else
|
@@ -218,7 +219,7 @@ module Morpheus
|
|
218
219
|
|
219
220
|
# credential type
|
220
221
|
handle_credential_type = -> {
|
221
|
-
credential_type = select_prompt(option_type.merge({'defaultValue' => value}), api_client, option_params.merge({'credentialTypes' => option_type['config']['credentialTypes']}), !value.nil?, nil, paging_enabled, ignore_empty)
|
222
|
+
credential_type = select_prompt(option_type.merge({'defaultValue' => value}), api_client, option_params.merge({'credentialTypes' => option_type['config']['credentialTypes']}), !value.nil?, nil, paging_enabled, ignore_empty, options[:edit_mode])
|
222
223
|
# continue prompting for local creds
|
223
224
|
if credential_type == 'local'
|
224
225
|
parent_context_map.reject! {|k,v| k == 'credential'}
|
@@ -247,14 +248,14 @@ module Morpheus
|
|
247
248
|
end
|
248
249
|
# these select prompts should just fall down through below, with the extra params no_prompt, use_value
|
249
250
|
elsif option_type['type'] == 'select'
|
250
|
-
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty)
|
251
|
+
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
|
251
252
|
elsif option_type['type'] == 'multiSelect'
|
252
253
|
# support value as csv like "thing1, thing2"
|
253
254
|
value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
254
255
|
input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
255
256
|
select_value_list = []
|
256
257
|
value_list.each_with_index do |v, i|
|
257
|
-
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty)
|
258
|
+
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
|
258
259
|
end
|
259
260
|
value = select_value_list
|
260
261
|
elsif option_type['type'] == 'typeahead'
|
@@ -302,7 +303,7 @@ module Morpheus
|
|
302
303
|
next
|
303
304
|
end
|
304
305
|
if ['select', 'multiSelect'].include?(option_type['type'])
|
305
|
-
value = select_prompt(option_type, api_client, option_params, true, nil, false, ignore_empty)
|
306
|
+
value = select_prompt(option_type, api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
|
306
307
|
value_found = !!value
|
307
308
|
end
|
308
309
|
if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
|
@@ -347,12 +348,12 @@ module Morpheus
|
|
347
348
|
# I suppose the entered value should take precedence
|
348
349
|
# api_params = api_params.merge(options) # this might be good enough
|
349
350
|
# dup it
|
350
|
-
value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty)
|
351
|
+
value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty, options[:edit_mode])
|
351
352
|
if value && option_type['type'] == 'multiSelect'
|
352
353
|
value = [value]
|
353
354
|
recommended_count = (option_type['config'] || {})['recommendedCount'] || 0
|
354
355
|
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => recommended_count > value.count}) do
|
355
|
-
if addn_value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty)
|
356
|
+
if addn_value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty, options[:edit_mode])
|
356
357
|
value << addn_value
|
357
358
|
else
|
358
359
|
break
|
@@ -476,7 +477,7 @@ module Morpheus
|
|
476
477
|
Thread.current[:_last_select]
|
477
478
|
end
|
478
479
|
|
479
|
-
def self.select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false, ignore_empty=false)
|
480
|
+
def self.select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false, ignore_empty=false, edit_mode=false)
|
480
481
|
paging_enabled = false if Morpheus::Cli.windows?
|
481
482
|
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
482
483
|
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
@@ -550,6 +551,8 @@ module Morpheus
|
|
550
551
|
print "\n"
|
551
552
|
exit 1
|
552
553
|
end
|
554
|
+
elsif edit_mode
|
555
|
+
# do not use a default value for edit mode
|
553
556
|
# skipSingleOption is no longer supported
|
554
557
|
# elsif !select_options.nil? && select_options.count == 1 && option_type['skipSingleOption'] == true
|
555
558
|
# value_found = true
|
data/lib/morpheus/cli/version.rb
CHANGED
data/lib/morpheus/formatters.rb
CHANGED
@@ -134,7 +134,7 @@ end
|
|
134
134
|
|
135
135
|
def format_duration_milliseconds(milliseconds, format="human", ms_threshold=1000)
|
136
136
|
out = ""
|
137
|
-
milliseconds = milliseconds.abs
|
137
|
+
milliseconds = milliseconds.to_i.abs
|
138
138
|
if ms_threshold && ms_threshold > milliseconds
|
139
139
|
out = "#{milliseconds}ms"
|
140
140
|
else
|
data/lib/morpheus/routes.rb
CHANGED
@@ -100,7 +100,13 @@ module Morpheus::Routes
|
|
100
100
|
],
|
101
101
|
},
|
102
102
|
backups: {
|
103
|
-
|
103
|
+
list: {},
|
104
|
+
show: {},
|
105
|
+
jobs: {},
|
106
|
+
history: [
|
107
|
+
"#!restores",
|
108
|
+
],
|
109
|
+
services: {}
|
104
110
|
},
|
105
111
|
monitoring: {
|
106
112
|
status: {},
|
@@ -162,9 +168,10 @@ module Morpheus::Routes
|
|
162
168
|
|
163
169
|
# lookup a route in the morpheus UI
|
164
170
|
# @param path [String] The input to lookup a route for eg. "dashboard"
|
171
|
+
# @param id [String] ID indicates the show route is needed for a resource for cases where it varies ie. backups
|
165
172
|
# @return full path like "/operations/dashboard"
|
166
|
-
def self.lookup(
|
167
|
-
path =
|
173
|
+
def self.lookup(path, id=nil)
|
174
|
+
path = path.to_s
|
168
175
|
if path.start_with?("/")
|
169
176
|
# absolute path is being looked up
|
170
177
|
return path
|
@@ -174,6 +181,14 @@ module Morpheus::Routes
|
|
174
181
|
|
175
182
|
# map well known aliases
|
176
183
|
case(path.dasherize.pluralize)
|
184
|
+
when "backups"
|
185
|
+
path = id ? "/backups/show" : "/backups/list"
|
186
|
+
when "backup-jobs"
|
187
|
+
path = "/backups/jobs"
|
188
|
+
when "backup-results"
|
189
|
+
path = "/backups/history"
|
190
|
+
when "backup-restores", "restores"
|
191
|
+
path = "/backups/history/#!restores"
|
177
192
|
when "servers","hosts","vms","virtual-machines"
|
178
193
|
# actually should be "/infrastructure/inventory" unless id is passed, show route uses /servers though
|
179
194
|
path = "/infrastructure/servers"
|