morpheus-cli 5.4.5 → 5.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +4 -0
  4. data/lib/morpheus/api/clusters_interface.rb +12 -0
  5. data/lib/morpheus/api/network_pool_servers_interface.rb +7 -0
  6. data/lib/morpheus/api/prices_interface.rb +6 -0
  7. data/lib/morpheus/api/scale_thresholds_interface.rb +9 -0
  8. data/lib/morpheus/cli/cli_command.rb +55 -23
  9. data/lib/morpheus/cli/commands/apps.rb +2 -2
  10. data/lib/morpheus/cli/commands/cloud_resource_pools_command.rb +33 -2
  11. data/lib/morpheus/cli/commands/clouds.rb +61 -31
  12. data/lib/morpheus/cli/commands/clusters.rb +66 -5
  13. data/lib/morpheus/cli/commands/cypher_command.rb +22 -23
  14. data/lib/morpheus/cli/commands/hosts.rb +5 -1
  15. data/lib/morpheus/cli/commands/instances.rb +12 -12
  16. data/lib/morpheus/cli/commands/integrations_command.rb +1 -1
  17. data/lib/morpheus/cli/commands/invoices_command.rb +8 -1
  18. data/lib/morpheus/cli/commands/jobs_command.rb +45 -225
  19. data/lib/morpheus/cli/commands/library_container_types_command.rb +52 -3
  20. data/lib/morpheus/cli/commands/library_option_lists_command.rb +18 -8
  21. data/lib/morpheus/cli/commands/library_option_types_command.rb +56 -62
  22. data/lib/morpheus/cli/commands/load_balancers.rb +11 -19
  23. data/lib/morpheus/cli/commands/network_pool_servers_command.rb +5 -2
  24. data/lib/morpheus/cli/commands/prices_command.rb +25 -11
  25. data/lib/morpheus/cli/commands/roles.rb +475 -70
  26. data/lib/morpheus/cli/commands/scale_thresholds.rb +103 -0
  27. data/lib/morpheus/cli/commands/tasks.rb +64 -22
  28. data/lib/morpheus/cli/commands/user_sources_command.rb +107 -39
  29. data/lib/morpheus/cli/commands/users.rb +10 -10
  30. data/lib/morpheus/cli/commands/view.rb +1 -0
  31. data/lib/morpheus/cli/commands/workflows.rb +21 -14
  32. data/lib/morpheus/cli/error_handler.rb +13 -4
  33. data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
  34. data/lib/morpheus/cli/mixins/execution_request_helper.rb +1 -1
  35. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +3 -3
  36. data/lib/morpheus/cli/mixins/jobs_helper.rb +173 -0
  37. data/lib/morpheus/cli/mixins/print_helper.rb +120 -38
  38. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -3
  39. data/lib/morpheus/cli/mixins/rest_command.rb +41 -14
  40. data/lib/morpheus/cli/option_types.rb +69 -13
  41. data/lib/morpheus/cli/version.rb +1 -1
  42. data/lib/morpheus/logging.rb +6 -8
  43. data/lib/morpheus/routes.rb +3 -5
  44. metadata +6 -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)