morpheus-cli 5.5.0 → 5.5.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 (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