morpheus-cli 6.1.2 → 6.2.1

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +8 -0
  4. data/lib/morpheus/api/backup_jobs_interface.rb +4 -0
  5. data/lib/morpheus/api/backup_restores_interface.rb +23 -0
  6. data/lib/morpheus/api/backup_results_interface.rb +28 -0
  7. data/lib/morpheus/api/backups_interface.rb +5 -4
  8. data/lib/morpheus/api/monitoring_settings_interface.rb +0 -4
  9. data/lib/morpheus/cli/cli_command.rb +176 -46
  10. data/lib/morpheus/cli/commands/appliance_settings_command.rb +7 -19
  11. data/lib/morpheus/cli/commands/apps.rb +1 -1
  12. data/lib/morpheus/cli/commands/backup_jobs_command.rb +78 -20
  13. data/lib/morpheus/cli/commands/backup_restores_command.rb +144 -0
  14. data/lib/morpheus/cli/commands/backup_results_command.rb +149 -0
  15. data/lib/morpheus/cli/commands/backups_command.rb +215 -93
  16. data/lib/morpheus/cli/commands/hosts.rb +15 -2
  17. data/lib/morpheus/cli/commands/instances.rb +18 -3
  18. data/lib/morpheus/cli/commands/monitoring_settings.rb +0 -16
  19. data/lib/morpheus/cli/commands/plugins.rb +2 -1
  20. data/lib/morpheus/cli/commands/roles.rb +9 -9
  21. data/lib/morpheus/cli/commands/service_catalog_command.rb +50 -83
  22. data/lib/morpheus/cli/commands/user_sources_command.rb +36 -8
  23. data/lib/morpheus/cli/commands/view.rb +20 -20
  24. data/lib/morpheus/cli/mixins/backups_helper.rb +58 -0
  25. data/lib/morpheus/cli/mixins/print_helper.rb +27 -4
  26. data/lib/morpheus/cli/option_types.rb +10 -7
  27. data/lib/morpheus/cli/version.rb +1 -1
  28. data/lib/morpheus/formatters.rb +1 -1
  29. data/lib/morpheus/routes.rb +18 -3
  30. metadata +6 -2
@@ -1206,7 +1206,7 @@ EOT
1206
1206
  role = find_role_by_name_or_id(account_id, name)
1207
1207
  exit 1 if role.nil?
1208
1208
 
1209
- role_json = @roles_interface.get(account_id, role['id'])
1209
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1210
1210
 
1211
1211
  cloud = nil
1212
1212
  if !do_all
@@ -1355,7 +1355,7 @@ EOT
1355
1355
  role = find_role_by_name_or_id(account_id, name)
1356
1356
  return 1 if role.nil?
1357
1357
 
1358
- role_json = @roles_interface.get(account_id, role['id'])
1358
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1359
1359
  instance_type = nil
1360
1360
  if !do_all
1361
1361
  instance_type = find_instance_type_by_name(instance_type_name)
@@ -1504,7 +1504,7 @@ EOT
1504
1504
  role = find_role_by_name_or_id(account_id, name)
1505
1505
  return 1 if role.nil?
1506
1506
 
1507
- role_json = @roles_interface.get(account_id, role['id'])
1507
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1508
1508
  blueprint_global_access = role_json['globalAppTemplateAccess'] || role_json['globalBlueprintAccess']
1509
1509
  blueprint_permissions = role_json['appTemplatePermissions'] || role_json['blueprintPermissions'] || []
1510
1510
 
@@ -1666,7 +1666,7 @@ EOT
1666
1666
  role = find_role_by_name_or_id(account_id, name)
1667
1667
  return 1 if role.nil?
1668
1668
 
1669
- role_json = @roles_interface.get(account_id, role['id'])
1669
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1670
1670
  catalog_item_type_global_access = role_json['globalCatalogItemTypeAccess']
1671
1671
  catalog_item_type_permissions = role_json['catalogItemTypePermissions'] || role_json['catalogItemTypes'] []
1672
1672
 
@@ -1821,7 +1821,7 @@ Update default persona access for a role.
1821
1821
  role = find_role_by_name_or_id(account_id, name)
1822
1822
  return 1 if role.nil?
1823
1823
 
1824
- role_json = @roles_interface.get(account_id, role['id'])
1824
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1825
1825
 
1826
1826
  # no lookup right now, pass the code serviceCatalog|standard
1827
1827
  persona_code = persona_id
@@ -1963,7 +1963,7 @@ EOT
1963
1963
  role = find_role_by_name_or_id(account_id, name)
1964
1964
  return 1 if role.nil?
1965
1965
 
1966
- role_json = @roles_interface.get(account_id, role['id'])
1966
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
1967
1967
  vdi_pool_global_access = role_json['globalVdiPoolAccess']
1968
1968
  vdi_pool_permissions = role_json['vdiPoolPermissions'] || role_json['vdiPools'] || []
1969
1969
 
@@ -2119,7 +2119,7 @@ EOT
2119
2119
  role = find_role_by_name_or_id(account_id, name)
2120
2120
  return 1 if role.nil?
2121
2121
 
2122
- role_json = @roles_interface.get(account_id, role['id'])
2122
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
2123
2123
  report_type_global_access = role_json['globalReportTypeAccess']
2124
2124
  report_type_permissions = role_json['reportTypePermissions'] || role_json['reportTypes'] || []
2125
2125
 
@@ -2273,7 +2273,7 @@ Update default task access for a role.
2273
2273
  role = find_role_by_name_or_id(account_id, name)
2274
2274
  return 1 if role.nil?
2275
2275
 
2276
- role_json = @roles_interface.get(account_id, role['id'])
2276
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
2277
2277
  task_permissions = role_json['taskPermissions'] || role_json['tasks'] || []
2278
2278
 
2279
2279
  # hacky, but support name or code lookup via the list returned in the show payload
@@ -2425,7 +2425,7 @@ Update default workflow access for a role.
2425
2425
  role = find_role_by_name_or_id(account_id, name)
2426
2426
  return 1 if role.nil?
2427
2427
 
2428
- role_json = @roles_interface.get(account_id, role['id'])
2428
+ role_json = @roles_interface.get(account_id, role['id'], {'includeDefaultAccess' => true})
2429
2429
  workflow_permissions = role_json['taskSetPermissions'] || role_json['taskSets'] || []
2430
2430
 
2431
2431
  # hacky, but support name or code lookup via the list returned in the show payload
@@ -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
- payloads = parse_payloads(options, update_cart_object_key) do |payload|
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
- process_payloads(payloads, options) do |payload|
570
- @service_catalog_interface.setopts(options)
571
- if options[:dry_run]
572
- print_dry_run @service_catalog_interface.dry.update_cart(payload)
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
- build_standard_update_options(opts, options, [:payloads, :sigdig])
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
- payloads = parse_payloads(options, add_item_object_key) do |payload|
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
- if options[:validate_only]
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
- render_response(json_response, options) do
727
- if options[:validate_only]
728
- if json_response['success']
729
- print_h2 "Validated Cart Item", [], options
730
- cart_item_columns = {
731
- "Type" => lambda {|it| it['type']['name'] rescue '' },
732
- #"Qty" => lambda {|it| it['quantity'] },
733
- "Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" },
734
- "Status" => lambda {|it|
735
- status_string = format_catalog_item_status(it)
736
- if it['errorMessage'].to_s != ""
737
- status_string << " - #{it['errorMessage']}"
738
- end
739
- status_string
740
- },
741
- #"Config" => lambda {|it| truncate_string(format_name_values(it['config']), 50) }
742
- }
743
- print as_pretty_table([cart_item], cart_item_columns.upcase_keys!)
744
- print reset, "\n"
745
- print_green_success(json_response['msg'] || "Item is valid")
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
- print_green_success "Added item to cart"
753
- get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
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
- payloads = parse_payloads(options, order_object_key) do |payload|
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
- if options[:validate_only]
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
- render_response(json_response, options) do
1096
- if options[:validate_only]
1097
- if json_response['success']
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
@@ -158,7 +158,8 @@ EOT
158
158
  "Login URL" => lambda {|it| it['loginURL'] },
159
159
  "Default Role" => lambda {|it| it['defaultAccountRole'] ? it['defaultAccountRole']['authority'] : '' },
160
160
  "External Login" => lambda {|it| format_boolean it['externalLogin'] },
161
- "Allow Custom Mappings" => lambda {|it| format_boolean it['allowCustomMappings'] },
161
+ "Enable Role Mapping Permission" => lambda {|it| format_boolean it['allowCustomMappings'] },
162
+ "Manual Role Assignment" => lambda {|it| it['manualRoleAssignment'].nil? ? '' : format_boolean(it['manualRoleAssignment']) },
162
163
  "Active" => lambda {|it| format_boolean it['active'] },
163
164
  }
164
165
  print_description_list(description_cols, user_source)
@@ -235,13 +236,27 @@ EOT
235
236
  opts.on('--description VALUE', String, "Description") do |val|
236
237
  params['description'] = val
237
238
  end
238
- opts.on("--allow-custom-mappings [on|off]", ['on','off'], "Allow Custom Mappings, Enable Role Mapping Permissions") do |val|
239
+ opts.on("--allow-custom-mappings [on|off]", ['on','off'], "Enable Role Mapping Permissions") do |val|
239
240
  params['allowCustomMappings'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
240
241
  end
241
- opts.on("--allowCustomMappings [on|off]", ['on','off'], "Allow Custom Mappings, Enable Role Mapping Permissions") do |val|
242
+ opts.on("--allowCustomMappings [on|off]", ['on','off'], "Enable Role Mapping Permissions") do |val|
242
243
  params['allowCustomMappings'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
243
244
  end
244
245
  opts.add_hidden_option('--allowCustomMappings')
246
+ opts.on("--manual-role-assignment [on|off]", ['on','off'], "Manual Role Assignment") do |val|
247
+ params['manualRoleAssignment'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
248
+ end
249
+ opts.on("--manualRoleAssignment [on|off]", ['on','off'], "Manual Role Assignment") do |val|
250
+ params['manualRoleAssignment'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
251
+ end
252
+ opts.add_hidden_option('--manualRoleAssignment')
253
+ opts.on("--manual-role-assignment [on|off]", ['on','off'], "Manual Role Assignment") do |val|
254
+ params['manualRoleAssignment'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
255
+ end
256
+ opts.on("--manualRoleAssignment [on|off]", ['on','off'], "Manual Role Assignment") do |val|
257
+ params['manualRoleAssignment'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
258
+ end
259
+ opts.add_hidden_option('--manualRoleAssignment')
245
260
  opts.on('--role-mappings MAPPINGS', String, "Role Mappings FQN in the format id1:FQN1,id2:FQN2") do |val|
246
261
  role_mappings = {}
247
262
  val.split(',').collect {|it| it.strip.split(':') }.each do |pair|
@@ -386,14 +401,22 @@ EOT
386
401
  end
387
402
  payload['userSource']['defaultAccountRole'] = {'id' => default_role_id }
388
403
 
389
- # Allow Custom Mappings
404
+ # Enable Role Mapping Permissions
390
405
  if !params['allowCustomMappings'].nil?
391
406
  payload['userSource']['allowCustomMappings'] = ["on","true"].include?(params['allowCustomMappings'].to_s)
392
407
  else
393
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'allowCustomMappings', 'type' => 'checkbox', 'fieldLabel' => 'Allow Custom Mappings', 'defaultValue' => false}], options[:options])
408
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'allowCustomMappings', 'type' => 'checkbox', 'fieldLabel' => 'Enable Role Mapping Permissions', 'defaultValue' => false}], options[:options])
394
409
  payload['userSource']['allowCustomMappings'] = ["on","true"].include?(v_prompt['allowCustomMappings'].to_s)
395
410
  end
396
411
 
412
+ # Manual Role Assignment
413
+ if !params['manualRoleAssignment'].nil?
414
+ payload['userSource']['manualRoleAssignment'] = ["on","true"].include?(params['allowCustomMappings'].to_s)
415
+ else
416
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'manualRoleAssignment', 'type' => 'checkbox', 'fieldLabel' => 'Manual Role Assignment', 'defaultValue' => false}], options[:options])
417
+ payload['userSource']['manualRoleAssignment'] = ["on","true"].include?(v_prompt['manualRoleAssignment'].to_s)
418
+ end
419
+
397
420
  if role_mappings
398
421
  payload['roleMappings'] = role_mappings
399
422
  end
@@ -435,10 +458,10 @@ EOT
435
458
  opts.on('--description VALUE', String, "Description") do |val|
436
459
  params['description'] = val
437
460
  end
438
- opts.on("--allow-custom-mappings [on|off]", ['on','off'], "Allow Custom Mappings, Enable Role Mapping Permissions") do |val|
461
+ opts.on("--allow-custom-mappings [on|off]", ['on','off'], "Enable Role Mapping Permissions") do |val|
439
462
  params['allowCustomMappings'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
440
463
  end
441
- opts.on("--allowCustomMappings [on|off]", ['on','off'], "Allow Custom Mappings, Enable Role Mapping Permissions") do |val|
464
+ opts.on("--allowCustomMappings [on|off]", ['on','off'], "Enable Role Mapping Permissions") do |val|
442
465
  params['allowCustomMappings'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
443
466
  end
444
467
  opts.add_hidden_option('--allowCustomMappings')
@@ -493,11 +516,16 @@ EOT
493
516
  payload['userSource']['description'] = params['description']
494
517
  end
495
518
 
496
- # Allow Custom Mappings
519
+ # Enable Role Mapping Permissions
497
520
  if !params['allowCustomMappings'].nil?
498
521
  payload['userSource']['allowCustomMappings'] = params['allowCustomMappings']
499
522
  end
500
523
 
524
+ # Manual Role Assignment
525
+ if !params['manualRoleAssignment'].nil?
526
+ payload['userSource']['manualRoleAssignment'] = params['manualRoleAssignment']
527
+ end
528
+
501
529
  if role_mappings
502
530
  payload['roleMappings'] = role_mappings
503
531
  end
@@ -46,20 +46,22 @@ Examples:
46
46
  EOT
47
47
  end
48
48
  optparse.parse!(args)
49
- # verify_args!(args:args, optparse:optparse, min: 0, max: 2)
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
- path, *ids = args
53
+ # input, *ids = args
54
+ input = args[0]
55
+ id = args[1]
54
56
  # default to index page "/"
55
- path = 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(s) to path if passed
73
- if ids.size > 0
74
- # convert names to ids
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
- ids = ids.collect do |id|
78
- if id.to_s !~ /\A\d{1,}\Z/
79
- # assume the last part of path is the type
80
- record_type = path.split("/").last
81
- record_type.sub!('#!', '')
82
- record = find_by_name(record_type, id)
83
- if record.nil?
84
- raise_command_error("[id] is invalid. No #{record_type} found for '#{id}'", args, optparse)
85
- end
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}/" + ids.join("/")
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
- print output
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
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "6.1.2"
4
+ VERSION = "6.2.1"
5
5
  end
6
6
  end
@@ -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.to_i
137
+ milliseconds = milliseconds.to_i.abs
138
138
  if ms_threshold && ms_threshold > milliseconds
139
139
  out = "#{milliseconds}ms"
140
140
  else