Initial cut at tower_token module

This commit is contained in:
John Westcott IV 2020-05-21 13:14:10 -04:00
parent e6b1e55274
commit e6416d770b
4 changed files with 220 additions and 0 deletions

View File

@ -96,6 +96,13 @@ class TowerModule(AnsibleModule):
if direct_value is not None:
setattr(self, short_param, direct_value)
# Perform magic depending on whether tower_oauthtoken is a string or a dict
if self.params.get('tower_oauthtoken'):
if type(self.params.get('tower_oauthtoken')) is dict:
setattr(self, 'oauth_token', self.params.get('tower_oauthtoken')['token'])
elif 'token' in self.params.get('tower_oauthtoken'):
setattr(self, 'oauth_token', self.params.get('tower_oauthtoken'))
# Perform some basic validation
if not re.match('^https{0,1}://', self.host):
self.host = "https://{0}".format(self.host)
@ -504,6 +511,9 @@ class TowerModule(AnsibleModule):
item_name = existing_item['username']
elif 'identifier' in existing_item:
item_name = existing_item['identifier']
elif item_type == 'o_auth2_access_token':
# An oauth2 token has no name, instead we will use its id for any of the messages
item_name = existing_item['id']
else:
self.fail_json(msg="Unable to process delete of {0} due to missing name".format(item_type))

View File

@ -0,0 +1,157 @@
#!/usr/bin/python
# coding: utf-8 -*-
# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: tower_token
author: "John Westcott IV (@john-westcott-iv)"
version_added: "2.3"
short_description: create, update, or destroy Ansible Tower tokens.
description:
- Create, update, or destroy Ansible Tower tokens. See
U(https://www.ansible.com/tower) for an overview.
options:
description:
description:
- Optional description of this access token.
required: False
type: str
default: ''
application:
description:
- The application tied to this token.
required: False
type: str
scope:
description:
- Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write'].
required: False
type: str
default: 'write'
choices: ["read", "write"]
existing_token:
description: An existing token (for use with state absent)
type: dict
state:
description:
- Desired state of the resource.
choices: ["present", "absent"]
default: "present"
type: str
tower_oauthtoken:
description:
- The Tower OAuth token to use.
required: False
type: str
version_added: "3.7"
extends_documentation_fragment: awx.awx.auth
'''
EXAMPLES = '''
- name: Create a new token using an existing token
tower_token:
description: '{{ token_description }}'
scope: "write"
state: present
tower_oauthtoken: "{{ ny_existing_token }}"
register: new_token
- name: Use our new token to make another call
tower_job_list:
tower_oauthtoken: "{{ tower_token }}"
- name: Delete our Token with the token we created
tower_token:
existing_token: "{{ tower_token }}"
state: absent
'''
RETURNS = '''
tower_token:
type: dict
description: A Tower token object which can be used for auth or token deletion
returned: on successful create
'''
from ..module_utils.tower_api import TowerModule
def return_token(module, last_response):
# A token is special because you can never get the actual token ID back from the API.
# So the default module return would give you an ID but then the token would forever be masked on you.
# This method will return the entire token object we got back so that a user has access to the token
module.json_output['token'] = last_response['token']
module.json_output['ansible_facts'] = {
'tower_token': last_response,
}
module.exit_json(**module.json_output)
def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict(
description=dict(),
application=dict(),
scope=dict(choices=['read', 'write'], default='write'),
existing_token=dict(type='dict'),
state=dict(choices=['present', 'absent'], default='present'),
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
# Extract our parameters
description = module.params.get('description')
application = module.params.get('application')
scope = module.params.get('scope')
existing_token = module.params.get('existing_token')
state = module.params.get('state')
with open('/tmp/john', 'w') as f:
f.write("State is {0}".format(state))
if state == 'absent':
with open('/tmp/john', 'a') as f:
f.write("Starting delete")
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
module.delete_if_needed(existing_token)
# Attempt to look up the related items the user specified (these will fail the module if not found)
application_id = None
if application:
application_id = module.resolve_name_to_id('applications', application)
# Create the data that gets sent for create and update
new_fields = {}
if description is not None:
new_fields['description'] = description
if application is not None:
new_fields['application'] = application_id
if scope is not None:
new_fields['scope'] = scope
# If the state was present and we can let the module build or update the existing item, this will return on its own
module.create_or_update_if_needed(
None, new_fields,
endpoint='tokens', item_type='token',
associations={
},
on_create=return_token,
)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,28 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
from awx.main.models import OAuth2AccessToken
@pytest.mark.django_db
def test_create_token(run_module, admin_user):
module_args = {
'description': 'barfoo',
'state': 'present',
'scope': 'read',
'tower_host': None,
'tower_username': None,
'tower_password': None,
'validate_certs': None,
'tower_oauthtoken': None,
'tower_config_file': None,
}
result = run_module('tower_token', module_args, admin_user)
assert result, result.get('changed')
tokens = OAuth2AccessToken.objects.filter(description='barfoo')
assert len(tokens) == 1, tokens[0].description == 'barfoo'

View File

@ -0,0 +1,25 @@
---
- name: Generate names
set_fact:
token_description: "AWX-Collection-tests-tower_token-description-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
- name: Create a Token
tower_token:
description: '{{ token_description }}'
scope: "write"
state: present
register: new_token
- name: Validate our token works by token
tower_job_list:
tower_oauthtoken: "{{ tower_token.token }}"
- name: Validate out token works by object
tower_job_list:
tower_oauthtoken: "{{ tower_token }}"
- name: Delete our Token with our own token
tower_token:
existing_token: "{{ tower_token }}"
tower_oauthtoken: "{{ tower_token }}"
state: absent