Converting tower_inventory_source

Fix up inventory_source module changes, fix import yaml sanity error, change inventory_source unit tests to comply with new structure.
This commit is contained in:
John Westcott IV
2020-02-05 13:24:46 -05:00
committed by beeankha
parent c08d402e66
commit 9955ee6548
3 changed files with 307 additions and 278 deletions

View File

@@ -1,8 +1,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError
from ansible.module_utils.six import PY2 from ansible.module_utils.six import PY2
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
@@ -14,7 +13,12 @@ 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
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
class ConfigFileException(Exception): class ConfigFileException(Exception):
@@ -56,15 +60,15 @@ class TowerModule(AnsibleModule):
mutually_exclusive_if = kwargs.pop('mutually_exclusive_if', None) mutually_exclusive_if = kwargs.pop('mutually_exclusive_if', None)
super(TowerModule, self).__init__(argument_spec=args, **kwargs) super(TowerModule, self).__init__(argument_spec=args, **kwargs)
# Eventually, we would like to push this as a feature to Ansible core for others to use... # Eventually, we would like to push this as a feature to Ansible core for others to use...
# Test mutually_exclusive if # Test mutually_exclusive if
if mutually_exclusive_if: if mutually_exclusive_if:
for (var_name, var_value, exclusive_names) in mutually_exclusive_if: for (var_name, var_value, exclusive_names) in mutually_exclusive_if:
if self.params.get(var_name) == var_value: if self.params.get(var_name) == var_value:
for excluded_param_name in exclusive_names: for excluded_param_name in exclusive_names:
if self.params.get(excluded_param_name) != None: if self.params.get(excluded_param_name) is not None:
self.fail_json(msg='Arguments {} can not be set if source is {}'.format(', '.join(exclusive_names), var_value)) self.fail_json(msg='Arguments {0} can not be set if source is {1}'.format(', '.join(exclusive_names), var_value))
self.load_config_files() self.load_config_files()
@@ -574,6 +578,9 @@ class TowerModule(AnsibleModule):
if not vars_value.startswith('@'): if not vars_value.startswith('@'):
return vars_value return vars_value
if not HAS_YAML:
self.fail_json(msg=self.missing_required_lib('yaml'))
file_name = None file_name = None
file_content = None file_content = None
try: try:

View File

@@ -8,9 +8,9 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
ANSIBLE_METADATA = {'status': ['preview'], ANSIBLE_METADATA = {'metadata_version': '1.1',
'supported_by': 'community', 'status': ['preview'],
'metadata_version': '1.1'} 'supported_by': 'community'}
DOCUMENTATION = ''' DOCUMENTATION = '''
@@ -20,7 +20,7 @@ author: "Adrien Fleury (@fleu42)"
version_added: "2.7" version_added: "2.7"
short_description: create, update, or destroy Ansible Tower inventory source. short_description: create, update, or destroy Ansible Tower inventory source.
description: description:
- Create, update, or destroy Ansible Tower inventories source. See - Create, update, or destroy Ansible Tower inventory source. See
U(https://www.ansible.com/tower) for an overview. U(https://www.ansible.com/tower) for an overview.
options: options:
name: name:
@@ -28,325 +28,338 @@ options:
- The name to use for the inventory source. - The name to use for the inventory source.
required: True required: True
type: str type: str
new_name:
description:
- A new name for this assets (will rename the asset)
required: False
type: str
description: description:
description: description:
- The description to use for the inventory source. - The description to use for the inventory source.
type: str type: str
inventory: inventory:
description: description:
- The inventory the source is linked to. - Inventory the group should be made a member of.
required: True required: True
type: str type: str
organization:
description:
- Organization the inventory belongs to.
type: str
source: source:
description: description:
- Types of inventory source. - The source to use for this group.
choices: choices: [ "manual", "file", "scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "cloudforms", "openstack", "rhv", "tower", "custom" ]
- file
- scm
- ec2
- gce
- azure
- azure_rm
- vmware
- satellite6
- cloudforms
- openstack
- rhv
- tower
- custom
required: True
type: str type: str
credential: required: False
source_path:
description: description:
- Credential to use to retrieve the inventory from. - For an SCM based inventory source, the source path points to the file within the repo to use as an inventory.
type: str
source_script:
description:
- Inventory script to be used when group type is C(custom).
type: str type: str
source_vars: source_vars:
description: description:
- >- - The variables or environment fields to apply to this source type.
The source_vars allow to Override variables found in the source config type: dict
file. For example with Openstack, specifying *private: false* would credential:
change the output of the openstack.py script. It has to be YAML or description:
JSON. - Credential to use for the source.
type: str type: str
source_regions:
description:
- Regions for cloud provider.
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
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
custom_virtualenv: custom_virtualenv:
version_added: "2.9" version_added: "2.9"
description: description:
- Local absolute file path containing a custom Python virtualenv to use. - Local absolute file path containing a custom Python virtualenv to use.
type: str type: str
required: False required: False
default: ''
timeout: timeout:
description: The amount of time (in seconds) to run before the task is canceled.
type: int
verbosity:
description: The verbosity level to run this inventory source under.
type: int
choices: [ 0, 1, 2 ]
update_on_launch:
description: description:
- Number in seconds after which the Tower API methods will time out. - Refresh inventory data from its source each time a job is run.
type: bool
default: 'no'
update_cache_timeout:
description:
- Time in seconds to consider an inventory sync to be current.
type: int type: int
source_project: source_project:
description: description:
- Use a *project* as a source for the *inventory*. - Project to use as source with scm option
type: str
source_path:
description:
- Path to the file to use as a source in the selected *project*.
type: str type: str
update_on_project_update: update_on_project_update:
description: description: Update this source when the related project updates if source is C(scm)
- >-
That parameter will sync the inventory when the project is synced. It
can only be used with a SCM source.
type: bool type: bool
source_regions:
description:
- >-
List of regions for your cloud provider. You can include multiple all
regions. Only Hosts associated with the selected regions will be
updated. Refer to Ansible Tower documentation for more detail.
type: str
instance_filters:
description:
- >-
Provide a comma-separated list of filter expressions. Hosts are
imported when all of the filters match. Refer to Ansible Tower
documentation for more detail.
type: str
group_by:
description:
- >-
Specify which groups to create automatically. Group names will be
created similar to the options selected. If blank, all groups above
are created. Refer to Ansible Tower documentation for more detail.
type: str
source_script:
description:
- >-
The source custom script to use to build the inventory. It needs to
exist.
type: str
overwrite:
description:
- >-
If set, any hosts and groups that were previously present on the
external source but are now removed will be removed from the Tower
inventory. Hosts and groups that were not managed by the inventory
source will be promoted to the next manually created group or if
there is no manually created group to promote them into, they will be
left in the "all" default group for the inventory. When not checked,
local child hosts and groups not found on the external source will
remain untouched by the inventory update process.
type: bool
overwrite_vars:
description:
- >-
If set, all variables for child groups and hosts will be removed
and replaced by those found on the external source. When not checked,
a merge will be performed, combining local variables with those found
on the external source.
type: bool
update_on_launch:
description:
- >-
Each time a job runs using this inventory, refresh the inventory from
the selected source before executing job tasks.
type: bool
update_cache_timeout:
description:
- >-
Time in seconds to consider an inventory sync to be current. During
job runs and callbacks the task system will evaluate the timestamp of
the latest sync. If it is older than Cache Timeout, it is not
considered current, and a new inventory sync will be performed.
type: int
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
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: Add tower inventory source - name: Add tower group
tower_inventory_source: tower_group:
name: Inventory source name: localhost
description: My Inventory source description: "Local Host Group"
inventory: My inventory inventory: "Local Inventory"
organization: My organization
credential: Devstack_credential
source: openstack
update_on_launch: true
overwrite: true
source_vars: '{ private: false }'
state: present state: present
validate_certs: false tower_config_file: "~/tower_cli.cfg"
''' '''
from ..module_utils.tower_api import TowerModule
RETURN = ''' # ''' from json import dumps
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
SOURCE_CHOICES = {
'file': 'Directory or Script',
'scm': 'Sourced from a Project',
'ec2': 'Amazon EC2',
'gce': 'Google Compute Engine',
'azure': 'Microsoft Azure',
'azure_rm': 'Microsoft Azure Resource Manager',
'vmware': 'VMware vCenter',
'satellite6': 'Red Hat Satellite 6',
'cloudforms': 'Red Hat CloudForms',
'openstack': 'OpenStack',
'rhv': 'Red Hat Virtualization',
'tower': 'Ansible Tower',
'custom': 'Custom Script',
}
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),
description=dict(required=False), new_name=dict(type='str'),
description=dict(),
inventory=dict(required=True), inventory=dict(required=True),
source=dict(required=True, #
choices=SOURCE_CHOICES.keys()), # How do we handle manual and file? Tower does not seem to be able to activate them
credential=dict(required=False), #
source_vars=dict(required=False), source=dict(choices=["manual", "file", "scm", "ec2", "gce",
timeout=dict(type='int', required=False), "azure_rm", "vmware", "satellite6", "cloudforms",
source_project=dict(required=False), "openstack", "rhv", "tower", "custom"], required=False),
source_path=dict(required=False), source_path=dict(),
update_on_project_update=dict(type='bool', required=False), source_script=dict(),
source_regions=dict(required=False), source_vars=dict(type='dict'),
instance_filters=dict(required=False), credential=dict(),
group_by=dict(required=False), source_regions=dict(),
source_script=dict(required=False), instance_filters=dict(),
overwrite=dict(type='bool', required=False), group_by=dict(),
overwrite_vars=dict(type='bool', required=False), overwrite=dict(type='bool'),
custom_virtualenv=dict(type='str', required=False), overwrite_vars=dict(type='bool'),
update_on_launch=dict(type='bool', required=False), custom_virtualenv=dict(type='str'),
update_cache_timeout=dict(type='int', required=False), timeout=dict(type='int'),
organization=dict(type='str'), verbosity=dict(type='int', choices=[0, 1, 2]),
update_on_launch=dict(type='bool'),
update_cache_timeout=dict(type='int'),
source_project=dict(type='str'),
update_on_project_update=dict(type='bool'),
state=dict(choices=['present', 'absent'], default='present'), state=dict(choices=['present', 'absent'], default='present'),
) )
module = TowerModule(argument_spec=argument_spec, supports_check_mode=True) # One question here is do we want to end up supporting this within the ansible module itself (i.e. required if, etc)
# Or do we want to let the API return issues with "this dosen't support that", etc.
#
# GUI OPTIONS:
# - - - - - - - manual: file: scm: ec2: gce azure_rm vmware sat cloudforms openstack rhv tower custom
# credential ? ? o o r r r r r r r r o
# source_project ? ? r - - - - - - - - - -
# source_path ? ? r - - - - - - - - - -
# verbosity ? ? o o o o o o o o o o o
# overwrite ? ? o o o o o o o o o o o
# overwrite_vars ? ? o o o o o o o o o o o
# update_on_launch ? ? o o o o o o o o o o o
# update_on_project_launch ? ? o - - - - - - - - - -
# source_regions ? ? - o o o - - - - - - -
# instance_filters ? ? - o - - o - - - - o -
# group_by ? ? - o - - o - - - - - -
# source_vars* ? ? - o - o o o o o - - -
# environmet vars* ? ? o - - - - - - - - - o
# source_script ? ? - - - - - - - - - - r
#
# * - source_vars are labeled environment_vars on project and custom sources
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
# We don't want to require source if state is present because
# you might be doing an update to an existing source.
# Later on in the code, we will do a test so that if state: present
# and if we don't have an object, we must have source.
('source', 'scm', ['source_project', 'source_path']),
('source', 'gce', ['credential']),
('source', 'azure_rm', ['credential']),
('source', 'vmware', ['credential']),
('source', 'satellite6', ['credential']),
('source', 'cloudforms', ['credential']),
('source', 'openstack', ['credential']),
('source', 'rhv', ['credential']),
('source', 'tower', ['credential']),
('source', 'custom', ['source_script']),
],
# This is provided by our module, it's not a core thing
mutually_exclusive_if=[
('source', 'scm', ['source_regions',
'instance_filters',
'group_by',
'source_script'
]),
('source', 'ec2', ['source_project',
'source_path',
'update_on_project_launch',
'source_script'
]),
('source', 'gce', ['source_project',
'source_path',
'update_on_project_launch',
'instance_filters',
'group_by',
'source_vars',
'source_script'
]),
('source', 'azure_rm', ['source_project',
'source_path',
'update_on_project_launch',
'instance_filters',
'group_by',
'source_script'
]),
('source', 'vmware', ['source_project', 'source_path', 'update_on_project_launch', 'source_regions', 'source_script']),
('source', 'satellite6', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'instance_filters',
'group_by',
'source_script'
]),
('source', 'cloudforms', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'instance_filters',
'group_by',
'source_script'
]),
('source', 'openstack', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'instance_filters',
'group_by',
'source_script'
]),
('source', 'rhv', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'instance_filters',
'group_by',
'source_vars',
'source_script'
]),
('source', 'tower', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'group_by',
'source_vars',
'source_script'
]),
('source', 'custom', ['source_project',
'source_path',
'update_on_project_launch',
'source_regions',
'instance_filters',
'group_by'
]),
])
optional_vars = {}
# Extract our parameters
name = module.params.get('name') name = module.params.get('name')
new_name = module.params.get('new_name')
optional_vars['description'] = module.params.get('description')
inventory = module.params.get('inventory') inventory = module.params.get('inventory')
source = module.params.get('source') optional_vars['source'] = module.params.get('source')
optional_vars['source_path'] = module.params.get('source_path')
source_script = module.params.get('source_script')
optional_vars['source_vars'] = module.params.get('source_vars')
credential = module.params.get('credential')
optional_vars['source_regions'] = module.params.get('source_regions')
optional_vars['instance_filters'] = module.params.get('instance_filters')
optional_vars['group_by'] = module.params.get('group_by')
optional_vars['overwrite'] = module.params.get('overwrite')
optional_vars['overwrite_vars'] = module.params.get('overwrite_vars')
optional_vars['custom_virtualenv'] = module.params.get('custom_virtualenv')
optional_vars['timeout'] = module.params.get('timeout')
optional_vars['verbosity'] = module.params.get('verbosity')
optional_vars['update_on_launch'] = module.params.get('update_on_launch')
optional_vars['update_cache_timeout'] = module.params.get('update_cache_timeout')
source_project = module.params.get('source_project')
optional_vars['update_on_project_update'] = module.params.get('update_on_project_update')
state = module.params.get('state') state = module.params.get('state')
organization = module.params.get('organization')
json_output = {'inventory_source': name, 'state': state} # Attempt to JSON encode source vars
if optional_vars['source_vars']:
optional_vars['source_vars'] = dumps(optional_vars['source_vars'])
tower_auth = tower_auth_config(module) # Attempt to lookup the related items the user specified (these will fail the module if not found)
with settings.runtime_values(**tower_auth): inventory_id = module.resolve_name_to_id('inventories', inventory)
tower_check_mode(module) if credential:
inventory_source = tower_cli.get_resource('inventory_source') optional_vars['credential'] = module.resolve_name_to_id('credentials', credential)
try: if source_project:
params = {} optional_vars['source_project'] = module.resolve_name_to_id('projects', source_project)
params['name'] = name if source_script:
params['source'] = source optional_vars['source_script'] = module.resolve_name_to_id('inventory_scripts', source_script)
if module.params.get('description'): # Attempt to lookup team based on the provided name and org ID
params['description'] = module.params.get('description') inventory_source = module.get_one('inventory_sources', **{
'data': {
'name': name,
'inventory': inventory_id,
}
})
if organization: # Sanity check on arguments
try: if state == 'present' and not inventory_source and not optional_vars['source']:
org_res = tower_cli.get_resource('organization') module.fail_json(msg="If creating a new inventory source, the source param must be present")
org = org_res.get(name=organization)
except (exc.NotFound) as excinfo:
module.fail_json(
msg='Failed to get organization,'
'organization not found: {0}'.format(excinfo),
changed=False
)
org_id = org['id']
else:
org_id = None # interpreted as not provided
if module.params.get('credential'): # Create data to sent to create and update
credential_res = tower_cli.get_resource('credential') inventory_source_fields = {
try: 'name': new_name if new_name else name,
credential = credential_res.get( 'inventory': inventory_id,
name=module.params.get('credential'), organization=org_id) }
params['credential'] = credential['id'] # Layer in all remaining optional information
except (exc.NotFound) as excinfo: for field_name in optional_vars:
module.fail_json( if optional_vars[field_name]:
msg='Failed to update credential source,' inventory_source_fields[field_name] = optional_vars[field_name]
'credential not found: {0}'.format(excinfo),
changed=False
)
if module.params.get('source_project'): if state == 'absent':
source_project_res = tower_cli.get_resource('project') # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
try: module.delete_if_needed(inventory_source)
source_project = source_project_res.get( elif state == 'present':
name=module.params.get('source_project'), organization=org_id) # If the state was present we can let the module build or update the existing inventory_source, this will return on its own
params['source_project'] = source_project['id'] module.create_or_update_if_needed(inventory_source, inventory_source_fields, endpoint='inventory_sources', item_type='inventory source')
except (exc.NotFound) as excinfo:
module.fail_json(
msg='Failed to update source project,'
'project not found: {0}'.format(excinfo),
changed=False
)
if module.params.get('source_script'):
source_script_res = tower_cli.get_resource('inventory_script')
try:
script = source_script_res.get(
name=module.params.get('source_script'), organization=org_id)
params['source_script'] = script['id']
except (exc.NotFound) as excinfo:
module.fail_json(
msg='Failed to update source script,'
'script not found: {0}'.format(excinfo),
changed=False
)
try:
inventory_res = tower_cli.get_resource('inventory')
params['inventory'] = inventory_res.get(name=inventory, organization=org_id)['id']
except (exc.NotFound) as excinfo:
module.fail_json(
msg='Failed to update inventory source, '
'inventory not found: {0}'.format(excinfo),
changed=False
)
for key in ('source_vars', 'custom_virtualenv', 'timeout', 'source_path',
'update_on_project_update', 'source_regions',
'instance_filters', 'group_by', 'overwrite',
'overwrite_vars', 'update_on_launch',
'update_cache_timeout'):
if module.params.get(key) is not None:
params[key] = module.params.get(key)
if state == 'present':
params['create_on_missing'] = True
result = inventory_source.modify(**params)
json_output['id'] = result['id']
elif state == 'absent':
params['fail_on_missing'] = False
result = inventory_source.delete(**params)
except (exc.ConnectionError, exc.BadRequest, exc.AuthError) as excinfo:
module.fail_json(msg='Failed to update inventory source: \
{0}'.format(excinfo), changed=False)
json_output['changed'] = result['changed']
module.exit_json(**json_output)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -21,11 +21,13 @@ def base_inventory():
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_source_create(run_module, admin_user, base_inventory): def test_inventory_source_create(run_module, admin_user, base_inventory):
source_path = '/var/lib/awx/example_source_path/'
result = run_module('tower_inventory_source', dict( result = run_module('tower_inventory_source', dict(
name='foo', name='foo',
inventory='test-inv', inventory='test-inv',
state='present', state='present',
source='scm', source='scm',
source_path=source_path,
source_project='test-proj' source_project='test-proj'
), admin_user) ), admin_user)
assert result.pop('changed', None), result assert result.pop('changed', None), result
@@ -35,8 +37,9 @@ def test_inventory_source_create(run_module, admin_user, base_inventory):
result.pop('invocation') result.pop('invocation')
assert result == { assert result == {
'id': inv_src.id, 'id': inv_src.id,
'inventory_source': 'foo', 'name': 'foo',
'state': 'present' 'state': 'present',
'credential_type': 'Nexus'
} }
@@ -58,7 +61,8 @@ def test_create_inventory_source_implied_org(run_module, admin_user):
result.pop('invocation') result.pop('invocation')
assert result == { assert result == {
"inventory_source": "Test Inventory Source", "credential_type": "Nexus",
"name": "Test Inventory Source",
"state": "present", "state": "present",
"id": inv_src.id, "id": inv_src.id,
} }
@@ -67,27 +71,27 @@ def test_create_inventory_source_implied_org(run_module, admin_user):
@pytest.mark.django_db @pytest.mark.django_db
def test_create_inventory_source_multiple_orgs(run_module, admin_user): def test_create_inventory_source_multiple_orgs(run_module, admin_user):
org = Organization.objects.create(name='test-org') org = Organization.objects.create(name='test-org')
inv = Inventory.objects.create(name='test-inv', organization=org) Inventory.objects.create(name='test-inv', organization=org)
# make another inventory by same name in another org # make another inventory by same name in another org
org2 = Organization.objects.create(name='test-org-number-two') org2 = Organization.objects.create(name='test-org-number-two')
Inventory.objects.create(name='test-inv', organization=org2) inv2 = Inventory.objects.create(name='test-inv', organization=org2)
result = run_module('tower_inventory_source', dict( result = run_module('tower_inventory_source', dict(
name='Test Inventory Source', name='Test Inventory Source',
inventory='test-inv', inventory=inv2.id,
source='ec2', source='ec2',
organization='test-org',
state='present' state='present'
), admin_user) ), admin_user)
assert result.pop('changed', None), result assert result.pop('changed', None), result
inv_src = InventorySource.objects.get(name='Test Inventory Source') inv_src = InventorySource.objects.get(name='Test Inventory Source')
assert inv_src.inventory == inv assert inv_src.inventory == inv2
result.pop('invocation') result.pop('invocation')
assert result == { assert result == {
"inventory_source": "Test Inventory Source", "credential_type": "Nexus",
"name": "Test Inventory Source",
"state": "present", "state": "present",
"id": inv_src.id, "id": inv_src.id,
} }
@@ -96,6 +100,7 @@ def test_create_inventory_source_multiple_orgs(run_module, admin_user):
@pytest.mark.django_db @pytest.mark.django_db
def test_create_inventory_source_with_venv(run_module, admin_user, base_inventory, mocker): def test_create_inventory_source_with_venv(run_module, admin_user, base_inventory, mocker):
path = '/var/lib/awx/venv/custom-venv/foobar13489435/' path = '/var/lib/awx/venv/custom-venv/foobar13489435/'
source_path = '/var/lib/awx/example_source_path/'
with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]): with mocker.patch('awx.main.models.mixins.get_custom_venv_choices', return_value=[path]):
result = run_module('tower_inventory_source', dict( result = run_module('tower_inventory_source', dict(
name='foo', name='foo',
@@ -103,7 +108,8 @@ def test_create_inventory_source_with_venv(run_module, admin_user, base_inventor
state='present', state='present',
source='scm', source='scm',
source_project='test-proj', source_project='test-proj',
custom_virtualenv=path custom_virtualenv=path,
source_path=source_path
), admin_user) ), admin_user)
assert result.pop('changed'), result assert result.pop('changed'), result
@@ -121,6 +127,7 @@ def test_custom_venv_no_op(run_module, admin_user, base_inventory, mocker):
This enforces assumptions about the behavior of the AnsibleModule This enforces assumptions about the behavior of the AnsibleModule
default argument_spec behavior. default argument_spec behavior.
""" """
source_path = '/var/lib/awx/example_source_path/'
inv_src = InventorySource.objects.create( inv_src = InventorySource.objects.create(
name='foo', name='foo',
inventory=base_inventory, inventory=base_inventory,
@@ -135,7 +142,9 @@ def test_custom_venv_no_op(run_module, admin_user, base_inventory, mocker):
description='this is the changed description', description='this is the changed description',
inventory='test-inv', inventory='test-inv',
source='scm', # is required, but behavior is arguable source='scm', # is required, but behavior is arguable
state='present' state='present',
source_project='test-proj',
source_path=source_path
), admin_user) ), admin_user)
assert result.pop('changed', None), result assert result.pop('changed', None), result
inv_src.refresh_from_db() inv_src.refresh_from_db()