morpheus-cli 5.4.5.1 → 5.5.1.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/backup_service_types_interface.rb +9 -0
  5. data/lib/morpheus/api/backup_services_interface.rb +9 -0
  6. data/lib/morpheus/api/clusters_interface.rb +12 -0
  7. data/lib/morpheus/api/network_pool_servers_interface.rb +7 -0
  8. data/lib/morpheus/api/prices_interface.rb +6 -0
  9. data/lib/morpheus/api/scale_thresholds_interface.rb +9 -0
  10. data/lib/morpheus/cli/cli_command.rb +55 -23
  11. data/lib/morpheus/cli/commands/apps.rb +1 -1
  12. data/lib/morpheus/cli/commands/backup_services_command.rb +44 -0
  13. data/lib/morpheus/cli/commands/cloud_resource_pools_command.rb +33 -2
  14. data/lib/morpheus/cli/commands/clouds.rb +67 -31
  15. data/lib/morpheus/cli/commands/clusters.rb +66 -5
  16. data/lib/morpheus/cli/commands/cypher_command.rb +22 -23
  17. data/lib/morpheus/cli/commands/hosts.rb +5 -1
  18. data/lib/morpheus/cli/commands/instances.rb +11 -11
  19. data/lib/morpheus/cli/commands/integrations_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/invoices_command.rb +8 -1
  21. data/lib/morpheus/cli/commands/jobs_command.rb +45 -225
  22. data/lib/morpheus/cli/commands/library_container_types_command.rb +52 -3
  23. data/lib/morpheus/cli/commands/library_option_lists_command.rb +18 -8
  24. data/lib/morpheus/cli/commands/library_option_types_command.rb +56 -62
  25. data/lib/morpheus/cli/commands/load_balancers.rb +11 -19
  26. data/lib/morpheus/cli/commands/network_pool_servers_command.rb +5 -2
  27. data/lib/morpheus/cli/commands/prices_command.rb +25 -11
  28. data/lib/morpheus/cli/commands/roles.rb +475 -70
  29. data/lib/morpheus/cli/commands/scale_thresholds.rb +103 -0
  30. data/lib/morpheus/cli/commands/tasks.rb +64 -22
  31. data/lib/morpheus/cli/commands/user_sources_command.rb +107 -39
  32. data/lib/morpheus/cli/commands/users.rb +10 -10
  33. data/lib/morpheus/cli/commands/view.rb +1 -0
  34. data/lib/morpheus/cli/commands/workflows.rb +21 -14
  35. data/lib/morpheus/cli/error_handler.rb +13 -4
  36. data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
  37. data/lib/morpheus/cli/mixins/execution_request_helper.rb +1 -1
  38. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +3 -3
  39. data/lib/morpheus/cli/mixins/jobs_helper.rb +173 -0
  40. data/lib/morpheus/cli/mixins/print_helper.rb +120 -38
  41. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -3
  42. data/lib/morpheus/cli/mixins/rest_command.rb +58 -15
  43. data/lib/morpheus/cli/option_types.rb +69 -13
  44. data/lib/morpheus/cli/version.rb +1 -1
  45. data/lib/morpheus/logging.rb +6 -8
  46. data/lib/morpheus/routes.rb +3 -5
  47. metadata +9 -4
@@ -0,0 +1,103 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::ScaleThresholds
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::RestCommand
6
+
7
+ set_command_name :'scale-thresholds'
8
+ set_command_description "View and manage scale thresholds."
9
+ register_subcommands :list, :get, :add, :update, :remove
10
+
11
+ # using convention matching /scale-thresholds now
12
+
13
+ # RestCommand settings
14
+ #set_rest_interface_name :thresholds
15
+ # register_interfaces :thresholds
16
+ # set_rest_interface_name :thresholds
17
+
18
+ # set_rest_name :threshold
19
+ # set_rest_label "Scale Threshold"
20
+ # set_rest_label_plural "Scale Thresholds"
21
+
22
+ protected
23
+
24
+ # def threshold_object_key
25
+ # 'scaleThreshold'
26
+ # end
27
+
28
+ # def threshold_list_key
29
+ # 'scaleThresholds'
30
+ # end
31
+
32
+ # def threshold_label
33
+ # 'Scale Threshold'
34
+ # end
35
+
36
+ # def threshold_label_plural
37
+ # 'Scale Thresholds'
38
+ # end
39
+
40
+ def scale_threshold_list_column_definitions(options)
41
+ {
42
+ "ID" => 'id',
43
+ "Name" => 'name',
44
+ "CPU" => lambda {|it| it['cpuEnabled'] ? "✔" : '' },
45
+ "Memory" => lambda {|it| it['memoryEnabled'] ? "✔" : '' },
46
+ "Disk" => lambda {|it| it['diskEnabled'] ? "✔" : '' },
47
+ }
48
+ end
49
+
50
+ def scale_threshold_column_definitions(options)
51
+ {
52
+ "ID" => 'id',
53
+ "Name" => 'name',
54
+ "Auto Upscale" => lambda {|it| format_boolean(it['autoUp']) },
55
+ "Auto Downscale" => lambda {|it| format_boolean(it['autoDown']) },
56
+ "Min Count" => lambda {|it| it['minCount'] },
57
+ "Max Count" => lambda {|it| it['maxCount'] },
58
+ "Enable CPU Threshold" => lambda {|it| format_boolean(it['cpuEnabled']) },
59
+ "Min CPU" => lambda {|it| it['minCpu'] },
60
+ "Max CPU" => lambda {|it| it['maxCpu'] },
61
+ "Enable Memory Threshold" => lambda {|it| format_boolean(it['memoryEnabled']) },
62
+ "Min Memory" => lambda {|it| it['minMemory'] },
63
+ "Max Memory" => lambda {|it| it['maxMemory'] },
64
+ "Enable Disk Threshold" => lambda {|it| format_boolean(it['diskEnabled']) },
65
+ "Min Disk" => lambda {|it| it['minDisk'] },
66
+ "Max Disk" => lambda {|it| it['maxDisk'] },
67
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
68
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
69
+ }
70
+ end
71
+
72
+ def add_scale_threshold_option_types()
73
+ [
74
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 10},
75
+ {'fieldName' => 'autoUp', 'fieldLabel' => 'Auto Upscale', 'type' => 'checkbox', 'required' => true, 'defaultValue' => false, 'displayOrder' => 30},
76
+ {'fieldName' => 'autoDown', 'fieldLabel' => 'Auto Downscale', 'type' => 'checkbox', 'required' => true, 'defaultValue' => false, 'displayOrder' => 40},
77
+ {'fieldName' => 'minCount', 'fieldLabel' => 'Min Count', 'type' => 'number', 'required' => true, 'defaultValue' => 1, 'displayOrder' => 41},
78
+ {'fieldName' => 'maxCount', 'fieldLabel' => 'Max Count', 'type' => 'number', 'required' => true, 'defaultValue' => 2, 'displayOrder' => 42},
79
+ {'fieldName' => 'cpuEnabled', 'fieldLabel' => 'Enable CPU Threshold', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'displayOrder' => 50},
80
+ {'fieldName' => 'minCpu', 'fieldLabel' => 'Min CPU', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 60},
81
+ {'fieldName' => 'maxCpu', 'fieldLabel' => 'Max CPU', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 70},
82
+ {'fieldName' => 'memoryEnabled', 'fieldLabel' => 'Enable Memory Threshold', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'displayOrder' => 80},
83
+ {'fieldName' => 'minMemory', 'fieldLabel' => 'Min Memory', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 90},
84
+ {'fieldName' => 'maxMemory', 'fieldLabel' => 'Max Memory', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 100},
85
+ {'fieldName' => 'diskEnabled', 'fieldLabel' => 'Enable Disk Threshold', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'displayOrder' => 110},
86
+ {'fieldName' => 'minDisk', 'fieldLabel' => 'Min Disk', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 120},
87
+ {'fieldName' => 'maxDisk', 'fieldLabel' => 'Max Disk', 'type' => 'number', 'required' => false, 'defaultValue' => 0, 'displayOrder' => 130},
88
+ ]
89
+ end
90
+
91
+ def add_scale_threshold_advanced_option_types()
92
+ []
93
+ end
94
+
95
+ def update_scale_threshold_option_types()
96
+ add_scale_threshold_option_types.collect {|it| it.delete('required'); it.delete('defaultValue'); it.delete('dependsOnCode'); it }
97
+ end
98
+
99
+ def update_scale_threshold_advanced_option_types()
100
+ add_scale_threshold_advanced_option_types.collect {|it| it.delete('required'); it.delete('defaultValue'); it.delete('dependsOnCode'); it }
101
+ end
102
+
103
+ end
@@ -2,6 +2,7 @@ require 'morpheus/cli/cli_command'
2
2
 
3
3
  class Morpheus::Cli::Tasks
4
4
  include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::JobsHelper
5
6
 
6
7
  register_subcommands :list, :get, :add, :update, :remove, :execute
7
8
  register_subcommands :'list-types' => :list_task_types
@@ -297,6 +298,9 @@ class Morpheus::Cli::Tasks
297
298
  opts.on('--execute-target VALUE', String, "Execute Target" ) do |val|
298
299
  options[:options]['executeTarget'] = val
299
300
  end
301
+ opts.on('--credential VALUE', String, "Credential ID or \"local\"" ) do |val|
302
+ options[:options]['credential'] = val
303
+ end
300
304
  opts.on('--target-host VALUE', String, "Target Host" ) do |val|
301
305
  options[:options]['taskOptions'] ||= {}
302
306
  options[:options]['taskOptions']['host'] = val
@@ -313,6 +317,10 @@ class Morpheus::Cli::Tasks
313
317
  options[:options]['taskOptions'] ||= {}
314
318
  options[:options]['taskOptions']['password'] = val
315
319
  end
320
+ opts.on('--target-ssh-key VALUE', String, "Target SSH Key" ) do |val|
321
+ options[:options]['taskOptions'] ||= {}
322
+ options[:options]['taskOptions']['sshKey'] = val
323
+ end
316
324
  opts.on('--git-repo VALUE', String, "Git Repo ID" ) do |val|
317
325
  options[:options]['taskOptions'] ||= {}
318
326
  options[:options]['taskOptions']['localScriptGitId'] = val
@@ -521,17 +529,38 @@ class Morpheus::Cli::Tasks
521
529
  payload['task']['taskOptions'] ||= {}
522
530
  payload['task']['taskOptions']['port'] = v_prompt['taskOptions']['port']
523
531
  end
524
- # Host
525
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'taskOptions', 'fieldName' => 'username', 'fieldLabel' => 'Username', 'type' => 'text', 'description' => 'Username for remote execution'}], options[:options], @api_client)
526
- if v_prompt['taskOptions'] && !v_prompt['taskOptions']['username'].to_s.empty?
527
- payload['task']['taskOptions'] ||= {}
528
- payload['task']['taskOptions']['username'] = v_prompt['taskOptions']['username']
532
+ # Credentials
533
+ credential_code = "credential"
534
+ credential_option_type = {'code' => credential_code, 'fieldName' => credential_code, 'fieldLabel' => 'Credentials', 'type' => 'select', 'optionSource' => 'credentials', 'description' => 'Enter an existing credential ID or choose "local"', 'defaultValue' => "local", 'required' => true}
535
+ supported_credential_types = ['username-keypair', 'username-password', 'username-password-keypair'].compact.flatten.join(",").split(",").collect {|it| it.strip }
536
+ credential_params = {"new" => false, "credentialTypes" => supported_credential_types}
537
+ credential_value = Morpheus::Cli::OptionTypes.select_prompt(credential_option_type, @api_client, credential_params, options[:no_prompt], options[:options][credential_code])
538
+ if !credential_value.to_s.empty?
539
+ if credential_value == "local"
540
+ payload['task'][credential_code] = {"type" => credential_value}
541
+ elsif credential_value.to_s =~ /\A\d{1,}\Z/
542
+ payload['task'][credential_code] = {"id" => credential_value.to_i}
543
+ end
529
544
  end
530
- # Host
531
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'taskOptions', 'fieldName' => 'password', 'fieldLabel' => 'Password', 'type' => 'password', 'description' => 'Password for remote execution'}], options[:options], @api_client)
532
- if v_prompt['taskOptions'] && !v_prompt['taskOptions']['password'].to_s.empty?
533
- payload['task']['taskOptions'] ||= {}
534
- payload['task']['taskOptions']['password'] = v_prompt['taskOptions']['password']
545
+ if credential_value == "local"
546
+ # Username
547
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'taskOptions', 'fieldName' => 'username', 'fieldLabel' => 'Username', 'type' => 'text', 'description' => 'Username for remote execution'}], options[:options], @api_client)
548
+ if v_prompt['taskOptions'] && !v_prompt['taskOptions']['username'].to_s.empty?
549
+ payload['task']['taskOptions'] ||= {}
550
+ payload['task']['taskOptions']['username'] = v_prompt['taskOptions']['username']
551
+ end
552
+ # Password
553
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'taskOptions', 'fieldName' => 'password', 'fieldLabel' => 'Password', 'type' => 'password', 'description' => 'Password for remote execution'}], options[:options], @api_client)
554
+ if v_prompt['taskOptions'] && !v_prompt['taskOptions']['password'].to_s.empty?
555
+ payload['task']['taskOptions'] ||= {}
556
+ payload['task']['taskOptions']['password'] = v_prompt['taskOptions']['password']
557
+ end
558
+ # SSH Key
559
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'taskOptions', 'fieldName' => 'sshKey', 'fieldLabel' => 'Key', 'type' => 'select', 'optionSource' => 'keyPairs', 'description' => 'SSH Key for remote execution'}], options[:options], @api_client)
560
+ if v_prompt['taskOptions'] && !v_prompt['taskOptions']['sshKey'].to_s.empty?
561
+ payload['task']['taskOptions'] ||= {}
562
+ payload['task']['taskOptions']['sshKey'] = v_prompt['taskOptions']['sshKey']
563
+ end
535
564
  end
536
565
  end
537
566
 
@@ -639,6 +668,9 @@ class Morpheus::Cli::Tasks
639
668
  opts.on('--execute-target VALUE', String, "Execute Target" ) do |val|
640
669
  options[:options]['executeTarget'] = val
641
670
  end
671
+ opts.on('--credential VALUE', String, "Credential ID or \"local\"" ) do |val|
672
+ options[:options]['credential'] = val
673
+ end
642
674
  opts.on('--target-host VALUE', String, "Target Host" ) do |val|
643
675
  options[:options]['taskOptions'] ||= {}
644
676
  options[:options]['taskOptions']['host'] = val
@@ -655,6 +687,10 @@ class Morpheus::Cli::Tasks
655
687
  options[:options]['taskOptions'] ||= {}
656
688
  options[:options]['taskOptions']['password'] = val
657
689
  end
690
+ opts.on('--target-ssh-key VALUE', String, "Target SSH Key" ) do |val|
691
+ options[:options]['taskOptions'] ||= {}
692
+ options[:options]['taskOptions']['sshKey'] = val
693
+ end
658
694
  opts.on('--git-repo VALUE', String, "Git Repo ID" ) do |val|
659
695
  options[:options]['taskOptions'] ||= {}
660
696
  options[:options]['taskOptions']['localScriptGitId'] = val
@@ -795,6 +831,7 @@ class Morpheus::Cli::Tasks
795
831
  instances = []
796
832
  server_ids = []
797
833
  servers = []
834
+ default_refresh_interval = 10
798
835
  optparse = Morpheus::Cli::OptionParser.new do |opts|
799
836
  opts.banner = subcommand_usage("[task] --instance [instance] [options]")
800
837
  opts.on('--instance INSTANCE', String, "Instance name or id to execute the task on. This option can be passed more than once.") do |val|
@@ -829,6 +866,12 @@ class Morpheus::Cli::Tasks
829
866
  opts.on('--config [TEXT]', String, "Custom config") do |val|
830
867
  params['customConfig'] = val.to_s
831
868
  end
869
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
870
+ options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
871
+ end
872
+ opts.on(nil, '--no-refresh', "Do not refresh" ) do
873
+ options[:no_refresh] = true
874
+ end
832
875
  build_common_options(opts, options, [:options, :json, :dry_run, :remote])
833
876
  end
834
877
  optparse.parse!(args)
@@ -901,10 +944,7 @@ class Morpheus::Cli::Tasks
901
944
  return
902
945
  end
903
946
  json_response = @tasks_interface.run(task['id'], payload)
904
- if options[:json]
905
- puts as_json(json_response, options)
906
- return json_response['success'] ? 0 : 1
907
- else
947
+ render_response(json_response, options) do
908
948
  target_desc = nil
909
949
  if instances.size() > 0
910
950
  target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
@@ -916,17 +956,19 @@ class Morpheus::Cli::Tasks
916
956
  else
917
957
  print_green_success "Executing task #{task['name']}"
918
958
  end
919
- # todo: refresh, use get processId and load process record isntead? err
920
959
  if json_response["jobExecution"] && json_response["jobExecution"]["id"]
921
- get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
922
- Morpheus::Logging::DarkPrinter.puts((['jobs', 'get-execution'] + get_args).join(' ')) if Morpheus::Logging.debug?
923
- return ::Morpheus::Cli::JobsCommand.new.handle(['get-execution'] + get_args)
960
+ job_execution_id = json_response["jobExecution"]["id"]
961
+ if options[:no_refresh]
962
+ get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
963
+ Morpheus::Logging::DarkPrinter.puts((['jobs', 'get-execution'] + get_args).join(' ')) if Morpheus::Logging.debug?
964
+ ::Morpheus::Cli::JobsCommand.new.handle(['get-execution'] + get_args)
965
+ else
966
+ #Morpheus::Cli::JobsCommand.new.handle(["get-execution", job_execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
967
+ job_execution_results = wait_for_job_execution(job_execution_id, options.merge({:details => true}))
968
+ end
924
969
  end
925
- return json_response['success'] ? 0 : 1
926
970
  end
927
- rescue RestClient::Exception => e
928
- print_rest_exception(e, options)
929
- return 1
971
+ return 0, nil
930
972
  end
931
973
  end
932
974
 
@@ -261,7 +261,7 @@ EOT
261
261
  end
262
262
  end
263
263
 
264
- opts.on('--default-role ID', String, "Default Role ID") do |val|
264
+ opts.on('--default-role ID', String, "Default Role ID or Authority") do |val|
265
265
  default_role_id = val
266
266
  end
267
267
  #build_option_type_options(opts, options, add_user_source_option_types())
@@ -285,24 +285,62 @@ EOT
285
285
 
286
286
 
287
287
 
288
- # find the account first, or just prompt for that too please.
289
- if !account_id
290
- print_error Morpheus::Terminal.angry_prompt
291
- puts_error "missing required argument [account]\n#{optparse}"
292
- return 1
288
+ # # find the account first, or just prompt for that too please.
289
+ # if !account_id
290
+ # print_error Morpheus::Terminal.angry_prompt
291
+ # puts_error "missing required argument [account]\n#{optparse}"
292
+ # return 1
293
+ # end
294
+
295
+ # tenant is optional, it is expected in the url right now instead of in the payload...this sets both
296
+ account = nil
297
+ if account_id
298
+ options[:options]['tenant'] = account_id
293
299
  end
294
- account = find_account_by_name_or_id(account_id)
295
- return 1 if account.nil?
296
- account_id = account['id']
300
+ account_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tenant', 'fieldLabel' => 'Tenant', 'type' => 'select', 'optionSource' => 'tenants', 'required' => false, 'description' => 'Tenant'}], options[:options], @api_client)
301
+ if account_id
302
+ options[:options].delete('tenant')
303
+ end
304
+ account_id = account_prompt['tenant']
305
+ if !account_id.to_s.empty?
306
+ # reload tenant by id, sure why not..
307
+ account = find_account_by_name_or_id(account_id)
308
+ return 1 if account.nil?
309
+ account_id = account['id']
310
+ else
311
+ account_id = nil
312
+ end
313
+
297
314
 
298
315
  # construct payload
299
316
  payload = {}
300
317
  if options[:payload]
301
318
  payload = options[:payload]
302
319
  payload.deep_merge!({'userSource' => parse_passed_options(options)})
320
+
321
+ # JD: should apply options on top of payload, but just do these two for now
322
+
323
+ # Tenant
324
+ if account
325
+ payload['userSource']['account'] = {'id' => account['id'] }
326
+ end
327
+
328
+ # Name
329
+ if params['name']
330
+ payload['userSource']['name'] = params['name']
331
+ end
332
+
303
333
  else
304
334
  payload.deep_merge!({'userSource' => parse_passed_options(options)})
305
335
 
336
+ # support old -O options
337
+ payload['userSource'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
338
+
339
+ # Tenant
340
+ if account
341
+ payload['userSource']['account'] = {'id' => account['id'] }
342
+ end
343
+
306
344
  # Identity Source Type
307
345
  user_source_types = @user_sources_interface.list_types({userSelectable: true})['userSourceTypes']
308
346
  if user_source_types.empty?
@@ -338,16 +376,15 @@ EOT
338
376
  payload['userSource'].deep_merge!(v_prompt)
339
377
 
340
378
  # Default Account Role
341
- # todo: a proper select
342
- if !default_role_id
343
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'defaultAccountRole', 'fieldName' => 'id', 'type' => 'text', 'fieldLabel' => 'Default Account Role ID', 'required' => true}], options[:options])
344
- if v_prompt['defaultAccountRole'] && v_prompt['defaultAccountRole']['id']
345
- default_role_id = v_prompt['defaultAccountRole']['id']
346
- end
347
- end
379
+ # always prompt for role to lookup id from name
348
380
  if default_role_id
349
- payload['userSource']['defaultAccountRole'] = {'id' => default_role_id }
381
+ options[:options]['defaultAccountRole'] = {'id' => default_role_id }
382
+ end
383
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'defaultAccountRole', 'fieldName' => 'id', 'type' => 'select', 'selectOptions' => get_available_role_options(account_id), 'fieldLabel' => 'Default Role', 'required' => false }], options[:options])
384
+ if v_prompt['defaultAccountRole'] && v_prompt['defaultAccountRole']['id']
385
+ default_role_id = v_prompt['defaultAccountRole']['id']
350
386
  end
387
+ payload['userSource']['defaultAccountRole'] = {'id' => default_role_id }
351
388
 
352
389
  # Allow Custom Mappings
353
390
  if !params['allowCustomMappings'].nil?
@@ -364,9 +401,6 @@ EOT
364
401
  if role_mapping_names
365
402
  payload['roleMappingNames'] = role_mapping_names
366
403
  end
367
-
368
- # support old -O options
369
- payload['userSource'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
370
404
 
371
405
 
372
406
  end
@@ -378,14 +412,12 @@ EOT
378
412
  # do it
379
413
  json_response = @user_sources_interface.create(account_id, payload)
380
414
  # print and return result
381
- if options[:json]
382
- puts as_json(json_response, options)
383
- return 0
415
+ render_response(json_response, options, 'userSource') do
416
+ user_source = json_response['userSource']
417
+ print_green_success "Added Identity Source #{user_source['name']}"
418
+ _get(user_source['id'], {}, options)
384
419
  end
385
- user_source = json_response['userSource']
386
- print_green_success "Added Identity Source #{user_source['name']}"
387
- get([user_source['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
388
- return 0
420
+ return 0, nil
389
421
  end
390
422
 
391
423
  def update(args)
@@ -394,6 +426,7 @@ EOT
394
426
  account_id = nil
395
427
  role_mappings = nil
396
428
  role_mapping_names = nil
429
+ default_role_id = nil
397
430
  optparse = Morpheus::Cli::OptionParser.new do|opts|
398
431
  opts.banner = subcommand_usage("[name] [options]")
399
432
  opts.on('--name VALUE', String, "Name for this identity source") do |val|
@@ -427,6 +460,9 @@ EOT
427
460
  end
428
461
  end
429
462
  end
463
+ opts.on('--default-role ROLE', String, "Default Role ID or Authority") do |val|
464
+ default_role_id = val
465
+ end
430
466
  build_standard_update_options(opts, options)
431
467
  opts.footer = "Update an identity source." + "\n" +
432
468
  "[name] is required. This is the name or id of an identity source."
@@ -470,22 +506,33 @@ EOT
470
506
  payload['roleMappingNames'] = role_mapping_names
471
507
  end
472
508
 
509
+ # Default Account Role
510
+ if default_role_id
511
+ if default_role_id == 'null'
512
+ payload['userSource']['defaultAccountRole'] = {'id' => nil }
513
+ else
514
+ # use no_prompt to convert name to id
515
+ options[:options]['defaultAccountRole'] = {'id' => default_role_id }
516
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt([{'fieldContext' => 'defaultAccountRole', 'fieldName' => 'id', 'type' => 'select', 'selectOptions' => get_available_role_options(user_source['account']['id']), 'fieldLabel' => 'Default Role', 'required' => false }], options[:options])
517
+ if v_prompt['defaultAccountRole'] && v_prompt['defaultAccountRole']['id']
518
+ default_role_id = v_prompt['defaultAccountRole']['id']
519
+ end
520
+ payload['userSource']['defaultAccountRole'] = {'id' => default_role_id }
521
+ end
522
+ end
473
523
  end
474
524
  @user_sources_interface.setopts(options)
475
525
  if options[:dry_run]
476
526
  print_dry_run @user_sources_interface.dry.update(nil, user_source['id'], payload)
477
527
  return
478
528
  end
479
-
480
529
  json_response = @user_sources_interface.update(nil, user_source['id'], payload)
481
-
482
- if options[:json]
483
- puts JSON.pretty_generate(json_response)
484
- return
530
+ render_response(json_response, options, 'userSource') do
531
+ user_source = json_response['userSource'] || user_source
532
+ print_green_success "Updated Identity Source #{user_source['name']}"
533
+ _get(user_source['id'], {}, options)
485
534
  end
486
-
487
- print_green_success "Updated Identity Source #{params['name'] || user_source['name']}"
488
- get([user_source['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
535
+ return 0, nil
489
536
  rescue RestClient::Exception => e
490
537
  print_rest_exception(e, options)
491
538
  exit 1
@@ -941,10 +988,20 @@ EOT
941
988
  ]
942
989
  elsif type_code == 'saml'
943
990
  [
944
- {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'Login Redirect URL', 'required' => true, 'description' => ''},
945
- {'fieldContext' => 'config', 'fieldName' => 'doNotIncludeSAMLRequest', 'type' => 'checkbox', 'fieldLabel' => 'Exclude SAMLRequest Parameter', 'required' => true, 'description' => 'Do not include SAMLRequest parameter', 'defaultValue' => false},
946
- {'fieldContext' => 'config', 'fieldName' => 'logoutUrl', 'type' => 'text', 'fieldLabel' => 'Logout Post URL', 'required' => true, 'description' => ''},
947
- {'fieldContext' => 'config', 'fieldName' => 'publicKey', 'type' => 'textarea', 'fieldLabel' => 'Signing Public Key', 'required' => true, 'description' => ''},
991
+ {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'Login Redirect URL', 'required' => true, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 1},
992
+ {'fieldContext' => 'config', 'fieldName' => 'doNotIncludeSAMLRequest', 'type' => 'checkbox', 'fieldLabel' => 'Exclude SAMLRequest Parameter', 'required' => true, 'description' => 'Do not include SAMLRequest parameter', 'defaultValue' => false, 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 2},
993
+ {'fieldContext' => 'config', 'fieldName' => 'logoutUrl', 'type' => 'text', 'fieldLabel' => 'Logout Post URL', 'required' => true, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 3},
994
+ {'code' => 'saml.SAMLSignatureMode', 'fieldContext' => 'config', 'fieldName' => 'SAMLSignatureMode', 'type' => 'select', 'selectOptions' => [{'name' => 'No Signature', 'value' => 'NoSignature'},{'name' => 'Self Signed', 'value' => 'SelfSigned'},{'name' => 'Custom RSA Signature', 'value' => 'CustomSignature'}], 'fieldLabel' => 'SAML Request', 'required' => true, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'defaultValue' => 'NoSignature', 'displayOrder' => 4},
995
+ {'dependsOnCode' => 'saml.SAMLSignatureMode:CustomSignature', 'fieldContext' => 'config', 'fieldName' => 'request509Certificate', 'type' => 'textarea', 'fieldLabel' => 'X.509 Certificate', 'required' => false, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 5},
996
+ {'dependsOnCode' => 'saml.SAMLSignatureMode:CustomSignature', 'fieldContext' => 'config', 'fieldName' => 'requestPrivateKey', 'type' => 'textarea', 'fieldLabel' => 'RSA Private Key', 'required' => false, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 6},
997
+ {'code' => 'saml.doNotValidateSignature', 'fieldContext' => 'config', 'fieldName' => 'doNotValidateSignature', 'type' => 'select', 'selectOptions' => [{'name' => 'Do Not Validate Assertion Signature', 'value' => 'true'},{'name' => 'Validate Assertion Signature', 'value' => 'false'}], 'fieldLabel' => 'SAML Response', 'required' => true, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'defaultValue' => 'Do Not Validate Assertion Signature', 'displayOrder' => 7},
998
+ {'dependsOnCode' => 'saml.doNotValidateSignature:false', 'fieldContext' => 'config', 'fieldName' => 'publicKey', 'type' => 'textarea', 'fieldLabel' => 'Signing Public Key', 'required' => false, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 8},
999
+ {'fieldContext' => 'config', 'fieldName' => 'privateKey', 'type' => 'textarea', 'fieldLabel' => 'Encryption RSA Private Key', 'required' => false, 'description' => '', 'fieldGroup' => 'SAML SSO Configuration', 'displayOrder' => 9},
1000
+ {'fieldContext' => 'config', 'fieldName' => 'givenNameAttribute', 'type' => 'text', 'fieldLabel' => 'Given Name Attribute Name', 'required' => false, 'description' => '', 'fieldGroup' => 'Assertion Attribute Mappings', 'displayOrder' => 10},
1001
+ {'fieldContext' => 'config', 'fieldName' => 'surnameAttribute', 'type' => 'text', 'fieldLabel' => 'Surname Attribute Name', 'required' => false, 'description' => '', 'fieldGroup' => 'Assertion Attribute Mappings', 'displayOrder' => 11},
1002
+ {'fieldContext' => 'config', 'fieldName' => 'roleAttributeName', 'type' => 'text', 'fieldLabel' => 'Role Attribute Name', 'required' => false, 'description' => '', 'fieldGroup' => 'Role Mappings', 'displayOrder' => 12},
1003
+ {'fieldContext' => 'config', 'fieldName' => 'requiredAttributeValue', 'type' => 'text', 'fieldLabel' => 'Required Role Attribute Value', 'required' => false, 'description' => '', 'fieldGroup' => 'Role Mappings', 'displayOrder' => 13},
1004
+
948
1005
  ]
949
1006
  elsif type_code == 'customExternal'
950
1007
  [
@@ -966,4 +1023,15 @@ EOT
966
1023
  []
967
1024
  end
968
1025
  end
1026
+
1027
+ def get_available_role_options(account_id)
1028
+ available_roles = @account_users_interface.available_roles(account_id)['roles']
1029
+ # if available_roles.empty?
1030
+ # print_red_alert "No available roles found."
1031
+ # exit 1
1032
+ # end
1033
+ role_options = available_roles.collect {|role|
1034
+ {'name' => role['authority'], 'value' => role['id']}
1035
+ }
1036
+ end
969
1037
  end
@@ -172,8 +172,8 @@ class Morpheus::Cli::Users
172
172
  options[:include_personas_access] = true
173
173
  params['includeAccess'] = true
174
174
  end
175
- opts.on('-i', '--include-none-access', "Include Items with 'None' Access in Access List") do
176
- options[:display_none_access] = true
175
+ opts.on(nil, '--hide-none-access', "Hide records with 'none' access") do
176
+ options[:hide_none_access] = true
177
177
  end
178
178
  build_standard_get_options(opts, options, [:account])
179
179
  opts.footer = <<-EOT
@@ -245,11 +245,11 @@ EOT
245
245
  end
246
246
  else
247
247
  available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type',
248
- 'app_templates' => 'Blueprint', 'catalog_item_types' => 'Catalog Item Types', 'personas' => 'Personas'}
248
+ 'app_templates' => 'Blueprint', 'catalog_item_types' => 'Catalog Item Type', 'personas' => 'Persona', 'vdi_pools' => 'VDI Pool', 'report_types' => 'Report Type'}
249
249
  available_field_options.each do |field, label|
250
250
  if !(field == 'sites' && is_tenant_account) && options["include_#{field}_access".to_sym]
251
251
  access = user['access'][field.split('_').enum_for(:each_with_index).collect {|word, idx| idx == 0 ? word : word.capitalize}.join]
252
- access = access.reject {|it| it['access'] == 'none'} if !options[:display_none_access]
252
+ access = access.reject {|it| it['access'] == 'none'} if options[:hide_none_access]
253
253
 
254
254
  if field == "features"
255
255
  # print_h2 "Permissions", options
@@ -291,10 +291,10 @@ EOT
291
291
  opts.on('-g','--global', "Global (All Tenants). Find users across all tenants. Default is your own tenant only.") do
292
292
  options[:global] = true
293
293
  end
294
- build_common_options(opts, options, [:account, :json, :yaml, :csv, :fields, :dry_run, :remote])
295
- opts.on('-i', '--include-none-access', "Include Items with 'None' Access in Access List") do
296
- options[:display_none_access] = true
294
+ opts.on('--hide-none-access', "Hide records with 'none' access") do
295
+ options[:hide_none_access] = true
297
296
  end
297
+ build_common_options(opts, options, [:account, :json, :yaml, :csv, :fields, :dry_run, :remote])
298
298
  opts.footer = "Display Access for a user." + "\n" +
299
299
  "[user] is required. This is the username or id of a user."
300
300
  end
@@ -366,11 +366,11 @@ EOT
366
366
 
367
367
  print_h1 "User Permissions: #{user['username']}", options
368
368
 
369
- available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type', 'app_templates' => 'Blueprint'}
369
+ available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type', 'app_templates' => 'Blueprint', 'catalog_item_types' => 'Catalog Item Type', 'vdi_pools' => 'VDI Pool', 'report_types' => 'Report Type','personas' => 'Persona'}
370
370
  available_field_options.each do |field, label|
371
371
  if !(field == 'sites' && is_tenant_account)
372
372
  access = json_response['access'][field.split('_').enum_for(:each_with_index).collect {|word, idx| idx == 0 ? word : word.capitalize}.join]
373
- access = access.reject {|it| it['access'] == 'none'} if !options[:display_none_access]
373
+ access = access.reject {|it| it['access'] == 'none'} if options[:hide_none_access]
374
374
 
375
375
  print_h2 "#{label} Access", options
376
376
  print cyan
@@ -381,7 +381,7 @@ EOT
381
381
  if access.count > 0
382
382
  access.each {|it| it['access'] = format_access_string(it['access'], available_access_levels)}
383
383
 
384
- if ['features', 'instance_types'].include?(field)
384
+ if ['features', 'instance_types', 'report_types'].include?(field)
385
385
  print as_pretty_table(access, [:name, :code, :access], options)
386
386
  else
387
387
  print as_pretty_table(access, [:name, :access], options)
@@ -74,6 +74,7 @@ EOT
74
74
  if id.to_s !~ /\A\d{1,}\Z/
75
75
  # assume the last part of path is the type
76
76
  record_type = path.split("/").last
77
+ record_type.sub!('#!', '')
77
78
  record = find_by_name(record_type, id)
78
79
  if record.nil?
79
80
  raise_command_error("[id] is invalid. No #{record_type} found for '#{id}'", args, optparse)