morpheus-cli 5.5.0 → 5.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) 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/scale_thresholds_interface.rb +9 -0
  7. data/lib/morpheus/cli/cli_command.rb +39 -20
  8. data/lib/morpheus/cli/commands/apps.rb +1 -1
  9. data/lib/morpheus/cli/commands/cloud_resource_pools_command.rb +33 -2
  10. data/lib/morpheus/cli/commands/clouds.rb +12 -6
  11. data/lib/morpheus/cli/commands/clusters.rb +66 -5
  12. data/lib/morpheus/cli/commands/hosts.rb +5 -1
  13. data/lib/morpheus/cli/commands/instances.rb +1 -1
  14. data/lib/morpheus/cli/commands/integrations_command.rb +1 -1
  15. data/lib/morpheus/cli/commands/invoices_command.rb +8 -1
  16. data/lib/morpheus/cli/commands/jobs_command.rb +45 -225
  17. data/lib/morpheus/cli/commands/library_container_types_command.rb +52 -3
  18. data/lib/morpheus/cli/commands/library_option_types_command.rb +56 -62
  19. data/lib/morpheus/cli/commands/load_balancers.rb +11 -19
  20. data/lib/morpheus/cli/commands/network_pool_servers_command.rb +5 -2
  21. data/lib/morpheus/cli/commands/roles.rb +475 -70
  22. data/lib/morpheus/cli/commands/scale_thresholds.rb +103 -0
  23. data/lib/morpheus/cli/commands/tasks.rb +19 -12
  24. data/lib/morpheus/cli/commands/user_sources_command.rb +107 -39
  25. data/lib/morpheus/cli/commands/users.rb +10 -10
  26. data/lib/morpheus/cli/commands/view.rb +1 -0
  27. data/lib/morpheus/cli/commands/workflows.rb +21 -14
  28. data/lib/morpheus/cli/error_handler.rb +13 -4
  29. data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
  30. data/lib/morpheus/cli/mixins/execution_request_helper.rb +1 -1
  31. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +3 -3
  32. data/lib/morpheus/cli/mixins/jobs_helper.rb +173 -0
  33. data/lib/morpheus/cli/mixins/print_helper.rb +120 -38
  34. data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -3
  35. data/lib/morpheus/cli/mixins/rest_command.rb +41 -14
  36. data/lib/morpheus/cli/option_types.rb +68 -37
  37. data/lib/morpheus/cli/version.rb +1 -1
  38. data/lib/morpheus/logging.rb +6 -8
  39. 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
@@ -830,6 +831,7 @@ class Morpheus::Cli::Tasks
830
831
  instances = []
831
832
  server_ids = []
832
833
  servers = []
834
+ default_refresh_interval = 10
833
835
  optparse = Morpheus::Cli::OptionParser.new do |opts|
834
836
  opts.banner = subcommand_usage("[task] --instance [instance] [options]")
835
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|
@@ -864,6 +866,12 @@ class Morpheus::Cli::Tasks
864
866
  opts.on('--config [TEXT]', String, "Custom config") do |val|
865
867
  params['customConfig'] = val.to_s
866
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
867
875
  build_common_options(opts, options, [:options, :json, :dry_run, :remote])
868
876
  end
869
877
  optparse.parse!(args)
@@ -936,10 +944,7 @@ class Morpheus::Cli::Tasks
936
944
  return
937
945
  end
938
946
  json_response = @tasks_interface.run(task['id'], payload)
939
- if options[:json]
940
- puts as_json(json_response, options)
941
- return json_response['success'] ? 0 : 1
942
- else
947
+ render_response(json_response, options) do
943
948
  target_desc = nil
944
949
  if instances.size() > 0
945
950
  target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
@@ -951,17 +956,19 @@ class Morpheus::Cli::Tasks
951
956
  else
952
957
  print_green_success "Executing task #{task['name']}"
953
958
  end
954
- # todo: refresh, use get processId and load process record isntead? err
955
959
  if json_response["jobExecution"] && json_response["jobExecution"]["id"]
956
- get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
957
- Morpheus::Logging::DarkPrinter.puts((['jobs', 'get-execution'] + get_args).join(' ')) if Morpheus::Logging.debug?
958
- 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
959
969
  end
960
- return json_response['success'] ? 0 : 1
961
970
  end
962
- rescue RestClient::Exception => e
963
- print_rest_exception(e, options)
964
- return 1
971
+ return 0, nil
965
972
  end
966
973
  end
967
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)
@@ -2,6 +2,7 @@ require 'morpheus/cli/cli_command'
2
2
 
3
3
  class Morpheus::Cli::Workflows
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
  set_default_subcommand :list
@@ -538,6 +539,7 @@ class Morpheus::Cli::Workflows
538
539
  instances = []
539
540
  server_ids = []
540
541
  servers = []
542
+ default_refresh_interval = 10
541
543
  optparse = Morpheus::Cli::OptionParser.new do |opts|
542
544
  opts.banner = subcommand_usage("[workflow] --instance [instance] [options]")
543
545
  opts.on('--instance INSTANCE', String, "Instance name or id to execute the workflow on. This option can be passed more than once.") do |val|
@@ -572,6 +574,12 @@ class Morpheus::Cli::Workflows
572
574
  opts.on('--config [TEXT]', String, "Custom config") do |val|
573
575
  params['customConfig'] = val.to_s
574
576
  end
577
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
578
+ options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
579
+ end
580
+ opts.on(nil, '--no-refresh', "Do not refresh" ) do
581
+ options[:no_refresh] = true
582
+ end
575
583
  build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
576
584
  end
577
585
  optparse.parse!(args)
@@ -580,7 +588,7 @@ class Morpheus::Cli::Workflows
580
588
  end
581
589
  workflow_name = args[0]
582
590
  connect(options)
583
- begin
591
+
584
592
  workflow = find_workflow_by_name_or_id(workflow_name)
585
593
  return 1 if workflow.nil?
586
594
 
@@ -642,10 +650,8 @@ class Morpheus::Cli::Workflows
642
650
  return 0
643
651
  end
644
652
  json_response = @task_sets_interface.run(workflow['id'], payload)
645
- if options[:json]
646
- puts as_json(json_response, options)
647
- return json_response['success'] ? 0 : 1
648
- else
653
+
654
+ render_response(json_response, options) do
649
655
  target_desc = nil
650
656
  if instances.size() > 0
651
657
  target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
@@ -657,18 +663,19 @@ class Morpheus::Cli::Workflows
657
663
  else
658
664
  print_green_success "Executing workflow #{workflow['name']}"
659
665
  end
660
- # todo: refresh, use get processId and load process record isntead? err
661
666
  if json_response["jobExecution"] && json_response["jobExecution"]["id"]
662
- get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
663
- Morpheus::Logging::DarkPrinter.puts((['jobs', 'get-execution'] + get_args).join(' ')) if Morpheus::Logging.debug?
664
- return ::Morpheus::Cli::JobsCommand.new.handle(['get-execution'] + get_args)
667
+ job_execution_id = json_response["jobExecution"]["id"]
668
+ if options[:no_refresh]
669
+ get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
670
+ Morpheus::Logging::DarkPrinter.puts((['jobs', 'get-execution'] + get_args).join(' ')) if Morpheus::Logging.debug?
671
+ ::Morpheus::Cli::JobsCommand.new.handle(['get-execution'] + get_args)
672
+ else
673
+ #Morpheus::Cli::JobsCommand.new.handle(["get-execution", job_execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
674
+ job_execution_results = wait_for_job_execution(job_execution_id, options.merge({:details => true}))
675
+ end
665
676
  end
666
- return json_response['success'] ? 0 : 1
667
677
  end
668
- rescue RestClient::Exception => e
669
- print_rest_exception(e, options)
670
- return 1
671
- end
678
+ return 0, nil
672
679
  end
673
680
 
674
681
  private
@@ -44,8 +44,13 @@ class Morpheus::Cli::ErrorHandler
44
44
  # exit_code = 127
45
45
  when Morpheus::Cli::CommandArgumentsError
46
46
  puts_angry_error err.message
47
- @stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
48
- @stderr.puts "Try --help for more usage information"
47
+ if err.args.include?("--help") || err.args.include?("--help")
48
+ @stderr.puts err.optparse
49
+ else
50
+ @stderr.puts err.optparse.banner if err.optparse && err.optparse.banner
51
+ @stderr.puts "Try --help for more usage information"
52
+ end
53
+
49
54
  do_print_stacktrace = false
50
55
  if err.exit_code
51
56
  exit_code = err.exit_code
@@ -60,8 +65,12 @@ class Morpheus::Cli::ErrorHandler
60
65
  if !message_lines.empty?
61
66
  @stderr.puts message_lines.join("\n") unless message_lines.empty?
62
67
  else
63
- @stderr.puts err.optparse.banner if err.optparse && err.optparse.banner && message_lines.empty?
64
- @stderr.puts "Try --help for more usage information"
68
+ if err.args.include?("--help") || err.args.include?("--help")
69
+ @stderr.puts err.optparse
70
+ else
71
+ @stderr.puts err.optparse.banner if err.optparse && err.optparse.banner && message_lines.empty?
72
+ @stderr.puts "Try --help for more usage information"
73
+ end
65
74
  end
66
75
  do_print_stacktrace = false
67
76
  if err.exit_code
@@ -136,7 +136,7 @@ module Morpheus::Cli::AccountsHelper
136
136
  "Multitenant" => lambda {|it|
137
137
  format_boolean(it['multitenant']).to_s + (it['multitenantLocked'] ? " (LOCKED)" : "")
138
138
  },
139
- "Default Persona" => lambda {|it| it['defaultPersona'] ? it['defaultPersona']['name'] : '(standard)' },
139
+ "Default Persona" => lambda {|it| it['defaultPersona'] ? it['defaultPersona']['name'] : '' },
140
140
  "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
141
141
  #"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
142
142
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
@@ -1,6 +1,6 @@
1
1
  require 'morpheus/cli/mixins/print_helper'
2
2
  # Mixin for Morpheus::Cli command classes
3
- # Provides common methods for provisioning instances
3
+ # Provides refreshing execution-request records by unique id (uuid)
4
4
  module Morpheus::Cli::ExecutionRequestHelper
5
5
 
6
6
  def self.included(klass)
@@ -111,9 +111,9 @@ module Morpheus::Cli::InfrastructureHelper
111
111
  return cloud
112
112
  end
113
113
 
114
- def get_available_cloud_types(refresh=false)
114
+ def get_available_cloud_types(refresh=false, params = {})
115
115
  if !@available_cloud_types || refresh
116
- @available_cloud_types = clouds_interface.cloud_types({max:1000})['zoneTypes']
116
+ @available_cloud_types = clouds_interface.cloud_types({max:1000}.deep_merge(params))['zoneTypes']
117
117
  end
118
118
  return @available_cloud_types
119
119
  end
@@ -130,7 +130,7 @@ module Morpheus::Cli::InfrastructureHelper
130
130
  end
131
131
 
132
132
  def cloud_type_for_name(name)
133
- return get_available_cloud_types().find { |z| z['name'].downcase == name.downcase || z['code'].downcase == name.downcase}
133
+ return get_available_cloud_types(true, {'name' => name}).find { |z| z['name'].downcase == name.downcase || z['code'].downcase == name.downcase}
134
134
  end
135
135
 
136
136