Converted tower_group

Splitting out tower_inventory_source from tower_group

Copy/Paste typo fix and README update for breaking backwards compatability

Update credential_type module and unit tests
This commit is contained in:
John Westcott IV
2020-02-04 13:00:01 -05:00
committed by beeankha
parent 8a0432efb7
commit 1c505beba6
13 changed files with 81 additions and 125 deletions

View File

@@ -22,6 +22,7 @@ The following notes are changes that may require changes to playbooks.
- Creating a "scan" type job template is no longer supported. - Creating a "scan" type job template is no longer supported.
- `extra_vars` in the `tower_job_launch` module worked with a list previously, but is now configured to work solely in a `dict` format. - `extra_vars` in the `tower_job_launch` module worked with a list previously, but is now configured to work solely in a `dict` format.
- When the `extra_vars` parameter is used with the `tower_job_launch` module, the Job Template launch will fail unless `add_extra_vars` or `survey_enabled` is explicitly set to `True` on the Job Template. - When the `extra_vars` parameter is used with the `tower_job_launch` module, the Job Template launch will fail unless `add_extra_vars` or `survey_enabled` is explicitly set to `True` on the Job Template.
- tower_group used to also service inventory sources. tower_inventory_source has been split out into its own module.
## Running ## Running

View File

@@ -14,6 +14,7 @@ import re
from json import loads, dumps from json import loads, dumps
from os.path import isfile, expanduser, split, join, exists, isdir from os.path import isfile, expanduser, split, join, exists, isdir
from os import access, R_OK, getcwd from os import access, R_OK, getcwd
import yaml
class ConfigFileException(Exception): class ConfigFileException(Exception):
@@ -556,3 +557,27 @@ class TowerModule(AnsibleModule):
return False return False
else: else:
return True return True
def load_variables_if_file_specified(self, vars_value, var_name):
if not vars_value.startswith('@'):
return vars_value
file_name = None
file_content = None
try:
file_name = expanduser(vars_value[1:])
with open(file_name, 'r') as f:
file_content = f.read()
except Exception as e:
self.fail_json(msg="Failed to load file {0} for {1} : {2}".format(file_name, var_name, e))
try:
vars_value = yaml.safe_load(file_content)
except yaml.YAMLError:
# Maybe it wasn't a YAML structure... lets try JSON
try:
vars_value = loads(file_content)
except ValueError:
self.fail_json(msg="Failed to load file {0} specifed by {1} as yaml or json".format(file_name, var_name))
return dumps(vars_value)

View File

@@ -150,7 +150,6 @@ def main():
# Add entries to json_output to match old module # Add entries to json_output to match old module
module.json_output['credential_type'] = name module.json_output['credential_type'] = name
module.json_output['state'] = state module.json_output['state'] = state
module.json_output['existing_credential_type'] = credential_type
if state == 'absent': if state == 'absent':
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this # If the state was absent we can let the module delete it if needed, the module will handle exiting from this

View File

@@ -28,6 +28,11 @@ options:
- The name to use for the group. - The name to use for the group.
required: True required: True
type: str type: str
new_name:
description:
- A new name for this group (for renaming)
required: False
type: str
description: description:
description: description:
- The description to use for the group. - The description to use for the group.
@@ -41,55 +46,17 @@ options:
description: description:
- Variables to use for the group, use C(@) for a file. - Variables to use for the group, use C(@) for a file.
type: str type: str
credential:
description:
- Credential to use for the group.
type: str
source:
description:
- The source to use for this group.
choices: ["manual", "file", "ec2", "vmware", "gce", "azure", "azure_rm", "openstack", "satellite6" , "cloudforms", "custom"]
type: str
source_regions:
description:
- Regions for cloud provider.
type: str
source_vars:
description:
- Override variables from source with variables from this field.
type: str
instance_filters:
description:
- Comma-separated list of filter expressions for matching hosts.
type: str
group_by:
description:
- Limit groups automatically created from inventory source.
type: str
source_script:
description:
- Inventory script to be used when group type is C(custom).
type: str
overwrite:
description:
- Delete child groups and hosts not found in source.
type: bool
default: 'no'
overwrite_vars:
description:
- Override vars in child groups and hosts with those from external source.
type: bool
update_on_launch:
description:
- Refresh inventory data from its source each time a job is run.
type: bool
default: 'no'
state: state:
description: description:
- Desired state of the resource. - Desired state of the resource.
default: "present" default: "present"
choices: ["present", "absent"] choices: ["present", "absent"]
type: str type: str
tower_oauthtoken:
description:
- The Tower OAuth token to use.
required: False
type: str
extends_documentation_fragment: awx.awx.auth extends_documentation_fragment: awx.awx.auth
''' '''
@@ -104,86 +71,62 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg" tower_config_file: "~/tower_cli.cfg"
''' '''
import os from ..module_utils.tower_api import TowerModule
from ..module_utils.ansible_tower import TowerModule, tower_auth_config, tower_check_mode
try:
import tower_cli
import tower_cli.exceptions as exc
from tower_cli.conf import settings
except ImportError:
pass
def main(): def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict( argument_spec = dict(
name=dict(required=True), name=dict(required=True),
new_name=dict(required=False),
description=dict(), description=dict(),
inventory=dict(required=True), inventory=dict(required=True),
variables=dict(), variables=dict(),
credential=dict(),
source=dict(choices=["manual", "file", "ec2", "vmware",
"gce", "azure", "azure_rm", "openstack",
"satellite6", "cloudforms", "custom"]),
source_regions=dict(),
source_vars=dict(),
instance_filters=dict(),
group_by=dict(),
source_script=dict(),
overwrite=dict(type='bool'),
overwrite_vars=dict(type='bool'),
update_on_launch=dict(type='bool'),
state=dict(choices=['present', 'absent'], default='present'), state=dict(choices=['present', 'absent'], default='present'),
) )
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec, supports_check_mode=True) module = TowerModule(argument_spec=argument_spec, supports_check_mode=True)
# Extract our parameters
name = module.params.get('name') name = module.params.get('name')
new_name = module.params.get('new_name')
inventory = module.params.get('inventory') inventory = module.params.get('inventory')
credential = module.params.get('credential') description = module.params.get('description')
state = module.params.pop('state') state = module.params.pop('state')
variables = module.params.get('variables') variables = module.params.get('variables')
# Attempt to look up the related items the user specified (these will fail the module if not found)
inventory_id = module.resolve_name_to_id('inventories', inventory)
# Attempt to look up the object based on the provided name and inventory ID
group = module.get_one('groups', **{
'data': {
'name': name,
'inventory': inventory_id
}
})
# If the variables were specified as a file, load them
if variables: if variables:
if variables.startswith('@'): variables = module.load_variables_if_file_specified(variables, 'variables')
filename = os.path.expanduser(variables[1:])
with open(filename, 'r') as f:
variables = f.read()
json_output = {'group': name, 'state': state} # Create data to sent to create and update
group_fields = {
'name': new_name if new_name else name,
'inventory': inventory_id,
}
if description:
group_fields['description'] = description
if variables:
group_fields['variables'] = variables
tower_auth = tower_auth_config(module) if state == 'absent':
with settings.runtime_values(**tower_auth): # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
tower_check_mode(module) module.delete_if_needed(group)
group = tower_cli.get_resource('group') elif state == 'present':
try: # If the state was present we can let the module build or update the existing group, this will return on its own
params = module.params.copy() module.create_or_update_if_needed(group, group_fields, endpoint='groups', item_type='group')
params['create_on_missing'] = True
params['variables'] = variables
inv_res = tower_cli.get_resource('inventory')
inv = inv_res.get(name=inventory)
params['inventory'] = inv['id']
if credential:
cred_res = tower_cli.get_resource('credential')
cred = cred_res.get(name=credential)
params['credential'] = cred['id']
if state == 'present':
result = group.modify(**params)
json_output['id'] = result['id']
elif state == 'absent':
result = group.delete(**params)
except (exc.NotFound) as excinfo:
module.fail_json(msg='Failed to update the group, inventory not found: {0}'.format(excinfo), changed=False)
except (exc.ConnectionError, exc.BadRequest, exc.AuthError) as excinfo:
module.fail_json(msg='Failed to update the group: {0}'.format(excinfo), changed=False)
json_output['changed'] = result['changed']
module.exit_json(**json_output)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -117,7 +117,7 @@ def main():
} }
}) })
# Create data to sent to create and update # Create the data sent to create and update
inventory_fields = { inventory_fields = {
'name': name, 'name': name,
'description': description, 'description': description,

View File

@@ -43,7 +43,7 @@ options:
description: description:
- The max hosts allowed in this organizations - The max hosts allowed in this organizations
default: "0" default: "0"
type: str type: int
required: False required: False
state: state:
description: description:
@@ -86,7 +86,7 @@ def main():
name=dict(type='str', required=True), name=dict(type='str', required=True),
description=dict(type='str', required=False), description=dict(type='str', required=False),
custom_virtualenv=dict(type='str', required=False), custom_virtualenv=dict(type='str', required=False),
max_hosts=dict(type='str', required=False, default="0"), max_hosts=dict(type='int', required=False, default="0"),
state=dict(type='str', choices=['present', 'absent'], default='present', required=False), state=dict(type='str', choices=['present', 'absent'], default='present', required=False),
) )
@@ -119,12 +119,7 @@ def main():
if custom_virtualenv: if custom_virtualenv:
org_fields['custom_virtualenv'] = custom_virtualenv org_fields['custom_virtualenv'] = custom_virtualenv
if max_hosts: if max_hosts:
int_max_hosts = 0 org_fields['max_hosts'] = max_hosts
try:
int_max_hosts = int(max_hosts)
except Exception:
module.fail_json(msg="Unable to convert max_hosts to an integer")
org_fields['max_hosts'] = int_max_hosts
if state == 'absent': if state == 'absent':
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this # If the state was absent we can let the module delete it if needed, the module will handle exiting from this

View File

@@ -42,7 +42,6 @@ def sanitize_dict(din):
@pytest.fixture @pytest.fixture
def run_module(request): def run_module(request):
# A placeholder to use while modules get converted
def rf(module_name, module_params, request_user): def rf(module_name, module_params, request_user):
def new_request(self, method, url, **kwargs): def new_request(self, method, url, **kwargs):

View File

@@ -78,7 +78,6 @@ def test_create_custom_credential_type(run_module, admin_user):
ct = CredentialType.objects.get(name='Nexus') ct = CredentialType.objects.get(name='Nexus')
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
result.pop('name') result.pop('name')
assert result == { assert result == {
"credential_type": "Nexus", "credential_type": "Nexus",

View File

@@ -25,8 +25,9 @@ def test_create_group(run_module, admin_user):
result.pop('invocation') result.pop('invocation')
assert result == { assert result == {
'credential_type': 'Nexus',
'id': group.id, 'id': group.id,
'group': 'Test Group', 'name': 'Test Group',
'changed': True, 'changed': True,
'state': 'present' 'state': 'present'
} }
@@ -53,7 +54,8 @@ def test_tower_group_idempotent(run_module, admin_user):
result.pop('invocation') result.pop('invocation')
assert result == { assert result == {
'id': group.id, 'id': group.id,
'group': 'Test Group', 'credential_type': 'Nexus',
'name': 'Test Group',
'changed': False, # idempotency assertion 'changed': False, # idempotency assertion
'state': 'present' 'state': 'present'
} }

View File

@@ -27,7 +27,6 @@ def test_create_organization(run_module, admin_user):
assert result.get('changed'), result assert result.get('changed'), result
org = Organization.objects.get(name='foo') org = Organization.objects.get(name='foo')
result.pop('existing_credential_type')
assert result == { assert result == {
"name": "foo", "name": "foo",
"changed": True, "changed": True,
@@ -55,7 +54,6 @@ def test_create_organization_with_venv(run_module, admin_user, mocker):
org = Organization.objects.get(name='foo') org = Organization.objects.get(name='foo')
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
assert result == { assert result == {
"credential_type": "Nexus", "credential_type": "Nexus",
"state": "present", "state": "present",

View File

@@ -23,7 +23,6 @@ def test_create_project(run_module, admin_user, organization):
assert proj.organization == organization assert proj.organization == organization
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
assert result == { assert result == {
'credential_type': 'Nexus', 'credential_type': 'Nexus',
'state': 'present', 'state': 'present',

View File

@@ -20,7 +20,6 @@ def test_create_team(run_module, admin_user):
team = Team.objects.filter(name='foo_team').first() team = Team.objects.filter(name='foo_team').first()
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
assert result == { assert result == {
"changed": True, "changed": True,
"name": "foo_team", "name": "foo_team",
@@ -50,7 +49,6 @@ def test_modify_team(run_module, admin_user):
}, admin_user) }, admin_user)
team.refresh_from_db() team.refresh_from_db()
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
assert result == { assert result == {
"state": "present", "state": "present",
"changed": True, "changed": True,
@@ -67,7 +65,6 @@ def test_modify_team(run_module, admin_user):
'organization': 'foo' 'organization': 'foo'
}, admin_user) }, admin_user)
result.pop('invocation') result.pop('invocation')
result.pop('existing_credential_type')
assert result == { assert result == {
"credential_type": "Nexus", "credential_type": "Nexus",
"name": "foo_team", "name": "foo_team",

View File

@@ -1,2 +1 @@
plugins/modules/tower_group.py use-argspec-type-path plugins/modules/tower_host.py use-argspec-type-path
plugins/modules/tower_host.py use-argspec-type-path