1) Create an org and a team (link the team to the org)
2) Create a new credential via the API and pass the team ID as the owner field:
{
"name": "Test",
"description": "",
"organization": null,
"credential_type": 1,
"inputs": {},
"user": null,
"team": 4
}
3) Check the credentials owners:
Via API:
"owners": [
{
"id": 82,
"type": "organization",
"name": "Ansible",
"description": "",
"url": "/api/v2/organizations/82/"
},
{
"id": 4,
"type": "team",
"name": "Ansible IT",
"description": "",
"url": "/api/v2/teams/4/"
}
]
Via UI:

4) Use awx cli to export the resources awx --conf.username user --conf.password 'pass' --conf.host "https://awx.example.com/" -k export > export.json. Checking the exported data:
The team was exported as:
{
"name": "Ansible IT",
"description": "",
"organization": {
"name": "Ansible",
"type": "organization"
},
"related": {
"roles": [
{
"name": "Use",
"type": "role",
"content_object": {
"organization": {
"name": "Ansible",
"type": "organization"
},
"name": "Test",
"credential_type": {
"name": "Machine",
"kind": "ssh",
"type": "credential_type"
},
"type": "credential"
}
}
]
},
"natural_key": {
"organization": {
"name": "Ansible",
"type": "organization"
},
"name": "Ansible IT",
"type": "team"
}
}
The credential was exported as:
{
"name": "Test",
"description": "",
"inputs": {},
"organization": {
"name": "Ansible",
"type": "organization"
},
"credential_type": {
"name": "Machine",
"kind": "ssh",
"type": "credential_type"
},
"natural_key": {
"organization": {
"name": "Ansible",
"type": "organization"
},
"name": "Test",
"credential_type": {
"name": "Machine",
"kind": "ssh",
"type": "credential_type"
},
"type": "credential"
}
}
And the org was exported as:
{
"name": "Ansible",
"description": "",
"max_hosts": 0,
"custom_virtualenv": null,
"related": {
"notification_templates": [],
"notification_templates_started": [],
"notification_templates_success": [],
"notification_templates_error": [],
"notification_templates_approvals": []
},
"natural_key": {
"name": "Ansible",
"type": "organization"
}
},
5) Delete the credential and import it from the exported data. Check its owners:
Via API:
"owners": [
{
"id": 82,
"type": "organization",
"name": "Ansible",
"description": "",
"url": "/api/v2/organizations/82/"
}
]
Via UI:

Importing credentials should keep the owners as when it was exported.
After importing, the credential does not have the same owners as when it was exported.
GET request at /api/v2/teams/4/roles/ returned similar roles before and after the import:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1462,
"type": "role",
"url": "/api/v2/roles/1462/",
"related": {
"users": "/api/v2/roles/1462/users/",
"teams": "/api/v2/roles/1462/teams/",
"credential": "/api/v2/credentials/42/"
},
"summary_fields": {
"user_capabilities": {
"unattach": true
},
"resource_name": "Test",
"resource_type": "credential",
"resource_type_display_name": "Credential",
"resource_id": 42
},
"name": "Use",
"description": "Can use the credential in a job template"
}
]
}
I've investigated this a little more and found this that some roles are assigned to the when when creating the credential but only the use role returns on API calls.
I've also found how the owners list is returned for roles defined as in the lines above:
So the team admin_role gets linked to the credential's admin_role parents when creating in the API. Since this does not get reflected by the roles endpoints it is probably why the import can't bring the credential's owners as when it was exported.
I've checked the credential via the shell_plus and found the following (I've formatted a little bit to make it easy to read):
>>> [{
... 'id': parent.content_object.pk,
... 'type': camelcase_to_underscore(parent.content_object.__class__.__name__),
... 'name': parent.content_object.name,
... 'description': parent.content_object.description,
... 'url': parent.content_object.get_absolute_url(),
... } for parent in cred.admin_role.parents.all() if parent.object_id is not None]
[
{
'id': 82,
'type': 'organization',
'name': 'Ansible',
'description': '', 'url': '/api/v2/organizations/82/'
},
{
'id': 4,
'type': 'team',
'name': 'Ansible IT',
'description': '',
'url': '/api/v2/teams/4/'
}
]
Due to the issue, inspecting the imported credential only the following is returned:
>>> [{
... 'id': parent.content_object.pk,
... 'type': camelcase_to_underscore(parent.content_object.__class__.__name__),
... 'name': parent.content_object.name,
... 'description': parent.content_object.description,
... 'url': parent.content_object.get_absolute_url(),
... } for parent in cred.admin_role.parents.all() if parent.object_id is not None]
[
{
'id': 82,
'type': 'organization',
'name': 'Ansible',
'description': '',
'url': '/api/v2/organizations/82/'
}
]
Chatted with @AlanCoding about this issue. @elyezer describes the reason import/export is unable to realize this scenario:
So the team admin_role gets linked to the credential's admin_role parents when creating in the API. Since this does not get reflected by the roles endpoints it is probably why the import can't bring the credential's owners as when it was exported.
https://github.com/ansible/awx/blob/fa53cdf3298ab9fa8362c6b033d43d4179ed361d/awx/api/serializers.py#L2674 is called when the team field is supplied on credential creation. This role can _ONLY_ be added to the credential on credential creation and when the team field is supplied.
Further, from the API, we can't _easily_ know that the credential was created in this manor and needs to be _recreated_ in this manor. Exposing that information isn't as trivial as adding the "Admin" role to the list at /api/v2/teams/2/roles/ either.
I agree with @AlanCoding , that we should solve this problem at the AWX API level by exposing the information needed for import/export to make the correct decision. Even after exposing the information needed for import/export the problem isn't entirely solved (see below).
For this work, we will not attempt to solve the problem of allowing for the team.admin_role to be associated w/ resources in the API outside of the creation. This decision means that when a Credential is re-created via import/export, and that Credential already exists, we can't correctly get the Credential in the desired state. But the deficiency of getting resources that already exist into the desired state already exists in import/export and is not something we intend to solve.
New direction to deal with this problem. Detect Credentials of this fancy crazy type via api/v2/credentials/?admin_role__parents__content_type__model=team and don't migrate them.
Ok, new plan. The import/export works as-is. We shouldn't continue to try and support this wacky team field. @AlanCoding eventually wants to phase it out. Currently, when import/export encounters this case, where credential admin roles gain the team admin roles, exporting back into AWX will result in the USE role being given to the associated team. This is a lesser permission than the original grant. The thought is that if a user really wants an admin level privilege to be given to the team for the credential, then the user can do so after.
TL;DR, works as is, write tests to ensure current behavior.
That sounds good @chrismeyersfsu, what are the next steps here, close as won't fix?
I spoke to @wenottingham about:
TL;DR, works as is, write tests to ensure current behavior.
...and it sounds like he's on board with that.
Closing as "works as is"
Most helpful comment
Ok, new plan. The import/export works as-is. We shouldn't continue to try and support this wacky
teamfield. @AlanCoding eventually wants to phase it out. Currently, when import/export encounters this case, where credential admin roles gain the team admin roles, exporting back into AWX will result in the USE role being given to the associated team. This is a lesser permission than the original grant. The thought is that if a user really wants an admin level privilege to be given to the team for the credential, then the user can do so after.TL;DR, works as is, write tests to ensure current behavior.