morpheus-cli 8.0.12.2 → 8.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '06781ec23df17a4185834c843d6b756bc28c9ec2239dd0c97647c8d2d67addc5'
4
- data.tar.gz: c075335c0e1b772e6f8a4bf35fc1186b9cf2abc18ebec73c23b19b0c4283c0c8
3
+ metadata.gz: f058297ee2bc6b9cc1dac3f97bb2aad27785687af9283e89b5275b101a126145
4
+ data.tar.gz: 21f00f181e173a464d579d2f6368d12fbe9880530d01fd59cebc40775eb9af78
5
5
  SHA512:
6
- metadata.gz: f6819ce4ebee4828c06dc23001532103dfbad397dc77de82c1846dbd3078f49e68c3ef84dcea19af82c38abb46b032de20c140babc38f5559457b24a40f7ef85
7
- data.tar.gz: 8970d7c663daf582e01eacbfd12eb9a965f74f91813bf296fac2f49f6cb069b5c1ab6dd233578127516c5f8a84d56e646e12d6a0f23ea4229fc8aae9a8e58265
6
+ metadata.gz: fea8e30a0ff487c060873ca4ba6a3d4af91c52f819e6956675d653dc57c330d495b2f27b9d821e7314da332573acb8bf6bc17380a283fe1404a80789eebda5b0
7
+ data.tar.gz: 8d5fb8b37a542e13f5e9656e2d355b9224ed51ede6a4e749620b3dbdf059a3449b2b09e64c4d97a0b2458469d99508dbf3029a158c1a234a72736ccdc7b6437f
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 8.0.12.2
3
+ RUN gem install morpheus-cli -v 8.1.0
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
data/bin/morpheus CHANGED
@@ -2,11 +2,14 @@
2
2
  require 'morpheus'
3
3
 
4
4
  # arguments
5
- args = ARGV
5
+ args = ARGV.dup
6
6
 
7
- # input pipe
8
- # append piped data as arguments
9
- if !$stdin.tty?
7
+ # input pipe: only read stdin as arguments when --stdin is explicitly passed.
8
+ # Previously stdin was always consumed when not a TTY, which caused morpheus
9
+ # invoked inside a piped bash script (e.g. base64 -d <<< <script> | bash -l)
10
+ # to consume the remaining script lines as CLI arguments (MORPH-6237).
11
+ # Usage: echo 42 | morpheus instances get --stdin
12
+ if args.delete('--stdin') && !$stdin.tty?
10
13
  pipe_data = $stdin.read
11
14
  if pipe_data
12
15
  args += pipe_data.split
@@ -412,6 +412,14 @@ class Morpheus::APIClient
412
412
  Morpheus::ServersInterface.new(common_interface_options).setopts(@options)
413
413
  end
414
414
 
415
+ def systems
416
+ Morpheus::SystemsInterface.new(common_interface_options).setopts(@options)
417
+ end
418
+
419
+ def system_types
420
+ Morpheus::SystemTypesInterface.new(common_interface_options).setopts(@options)
421
+ end
422
+
415
423
  def server_devices
416
424
  Morpheus::ServerDevicesInterface.new(common_interface_options).setopts(@options)
417
425
  end
@@ -118,6 +118,20 @@ class Morpheus::RolesInterface < Morpheus::APIClient
118
118
  execute(method: :put, url: url, headers: headers, payload: payload.to_json)
119
119
  end
120
120
 
121
+ def update_cluster_type(account_id, id, options)
122
+ url = build_url(account_id, id) + "/update-cluster-type"
123
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
124
+ payload = options
125
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
126
+ end
127
+
128
+ def validate(account_id, options, params={})
129
+ url = "#{@base_url}/api/roles/validate"
130
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
131
+ payload = options
132
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json, params: params)
133
+ end
134
+
121
135
  private
122
136
 
123
137
  def build_url(account_id=nil, role_id=nil)
@@ -0,0 +1,13 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::SystemTypesInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/infrastructure/system-types"
7
+ end
8
+
9
+ def list_layouts(type_id, params = {})
10
+ execute(method: :get, url: "#{base_path}/#{type_id}/layouts", params: params)
11
+ end
12
+
13
+ end
@@ -0,0 +1,9 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::SystemsInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/infrastructure/systems"
7
+ end
8
+
9
+ end
@@ -528,6 +528,8 @@ EOT
528
528
  {
529
529
  "ID" => 'id',
530
530
  "Name" => 'name',
531
+ "Type" => lambda {|it| format_backup_type_tag(it) },
532
+ "Location" => lambda {|it| format_backup_location_tag(it) },
531
533
  "Schedule" => lambda {|it| it['schedule']['name'] rescue '' },
532
534
  "Backup Job" => lambda {|it| it['job']['name'] rescue '' },
533
535
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
@@ -539,6 +541,8 @@ EOT
539
541
  {
540
542
  "ID" => 'id',
541
543
  "Name" => 'name',
544
+ "Backup Type" => lambda {|it| format_backup_type_tag(it) },
545
+ "Backup Location" => lambda {|it| format_backup_location_tag(it) },
542
546
  "Location Type" => lambda {|it|
543
547
  if it['locationType'] == "instance"
544
548
  "Instance"
@@ -589,6 +593,33 @@ EOT
589
593
  "#{result['backup']['name']} (#{format_local_dt(result['startDate'])})"
590
594
  end
591
595
 
596
+ # format backup type tag (manual or policy-based)
597
+ def format_backup_type_tag(backup)
598
+ # check if backup has a schdule or job associated with it
599
+ # manual backups typically dont have a schedule while policy-based do
600
+ if backup['cronExpression'] || (backup['schedule'] && backup['schedule']['id'])
601
+ "#{cyan}POLICY#{reset}"
602
+ elsif backup['job'] && backup['job']['id']
603
+ "#{cyan}POLICY#{reset}"
604
+ else
605
+ "#{yellow}MANUAL#{reset}"
606
+ end
607
+ end
608
+
609
+ # format backup location tag (local or remote)
610
+ def format_backup_location_tag(backup)
611
+ # check storage provider or backup provider indicating remote storage
612
+ if backup['storageProvider'] && backup['storageProvider']['id']
613
+ provider_type = backup['storageProvider']['type'] || backup['storageProvider']['providerType'] || ''
614
+ "#{green}REMOTE#{reset} (#{provider_type})"
615
+ elsif backup['backupProvider'] && backup['backupProvider']['id']
616
+ provider_type = backup['backupProvider']['type'] || backup['backupProvider']['providerType'] || ''
617
+ "#{green}REMOTE#{reset} (#{provider_type})"
618
+ else
619
+ "#{blue}LOCAL#{reset}"
620
+ end
621
+ end
622
+
592
623
  # prompt for an instance config (vdiPool.instanceConfig)
593
624
  def prompt_restore_instance_config(options)
594
625
  # use config if user passed one in..
@@ -316,9 +316,8 @@ EOT
316
316
  parse_result = parse_json_or_yaml(file_content)
317
317
  config_map = parse_result[:data]
318
318
  if config_map.nil?
319
- # todo: bubble up JSON.parse error message
320
- raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
321
- #raise_command_error "Failed to parse config as valid YAML or JSON."
319
+ params['config'] = file_content
320
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse config as JSON or YAML. Defaulting to String."
322
321
  else
323
322
  params['config'] = config_map
324
323
  options[:options]['config'] = params['config'] # or file_content
@@ -386,9 +385,8 @@ EOT
386
385
  parse_result = parse_json_or_yaml(config)
387
386
  config_map = parse_result[:data]
388
387
  if config_map.nil?
389
- # todo: bubble up JSON.parse error message
390
- raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
391
- #raise_command_error "Failed to parse config as valid YAML or JSON."
388
+ params['config'] = config
389
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse config as JSON or YAML. Defaulting to String."
392
390
  else
393
391
  params['config'] = config_map
394
392
  end
@@ -466,9 +464,8 @@ EOT
466
464
  parse_result = parse_json_or_yaml(file_content)
467
465
  config_map = parse_result[:data]
468
466
  if config_map.nil?
469
- # todo: bubble up JSON.parse error message
470
- raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
471
- #raise_command_error "Failed to parse config as valid YAML or JSON."
467
+ params['config'] = file_content
468
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse config as JSON or YAML. Defaulting to String."
472
469
  else
473
470
  params['config'] = config_map
474
471
  options[:options]['config'] = params['config'] # or file_content
@@ -529,9 +526,8 @@ EOT
529
526
  parse_result = parse_json_or_yaml(config)
530
527
  config_map = parse_result[:data]
531
528
  if config_map.nil?
532
- # todo: bubble up JSON.parse error message
533
- raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
534
- #raise_command_error "Failed to parse config as valid YAML or JSON."
529
+ params['config'] = config
530
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse config as JSON or YAML. Defaulting to String."
535
531
  else
536
532
  params['config'] = config_map
537
533
  end
@@ -499,7 +499,7 @@ class Morpheus::Cli::Clusters
499
499
 
500
500
  # Group / Site
501
501
  group = load_group(cluster_type['code'], options)
502
- cluster_payload['group'] = {'id' => group['id']}
502
+ cluster_payload['group'] = {'id' => group['id']} if group
503
503
 
504
504
  # Cluster Name
505
505
  if args.empty? && options[:no_prompt]
@@ -561,19 +561,19 @@ class Morpheus::Cli::Clusters
561
561
 
562
562
  # Cloud / Zone
563
563
  cloud_id = nil
564
- cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group['id'], options[:cloud]) : nil
564
+ cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group ? group['id'] : nil, options[:cloud]) : nil
565
565
  if cloud
566
566
  # load full cloud
567
567
  cloud = @clouds_interface.get(cloud['id'])['zone']
568
568
  cloud_id = cloud['id']
569
569
  else
570
- available_clouds = get_available_clouds(group['id'], {groupType: cluster_payload['type']})
570
+ available_clouds = get_available_clouds(group ? group['id'] : nil, {groupType: cluster_payload['type']})
571
571
 
572
572
  if available_clouds.empty?
573
- print_red_alert "Group #{group['name']} has no available clouds"
573
+ print_red_alert group ? "Group #{group['name']} has no available clouds" : "No available clouds"
574
574
  exit 1
575
575
  else
576
- cloud_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => available_clouds, 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group['id']})['cloud']
576
+ cloud_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => available_clouds, 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group ? group['id'] : nil})['cloud']
577
577
  end
578
578
  cloud = @clouds_interface.get(cloud_id)['zone']
579
579
  end
@@ -598,7 +598,7 @@ class Morpheus::Cli::Clusters
598
598
  # Provision Type
599
599
  provision_type = (layout && layout['provisionType'] ? layout['provisionType'] : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
600
600
  provision_type = @provision_types_interface.get(provision_type['id'])['provisionType'] if !provision_type.nil?
601
- api_params = {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id'], groupTypeId: cluster_type['id'], provisionType: provision_type['code'], provisionTypeId: provision_type['id']}
601
+ api_params = {zoneId: cloud['id'], siteId: group ? group['id'] : nil, layoutId: layout['id'], groupTypeId: cluster_type['id'], provisionType: provision_type['code'], provisionTypeId: provision_type['id']}.compact
602
602
 
603
603
  # Service Plan
604
604
  service_plan = prompt_service_plan(api_params, options)
@@ -809,7 +809,7 @@ class Morpheus::Cli::Clusters
809
809
  elsif server_payload['sshHosts'].is_a?(Array)
810
810
  server_payload['sshHosts'] = server_payload['sshHosts'].collect {|it| it.is_a?(String) ? {"ip" => it} : it }
811
811
  end
812
-
812
+ server_payload['sshHosts'] = server_payload['sshHosts'].flatten
813
813
  # inject the optionalNames array into the sshHosts if present
814
814
  if server_payload['optionalNames']
815
815
  optional_names = server_payload['optionalNames'].is_a?(String) ? server_payload['optionalNames'].split(",") : [server_payload['optionalNames']].flatten
@@ -1344,7 +1344,7 @@ class Morpheus::Cli::Clusters
1344
1344
  server_payload['labels'] = labels if labels
1345
1345
 
1346
1346
  # Cloud
1347
- available_clouds = options_interface.options_for_source('clouds', {groupId: cluster['site']['id'], clusterId: cluster['id'], ownerOnly: true})['data']
1347
+ available_clouds = options_interface.options_for_source('clouds', {groupId: cluster['site'] ? cluster['site']['id'] : nil, clusterId: cluster['id'], ownerOnly: true}.compact)['data']
1348
1348
  cloud_id = nil
1349
1349
 
1350
1350
  if options[:cloud]
@@ -1366,12 +1366,12 @@ class Morpheus::Cli::Clusters
1366
1366
  # resources (zone pools)
1367
1367
  cloud = @clouds_interface.get(cloud_id)['zone']
1368
1368
  cloud['zoneType'] = get_cloud_type(cloud['zoneType']['id'])
1369
- group = @groups_interface.get(cluster['site']['id'])['group']
1369
+ group = cluster['site'] ? @groups_interface.get(cluster['site']['id'])['group'] : nil
1370
1370
  provision_type = server_type['provisionType'] || {}
1371
1371
  provision_type = @provision_types_interface.get(provision_type['id'])['provisionType'] if !provision_type.nil?
1372
1372
 
1373
1373
  server_payload['cloud'] = {'id' => cloud_id}
1374
- service_plan = prompt_service_plan({zoneId: cloud_id, siteId: cluster['site']['id'], provisionTypeId: server_type['provisionType']['id'], groupTypeId: cluster_type['id'], }, options)
1374
+ service_plan = prompt_service_plan({zoneId: cloud_id, siteId: group ? group['id'] : nil, provisionTypeId: server_type['provisionType']['id'], groupTypeId: cluster_type['id']}.compact, options)
1375
1375
 
1376
1376
  if service_plan
1377
1377
  server_payload['plan'] = {'code' => service_plan['code']}
@@ -1420,7 +1420,7 @@ class Morpheus::Cli::Clusters
1420
1420
  metadata_option_type = cluster_type['optionTypes'].find {|type| type['fieldName'] == 'metadata' }
1421
1421
  option_type_list = option_type_list.reject {|type| type['fieldName'] == 'metadata' }
1422
1422
 
1423
- server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id']}))
1423
+ server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, {zoneId: cloud['id'], siteId: group ? group['id'] : nil, layoutId: layout['id']}.compact))
1424
1424
 
1425
1425
  # Metadata Tags
1426
1426
  if metadata_option_type
@@ -4751,11 +4751,15 @@ class Morpheus::Cli::Clusters
4751
4751
  print_red_alert "No available groups"
4752
4752
  exit 1
4753
4753
  else available_groups.count > 1 && !options[:no_prompt]
4754
- group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => available_groups, 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{})['group']
4754
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => available_groups, 'required' => false, 'description' => 'Select Group.'}],options[:options],@api_client,{})['group']
4755
4755
  end
4756
4756
  end
4757
4757
  end
4758
- @groups_interface.get(group_id)['group']
4758
+ if group_id
4759
+ return @groups_interface.get(group_id)['group']
4760
+ else
4761
+ return nil
4762
+ end
4759
4763
  end
4760
4764
 
4761
4765
  def prompt_service_plan(api_params, options)
@@ -4948,7 +4952,7 @@ class Morpheus::Cli::Clusters
4948
4952
  resource_pool = options[:resourcePool] ? find_cloud_resource_pool_by_name_or_id(cloud['id'], options[:resourcePool]) : nil
4949
4953
 
4950
4954
  if !resource_pool
4951
- resource_pool_options = @options_interface.options_for_source('zonePools', {groupId: group['id'], zoneId: cloud['id']}.merge(service_plan ? {planId: service_plan['id']} : {}))['data'].reject { |it| it['id'].nil? && it['name'].nil? }
4955
+ resource_pool_options = @options_interface.options_for_source('zonePools', {groupId: group ? group['id'] : nil, zoneId: cloud['id'], planId: service_plan ? service_plan['id'] : nil}).compact['data'].reject { |it| it['id'].nil? && it['name'].nil? }
4952
4956
 
4953
4957
  if resource_pool_options.empty?
4954
4958
  print yellow,bold, "Cloud #{cloud['name']} has no available resource pools",reset,"\n\n"
@@ -171,11 +171,17 @@ class Morpheus::Cli::LibraryOptionListsCommand
171
171
  "Credentials" => lambda {|it| it['credential'] ? (it['credential']['type'] == 'local' ? '(Local)' : it['credential']['name']) : nil },
172
172
  "Username" => 'serviceUsername',
173
173
  "Password" => 'servicePassword',
174
+ "Inject System Auth Header" => lambda {|it| format_boolean it['injectExecutionLeaseAuth'] },
175
+ "Use Owner Authorization" => lambda {|it| format_boolean it['useOwnerAuth'] }
174
176
  }
175
177
  option_list_columns.delete("API Type") if option_type_list['type'] != 'api'
176
178
  option_list_columns.delete("Credentials") if !['rest','ldap'].include?(option_type_list['type']) # || !(option_type_list['credential'] && option_type_list['credential']['id'])
177
179
  option_list_columns.delete("Username") if !['rest','ldap'].include?(option_type_list['type']) || !(option_type_list['serviceUsername'])
178
180
  option_list_columns.delete("Password") if !['rest','ldap'].include?(option_type_list['type']) || !(option_type_list['servicePassword'])
181
+ option_list_columns.delete("Inject System Auth Header") if option_type_list['type'] != 'rest'
182
+ option_list_columns.delete("Use Owner Authorization") if option_type_list['type'] != 'rest'
183
+ option_list_columns.delete("Inject System Auth Header") if option_type_list['type'] != 'rest'
184
+ option_list_columns.delete("Use Owner Authorization") if option_type_list['type'] != 'rest'
179
185
  source_headers = []
180
186
  if option_type_list['config'] && option_type_list['config']['sourceHeaders']
181
187
  source_headers = option_type_list['config']['sourceHeaders'].collect do |header|
@@ -291,7 +297,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
291
297
  end
292
298
  end
293
299
  # tweak payload for API
294
- ['ignoreSSLErrors', 'realTime'].each { |k|
300
+ ['ignoreSSLErrors', 'realTime', 'injectExecutionLeaseAuth', 'useOwnerAuth'].each { |k|
295
301
  list_payload[k] = ['on','true'].include?(list_payload[k].to_s) if list_payload.key?(k)
296
302
  }
297
303
  payload.deep_merge!({'optionTypeList' => list_payload})
@@ -348,7 +354,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
348
354
  end
349
355
  end
350
356
  # tweak payload for API
351
- ['ignoreSSLErrors', 'realTime'].each { |k|
357
+ ['ignoreSSLErrors', 'realTime', 'injectExecutionLeaseAuth', 'useOwnerAuth'].each { |k|
352
358
  list_payload[k] = ['on','true'].include?(list_payload[k].to_s) if list_payload.key?(k)
353
359
  }
354
360
  payload.deep_merge!({'optionTypeList' => list_payload})
@@ -437,6 +443,8 @@ class Morpheus::Cli::LibraryOptionListsCommand
437
443
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'sourceUrl', 'fieldLabel' => 'Source Url', 'type' => 'text', 'required' => true, 'description' => "A REST URL can be used to fetch list data and is cached in the appliance database.", 'displayOrder' => 6},
438
444
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'ignoreSSLErrors', 'fieldLabel' => 'Ignore SSL Errors', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 7},
439
445
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'realTime', 'fieldLabel' => 'Real Time', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 8},
446
+ {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'injectExecutionLeaseAuth', 'switch' => 'inject-execution-lease-auth', 'fieldLabel' => 'Inject System Auth Header', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Injects an authorization header using a system lease token when making the REST call.', 'displayOrder' => 21},
447
+ {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'useOwnerAuth', 'switch' => 'use-owner-auth', 'fieldLabel' => 'Use Owner Authorization', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Uses the authorization credentials of the owner of the option list.', 'displayOrder' => 22},
440
448
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'sourceMethod', 'fieldLabel' => 'Source Method', 'type' => 'select', 'selectOptions' => [{'name' => 'GET', 'value' => 'GET'}, {'name' => 'POST', 'value' => 'POST'}], 'defaultValue' => 'GET', 'required' => true, 'displayOrder' => 9},
441
449
  {'dependsOnCode' => 'optionTypeList.type:rest|ldap', 'fieldName' => 'credential', 'fieldLabel' => 'Credentials', 'type' => 'select', 'optionSource' => 'credentials', 'description' => 'Credential ID or use "local" to specify username and password', 'displayOrder' => 10, 'defaultValue' => "local", 'required' => true, :for_help_only => true}, # hacky way to render this but not prompt for it
442
450
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'serviceUsername', 'fieldLabel' => 'Username', 'type' => 'text', 'description' => "A Basic Auth Username for use when type is 'rest'.", 'displayOrder' => 11, "credentialFieldContext" => 'credential', "credentialFieldName" => 'username', "credentialType" => "username-password,oauth2"},