Merge pull request #5 from ansible/devel

Rebase from devel
This commit is contained in:
Sean Sullivan
2020-09-01 14:42:23 -05:00
committed by GitHub
370 changed files with 11094 additions and 5576 deletions

View File

@@ -72,7 +72,7 @@ from ansible.errors import AnsibleParserError, AnsibleOptionsError
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.config.manager import ensure_type
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def handle_error(**kwargs):
@@ -104,12 +104,12 @@ class InventoryModule(BaseInventoryPlugin):
# Defer processing of params to logic shared with the modules
module_params = {}
for plugin_param, module_param in TowerModule.short_params.items():
for plugin_param, module_param in TowerAPIModule.short_params.items():
opt_val = self.get_option(plugin_param)
if opt_val is not None:
module_params[module_param] = opt_val
module = TowerModule(
module = TowerAPIModule(
argument_spec={}, direct_params=module_params,
error_callback=handle_error, warn_callback=self.warn_callback
)

View File

@@ -115,7 +115,7 @@ from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.utils.display import Display
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
class LookupModule(LookupBase):
@@ -133,13 +133,13 @@ class LookupModule(LookupBase):
# Defer processing of params to logic shared with the modules
module_params = {}
for plugin_param, module_param in TowerModule.short_params.items():
for plugin_param, module_param in TowerAPIModule.short_params.items():
opt_val = self.get_option(plugin_param)
if opt_val is not None:
module_params[module_param] = opt_val
# Create our module
module = TowerModule(
module = TowerAPIModule(
argument_spec={}, direct_params=module_params,
error_callback=self.handle_error, warn_callback=self.warn_callback
)

View File

@@ -1,37 +1,19 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule, env_fallback
from . tower_module import TowerModule
from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError
from ansible.module_utils.six import PY2, string_types
from ansible.module_utils.six.moves import StringIO
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
from ansible.module_utils.six import PY2
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.six.moves.http_cookiejar import CookieJar
from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError
from socket import gethostbyname
import time
import re
from json import loads, dumps
from os.path import isfile, expanduser, split, join, exists, isdir
from os import access, R_OK, getcwd
from distutils.util import strtobool
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
class ConfigFileException(Exception):
pass
class ItemNotDefined(Exception):
pass
class TowerModule(AnsibleModule):
class TowerAPIModule(TowerModule):
# TODO: Move the collection version check into tower_module.py
# This gets set by the make process so whatever is in here is irrelevant
_COLLECTION_VERSION = "0.0.1-devel"
_COLLECTION_TYPE = "awx"
@@ -41,197 +23,16 @@ class TowerModule(AnsibleModule):
'awx': 'AWX',
'tower': 'Red Hat Ansible Tower',
}
url = None
AUTH_ARGSPEC = dict(
tower_host=dict(required=False, fallback=(env_fallback, ['TOWER_HOST'])),
tower_username=dict(required=False, fallback=(env_fallback, ['TOWER_USERNAME'])),
tower_password=dict(no_log=True, required=False, fallback=(env_fallback, ['TOWER_PASSWORD'])),
validate_certs=dict(type='bool', aliases=['tower_verify_ssl'], required=False, fallback=(env_fallback, ['TOWER_VERIFY_SSL'])),
tower_oauthtoken=dict(type='raw', no_log=True, required=False, fallback=(env_fallback, ['TOWER_OAUTH_TOKEN'])),
tower_config_file=dict(type='path', required=False, default=None),
)
short_params = {
'host': 'tower_host',
'username': 'tower_username',
'password': 'tower_password',
'verify_ssl': 'validate_certs',
'oauth_token': 'tower_oauthtoken',
}
host = '127.0.0.1'
username = None
password = None
verify_ssl = True
oauth_token = None
oauth_token_id = None
session = None
cookie_jar = CookieJar()
authenticated = False
config_name = 'tower_cli.cfg'
ENCRYPTED_STRING = "$encrypted$"
version_checked = False
error_callback = None
warn_callback = None
def __init__(self, argument_spec, direct_params=None, error_callback=None, warn_callback=None, **kwargs):
full_argspec = {}
full_argspec.update(TowerModule.AUTH_ARGSPEC)
full_argspec.update(argument_spec)
kwargs['supports_check_mode'] = True
self.error_callback = error_callback
self.warn_callback = warn_callback
self.json_output = {'changed': False}
if direct_params is not None:
self.params = direct_params
else:
super(TowerModule, self).__init__(argument_spec=full_argspec, **kwargs)
self.load_config_files()
# Parameters specified on command line will override settings in any config
for short_param, long_param in self.short_params.items():
direct_value = self.params.get(long_param)
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'):
token_param = self.params.get('tower_oauthtoken')
if type(token_param) is dict:
if 'token' in token_param:
self.oauth_token = self.params.get('tower_oauthtoken')['token']
else:
self.fail_json(msg="The provided dict in tower_oauthtoken did not properly contain the token entry")
elif isinstance(token_param, string_types):
self.oauth_token = self.params.get('tower_oauthtoken')
else:
error_msg = "The provided tower_oauthtoken type was not valid ({0}). Valid options are str or dict.".format(type(token_param).__name__)
self.fail_json(msg=error_msg)
# Perform some basic validation
if not re.match('^https{0,1}://', self.host):
self.host = "https://{0}".format(self.host)
# Try to parse the hostname as a url
try:
self.url = urlparse(self.host)
except Exception as e:
self.fail_json(msg="Unable to parse tower_host as a URL ({1}): {0}".format(self.host, e))
# Try to resolve the hostname
hostname = self.url.netloc.split(':')[0]
try:
gethostbyname(hostname)
except Exception as e:
self.fail_json(msg="Unable to resolve tower_host ({1}): {0}".format(hostname, e))
super(TowerAPIModule, self).__init__(argument_spec=argument_spec, direct_params=direct_params,
error_callback=error_callback, warn_callback=warn_callback, **kwargs)
self.session = Request(cookies=CookieJar(), validate_certs=self.verify_ssl)
def load_config_files(self):
# Load configs like TowerCLI would have from least import to most
config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))]
local_dir = getcwd()
config_files.append(join(local_dir, self.config_name))
while split(local_dir)[1]:
local_dir = split(local_dir)[0]
config_files.insert(2, join(local_dir, ".{0}".format(self.config_name)))
# If we have a specified tower config, load it
if self.params.get('tower_config_file'):
duplicated_params = [
fn for fn in self.AUTH_ARGSPEC
if fn != 'tower_config_file' and self.params.get(fn) is not None
]
if duplicated_params:
self.warn((
'The parameter(s) {0} were provided at the same time as tower_config_file. '
'Precedence may be unstable, we suggest either using config file or params.'
).format(', '.join(duplicated_params)))
try:
# TODO: warn if there are conflicts with other params
self.load_config(self.params.get('tower_config_file'))
except ConfigFileException as cfe:
# Since we were told specifically to load this we want it to fail if we have an error
self.fail_json(msg=cfe)
else:
for config_file in config_files:
if exists(config_file) and not isdir(config_file):
# Only throw a formatting error if the file exists and is not a directory
try:
self.load_config(config_file)
except ConfigFileException:
self.fail_json(msg='The config file {0} is not properly formatted'.format(config_file))
def load_config(self, config_path):
# Validate the config file is an actual file
if not isfile(config_path):
raise ConfigFileException('The specified config file does not exist')
if not access(config_path, R_OK):
raise ConfigFileException("The specified config file cannot be read")
# Read in the file contents:
with open(config_path, 'r') as f:
config_string = f.read()
# First try to yaml load the content (which will also load json)
try:
try_config_parsing = True
if HAS_YAML:
try:
config_data = yaml.load(config_string, Loader=yaml.SafeLoader)
# If this is an actual ini file, yaml will return the whole thing as a string instead of a dict
if type(config_data) is not dict:
raise AssertionError("The yaml config file is not properly formatted as a dict.")
try_config_parsing = False
except(AttributeError, yaml.YAMLError, AssertionError):
try_config_parsing = True
if try_config_parsing:
# TowerCLI used to support a config file with a missing [general] section by prepending it if missing
if '[general]' not in config_string:
config_string = '[general]\n{0}'.format(config_string)
config = ConfigParser()
try:
placeholder_file = StringIO(config_string)
# py2 ConfigParser has readfp, that has been deprecated in favor of read_file in py3
# This "if" removes the deprecation warning
if hasattr(config, 'read_file'):
config.read_file(placeholder_file)
else:
config.readfp(placeholder_file)
# If we made it here then we have values from reading the ini file, so let's pull them out into a dict
config_data = {}
for honorred_setting in self.short_params:
try:
config_data[honorred_setting] = config.get('general', honorred_setting)
except NoOptionError:
pass
except Exception as e:
raise ConfigFileException("An unknown exception occured trying to ini load config file: {0}".format(e))
except Exception as e:
raise ConfigFileException("An unknown exception occured trying to load config file: {0}".format(e))
# If we made it here, we have a dict which has values in it from our config, any final settings logic can be performed here
for honorred_setting in self.short_params:
if honorred_setting in config_data:
# Veriffy SSL must be a boolean
if honorred_setting == 'verify_ssl':
if type(config_data[honorred_setting]) is str:
setattr(self, honorred_setting, strtobool(config_data[honorred_setting]))
else:
setattr(self, honorred_setting, bool(config_data[honorred_setting]))
else:
setattr(self, honorred_setting, config_data[honorred_setting])
@staticmethod
def param_to_endpoint(name):
exceptions = {
@@ -305,32 +106,39 @@ class TowerModule(AnsibleModule):
return response['json']['results'][0]
def resolve_name_to_id(self, endpoint, name_or_id):
# Try to resolve the object by name
def get_one_by_name_or_id(self, endpoint, name_or_id):
name_field = 'name'
if endpoint == 'users':
name_field = 'username'
response = self.get_endpoint(endpoint, **{'data': {name_field: name_or_id}})
if response['status_code'] == 400:
self.fail_json(msg="Unable to try and resolve {0} for {1} : {2}".format(endpoint, name_or_id, response['json']['detail']))
query_params = {'or__{0}'.format(name_field): name_or_id}
try:
query_params['or__id'] = int(name_or_id)
except ValueError:
# If we get a value error, then we didn't have an integer so we can just pass and fall down to the fail
pass
response = self.get_endpoint(endpoint, **{'data': query_params})
if response['status_code'] != 200:
self.fail_json(
msg="Failed to query endpoint {0} for {1} {2} ({3}), see results".format(endpoint, name_field, name_or_id, response['status_code']),
resuls=response
)
if response['json']['count'] == 1:
return response['json']['results'][0]['id']
return response['json']['results'][0]
elif response['json']['count'] > 1:
for tower_object in response['json']['results']:
# ID takes priority, so we match on that first
if str(tower_object['id']) == name_or_id:
return tower_object
# We didn't match on an ID but we found more than 1 object, therefore the results are ambiguous
self.fail_json(msg="The requested name or id was ambiguous and resulted in too many items")
elif response['json']['count'] == 0:
try:
int(name_or_id)
# If we got 0 items by name, maybe they gave us an ID, let's try looking it up by ID
response = self.head_endpoint("{0}/{1}".format(endpoint, name_or_id), **{'return_none_on_404': True})
if response is not None:
return name_or_id
except ValueError:
# If we got a value error than we didn't have an integer so we can just pass and fall down to the fail
pass
self.fail_json(msg="The {0} {1} was not found on the Tower server".format(endpoint, name_or_id))
else:
self.fail_json(msg="Found too many names {0} at endpoint {1} try using an ID instead of a name".format(name_or_id, endpoint))
def resolve_name_to_id(self, endpoint, name_or_id):
return self.get_one_by_name_or_id(endpoint, name_or_id)['id']
def make_request(self, method, endpoint, *args, **kwargs):
# In case someone is calling us directly; make sure we were given a method, let's not just assume a GET
@@ -650,13 +458,13 @@ class TowerModule(AnsibleModule):
"""
if isinstance(obj, dict):
for val in obj.values():
if TowerModule.has_encrypted_values(val):
if TowerAPIModule.has_encrypted_values(val):
return True
elif isinstance(obj, list):
for val in obj:
if TowerModule.has_encrypted_values(val):
if TowerAPIModule.has_encrypted_values(val):
return True
elif obj == TowerModule.ENCRYPTED_STRING:
elif obj == TowerAPIModule.ENCRYPTED_STRING:
return True
return False
@@ -678,10 +486,9 @@ class TowerModule(AnsibleModule):
# This will exit from the module on its own
# If the method successfully updates an item and on_update param is defined,
# the on_update parameter will be called as a method pasing in this object and the json from the response
# This will return one of three things:
# This will return one of two things:
# 1. None if the existing_item does not need to be updated
# 2. The response from Tower from patching to the endpoint. It's up to you to process the response and exit from the module.
# 3. An ItemNotDefined exception, if the existing_item does not exist
# Note: common error codes from the Tower API can cause the module to fail
response = None
if existing_item:
@@ -745,7 +552,7 @@ class TowerModule(AnsibleModule):
return self.create_if_needed(existing_item, new_item, endpoint, on_create=on_create, item_type=item_type, associations=associations)
def logout(self):
if self.authenticated:
if self.authenticated and self.oauth_token_id:
# Attempt to delete our current token from /api/v2/tokens/
# Post to the tokens endpoint with baisc auth to try and get a token
api_token_url = (
@@ -777,27 +584,47 @@ class TowerModule(AnsibleModule):
# Sanity check: Did the server send back some kind of internal error?
self.warn('Failed to release tower token {0}: {1}'.format(self.oauth_token_id, e))
def fail_json(self, **kwargs):
# Try to log out if we are authenticated
self.logout()
if self.error_callback:
self.error_callback(**kwargs)
else:
super(TowerModule, self).fail_json(**kwargs)
def exit_json(self, **kwargs):
# Try to log out if we are authenticated
self.logout()
super(TowerModule, self).exit_json(**kwargs)
def warn(self, warning):
if self.warn_callback is not None:
self.warn_callback(warning)
else:
super(TowerModule, self).warn(warning)
def is_job_done(self, job_status):
if job_status in ['new', 'pending', 'waiting', 'running']:
return False
else:
return True
def wait_on_url(self, url, object_name, object_type, timeout=30, interval=10):
# Grab our start time to compare against for the timeout
start = time.time()
result = self.get_endpoint(url)
while not result['json']['finished']:
# If we are past our time out fail with a message
if timeout and timeout < time.time() - start:
# Account for Legacy messages
if object_type is 'legacy_job_wait':
self.json_output['msg'] = 'Monitoring of Job - {0} aborted due to timeout'.format(object_name)
else:
self.json_output['msg'] = 'Monitoring of {0} - {1} aborted due to timeout'.format(object_type, object_name)
self.wait_output(result)
self.fail_json(**self.json_output)
# Put the process to sleep for our interval
time.sleep(interval)
result = self.get_endpoint(url)
self.json_output['status'] = result['json']['status']
# If the job has failed, we want to raise a task failure for that so we get a non-zero response.
if result['json']['failed']:
# Account for Legacy messages
if object_type is 'legacy_job_wait':
self.json_output['msg'] = 'Job with id {0} failed'.format(object_name)
else:
self.json_output['msg'] = 'The {0} - {1}, failed'.format(object_type, object_name)
self.wait_output(result)
self.fail_json(**self.json_output)
self.wait_output(result)
return result
def wait_output(self, response):
for k in ('id', 'status', 'elapsed', 'started', 'finished'):
self.json_output[k] = response['json'].get(k)

View File

@@ -0,0 +1,53 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from . tower_module import TowerModule
from ansible.module_utils.basic import missing_required_lib
try:
from awxkit.api.client import Connection
from awxkit.api.pages.api import ApiV2
from awxkit.api import get_registered_page
HAS_AWX_KIT = True
except ImportError:
HAS_AWX_KIT = False
class TowerAWXKitModule(TowerModule):
connection = None
apiV2Ref = None
def __init__(self, argument_spec, **kwargs):
kwargs['supports_check_mode'] = False
super(TowerAWXKitModule, self).__init__(argument_spec=argument_spec, **kwargs)
# Die if we don't have AWX_KIT installed
if not HAS_AWX_KIT:
self.exit_json(msg=missing_required_lib('awxkit'))
# Establish our conneciton object
self.connection = Connection(self.host, verify=self.verify_ssl)
def authenticate(self):
try:
if self.oauth_token:
self.connection.login(None, None, token=self.oauth_token)
self.authenticated = True
elif self.username:
self.connection.login(username=self.username, password=self.password)
self.authenticated = True
except Exception:
self.exit_json("Failed to authenticate")
def get_api_v2_object(self):
if not self.apiV2Ref:
if not self.authenticated:
self.authenticate()
v2_index = get_registered_page('/api/v2/')(self.connection).get()
self.api_ref = ApiV2(connection=self.connection, **{'json': v2_index})
return self.api_ref
def logout(self):
if self.authenticated:
self.connection.logout()

View File

@@ -91,7 +91,7 @@ def tower_check_mode(module):
module.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
class TowerModule(AnsibleModule):
class TowerLegacyModule(AnsibleModule):
def __init__(self, argument_spec, **kwargs):
args = dict(
tower_host=dict(),
@@ -110,7 +110,7 @@ class TowerModule(AnsibleModule):
('tower_config_file', 'validate_certs'),
))
super(TowerModule, self).__init__(argument_spec=args, **kwargs)
super(TowerLegacyModule, self).__init__(argument_spec=args, **kwargs)
if not HAS_TOWER_CLI:
self.fail_json(msg=missing_required_lib('ansible-tower-cli'),

View File

@@ -0,0 +1,239 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.six import PY2, string_types
from ansible.module_utils.six.moves import StringIO
from ansible.module_utils.six.moves.urllib.parse import urlparse
from ansible.module_utils.six.moves.configparser import ConfigParser, NoOptionError
from socket import gethostbyname
import re
from os.path import isfile, expanduser, split, join, exists, isdir
from os import access, R_OK, getcwd
from distutils.util import strtobool
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
class ConfigFileException(Exception):
pass
class ItemNotDefined(Exception):
pass
class TowerModule(AnsibleModule):
url = None
AUTH_ARGSPEC = dict(
tower_host=dict(required=False, fallback=(env_fallback, ['TOWER_HOST'])),
tower_username=dict(required=False, fallback=(env_fallback, ['TOWER_USERNAME'])),
tower_password=dict(no_log=True, required=False, fallback=(env_fallback, ['TOWER_PASSWORD'])),
validate_certs=dict(type='bool', aliases=['tower_verify_ssl'], required=False, fallback=(env_fallback, ['TOWER_VERIFY_SSL'])),
tower_oauthtoken=dict(type='raw', no_log=True, required=False, fallback=(env_fallback, ['TOWER_OAUTH_TOKEN'])),
tower_config_file=dict(type='path', required=False, default=None),
)
short_params = {
'host': 'tower_host',
'username': 'tower_username',
'password': 'tower_password',
'verify_ssl': 'validate_certs',
'oauth_token': 'tower_oauthtoken',
}
host = '127.0.0.1'
username = None
password = None
verify_ssl = True
oauth_token = None
oauth_token_id = None
authenticated = False
config_name = 'tower_cli.cfg'
ENCRYPTED_STRING = "$encrypted$"
version_checked = False
error_callback = None
warn_callback = None
def __init__(self, argument_spec=None, direct_params=None, error_callback=None, warn_callback=None, **kwargs):
full_argspec = {}
full_argspec.update(TowerModule.AUTH_ARGSPEC)
full_argspec.update(argument_spec)
kwargs['supports_check_mode'] = True
self.error_callback = error_callback
self.warn_callback = warn_callback
self.json_output = {'changed': False}
if direct_params is not None:
self.params = direct_params
else:
super(TowerModule, self).__init__(argument_spec=full_argspec, **kwargs)
self.load_config_files()
# Parameters specified on command line will override settings in any config
for short_param, long_param in self.short_params.items():
direct_value = self.params.get(long_param)
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'):
token_param = self.params.get('tower_oauthtoken')
if type(token_param) is dict:
if 'token' in token_param:
self.oauth_token = self.params.get('tower_oauthtoken')['token']
else:
self.fail_json(msg="The provided dict in tower_oauthtoken did not properly contain the token entry")
elif isinstance(token_param, string_types):
self.oauth_token = self.params.get('tower_oauthtoken')
else:
error_msg = "The provided tower_oauthtoken type was not valid ({0}). Valid options are str or dict.".format(type(token_param).__name__)
self.fail_json(msg=error_msg)
# Perform some basic validation
if not re.match('^https{0,1}://', self.host):
self.host = "https://{0}".format(self.host)
# Try to parse the hostname as a url
try:
self.url = urlparse(self.host)
except Exception as e:
self.fail_json(msg="Unable to parse tower_host as a URL ({1}): {0}".format(self.host, e))
# Try to resolve the hostname
hostname = self.url.netloc.split(':')[0]
try:
gethostbyname(hostname)
except Exception as e:
self.fail_json(msg="Unable to resolve tower_host ({1}): {0}".format(hostname, e))
def load_config_files(self):
# Load configs like TowerCLI would have from least import to most
config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))]
local_dir = getcwd()
config_files.append(join(local_dir, self.config_name))
while split(local_dir)[1]:
local_dir = split(local_dir)[0]
config_files.insert(2, join(local_dir, ".{0}".format(self.config_name)))
# If we have a specified tower config, load it
if self.params.get('tower_config_file'):
duplicated_params = [
fn for fn in self.AUTH_ARGSPEC
if fn != 'tower_config_file' and self.params.get(fn) is not None
]
if duplicated_params:
self.warn((
'The parameter(s) {0} were provided at the same time as tower_config_file. '
'Precedence may be unstable, we suggest either using config file or params.'
).format(', '.join(duplicated_params)))
try:
# TODO: warn if there are conflicts with other params
self.load_config(self.params.get('tower_config_file'))
except ConfigFileException as cfe:
# Since we were told specifically to load this we want it to fail if we have an error
self.fail_json(msg=cfe)
else:
for config_file in config_files:
if exists(config_file) and not isdir(config_file):
# Only throw a formatting error if the file exists and is not a directory
try:
self.load_config(config_file)
except ConfigFileException:
self.fail_json(msg='The config file {0} is not properly formatted'.format(config_file))
def load_config(self, config_path):
# Validate the config file is an actual file
if not isfile(config_path):
raise ConfigFileException('The specified config file does not exist')
if not access(config_path, R_OK):
raise ConfigFileException("The specified config file cannot be read")
# Read in the file contents:
with open(config_path, 'r') as f:
config_string = f.read()
# First try to yaml load the content (which will also load json)
try:
try_config_parsing = True
if HAS_YAML:
try:
config_data = yaml.load(config_string, Loader=yaml.SafeLoader)
# If this is an actual ini file, yaml will return the whole thing as a string instead of a dict
if type(config_data) is not dict:
raise AssertionError("The yaml config file is not properly formatted as a dict.")
try_config_parsing = False
except(AttributeError, yaml.YAMLError, AssertionError):
try_config_parsing = True
if try_config_parsing:
# TowerCLI used to support a config file with a missing [general] section by prepending it if missing
if '[general]' not in config_string:
config_string = '[general]\n{0}'.format(config_string)
config = ConfigParser()
try:
placeholder_file = StringIO(config_string)
# py2 ConfigParser has readfp, that has been deprecated in favor of read_file in py3
# This "if" removes the deprecation warning
if hasattr(config, 'read_file'):
config.read_file(placeholder_file)
else:
config.readfp(placeholder_file)
# If we made it here then we have values from reading the ini file, so let's pull them out into a dict
config_data = {}
for honorred_setting in self.short_params:
try:
config_data[honorred_setting] = config.get('general', honorred_setting)
except NoOptionError:
pass
except Exception as e:
raise ConfigFileException("An unknown exception occured trying to ini load config file: {0}".format(e))
except Exception as e:
raise ConfigFileException("An unknown exception occured trying to load config file: {0}".format(e))
# If we made it here, we have a dict which has values in it from our config, any final settings logic can be performed here
for honorred_setting in self.short_params:
if honorred_setting in config_data:
# Veriffy SSL must be a boolean
if honorred_setting == 'verify_ssl':
if type(config_data[honorred_setting]) is str:
setattr(self, honorred_setting, strtobool(config_data[honorred_setting]))
else:
setattr(self, honorred_setting, bool(config_data[honorred_setting]))
else:
setattr(self, honorred_setting, config_data[honorred_setting])
def logout(self):
# This method is intended to be overridden
pass
def fail_json(self, **kwargs):
# Try to log out if we are authenticated
self.logout()
if self.error_callback:
self.error_callback(**kwargs)
else:
super(TowerModule, self).fail_json(**kwargs)
def exit_json(self, **kwargs):
# Try to log out if we are authenticated
self.logout()
super(TowerModule, self).exit_json(**kwargs)
def warn(self, warning):
if self.warn_callback is not None:
self.warn_callback(warning)
else:
super(TowerModule, self).warn(warning)

View File

@@ -269,7 +269,7 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
KIND_CHOICES = {
'ssh': 'Machine',
@@ -336,7 +336,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec, required_one_of=[['kind', 'credential_type']])
module = TowerAPIModule(argument_spec=argument_spec, required_one_of=[['kind', 'credential_type']])
# Extract our parameters
name = module.params.get('name')

View File

@@ -70,7 +70,7 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -85,7 +85,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
description = module.params.get('description')

View File

@@ -81,7 +81,7 @@ EXAMPLES = '''
RETURN = ''' # '''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
KIND_CHOICES = {
'ssh': 'Machine',
@@ -105,7 +105,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -0,0 +1,166 @@
#!/usr/bin/python
# coding: utf-8 -*-
# (c) 2017, 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_export
author: "John Westcott IV (@john-westcott-iv)"
version_added: "3.7"
short_description: export resources from Ansible Tower.
description:
- Export assets from Ansible Tower.
options:
all:
description:
- Export all assets
type: bool
default: 'False'
organizations:
description:
- organization name to export
type: str
users:
description:
- user name to export
type: str
teams:
description:
- team name to export
type: str
credential_types:
description:
- credential type name to export
type: str
credentials:
description:
- credential name to export
type: str
notification_templates:
description:
- notification template name to export
type: str
inventory_sources:
description:
- inventory soruce to export
type: str
inventory:
description:
- inventory name to export
type: str
projects:
description:
- project name to export
type: str
job_templates:
description:
- job template name to export
type: str
workflow_job_templates:
description:
- workflow name to export
type: str
requirements:
- "awxkit >= 9.3.0"
notes:
- Specifying a name of "all" for any asset type will export all items of that asset type.
extends_documentation_fragment: awx.awx.auth
'''
EXAMPLES = '''
- name: Export all tower assets
tower_export:
all: True
- name: Export all inventories
tower_export:
inventory: 'all'
- name: Export a job template named "My Template" and all Credentials
tower_export:
job_template: "My Template"
credential: 'all'
'''
from os import environ
import logging
from ansible.module_utils.six.moves import StringIO
from ..module_utils.tower_awxkit import TowerAWXKitModule
try:
from awxkit.api.pages.api import EXPORTABLE_RESOURCES
HAS_EXPORTABLE_RESOURCES = True
except ImportError:
HAS_EXPORTABLE_RESOURCES = False
def main():
argument_spec = dict(
all=dict(type='bool', default=False),
)
# We are not going to raise an error here because the __init__ method of TowerAWXKitModule will do that for us
if HAS_EXPORTABLE_RESOURCES:
for resource in EXPORTABLE_RESOURCES:
argument_spec[resource] = dict(type='str')
module = TowerAWXKitModule(argument_spec=argument_spec)
if not HAS_EXPORTABLE_RESOURCES:
module.fail_json(msg="Your version of awxkit does not have import/export")
# The export process will never change a Tower system
module.json_output['changed'] = False
# The exporter code currently works like the following:
# Empty string == all assets of that type
# Non-Empty string = just one asset of that type (by name or ID)
# Asset type not present or None = skip asset type (unless everything is None, then export all)
# Here we are going to setup a dict of values to export
export_args = {}
for resource in EXPORTABLE_RESOURCES:
if module.params.get('all') or module.params.get(resource) == 'all':
# If we are exporting everything or we got the keyword "all" we pass in an empty string for this asset type
export_args[resource] = ''
else:
# Otherwise we take either the string or None (if the parameter was not passed) to get one or no items
export_args[resource] = module.params.get(resource)
# Currently the import process does not return anything on error
# It simply just logs to pythons logger
# Setup a log gobbler to get error messages from import_assets
log_capture_string = StringIO()
ch = logging.StreamHandler(log_capture_string)
for logger_name in ['awxkit.api.pages.api', 'awxkit.api.pages.page']:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING)
ch.setLevel(logging.WARNING)
logger.addHandler(ch)
log_contents = ''
# Run the import process
try:
module.json_output['assets'] = module.get_api_v2_object().export_assets(**export_args)
module.exit_json(**module.json_output)
except Exception as e:
module.fail_json(msg="Failed to export assets {0}".format(e))
finally:
# Finally consume the logs incase there were any errors and die if there were
log_contents = log_capture_string.getvalue()
log_capture_string.close()
if log_contents != '':
module.fail_json(msg=log_contents)
if __name__ == '__main__':
main()

View File

@@ -76,7 +76,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
@@ -94,7 +94,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -72,7 +72,7 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
@@ -89,7 +89,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -0,0 +1,105 @@
#!/usr/bin/python
# coding: utf-8 -*-
# (c) 2017, 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_import
author: "John Westcott (@john-westcott-iv)"
version_added: "3.7"
short_description: import resources into Ansible Tower.
description:
- Import assets into Ansible Tower. See
U(https://www.ansible.com/tower) for an overview.
options:
assets:
description:
- The assets to import.
- This can be the output of tower_export or loaded from a file
required: True
type: dict
requirements:
- "awxkit >= 9.3.0"
extends_documentation_fragment: awx.awx.auth
'''
EXAMPLES = '''
- name: Export all assets
tower_export:
all: True
registeR: export_output
- name: Import all tower assets from our export
tower_import:
assets: "{{ export_output.assets }}"
- name: Load data from a json file created by a command like awx export --organization Default
tower_import:
assets: "{{ lookup('file', 'org.json') | from_json() }}"
'''
from ..module_utils.tower_awxkit import TowerAWXKitModule
# These two lines are not needed if awxkit changes to do progamatic notifications on issues
from ansible.module_utils.six.moves import StringIO
import logging
# In this module we don't use EXPORTABLE_RESOURCES, we just want to validate that our installed awxkit has import/export
try:
from awxkit.api.pages.api import EXPORTABLE_RESOURCES
HAS_EXPORTABLE_RESOURCES = True
except ImportError:
HAS_EXPORTABLE_RESOURCES = False
def main():
argument_spec = dict(
assets=dict(type='dict', required=True)
)
module = TowerAWXKitModule(argument_spec=argument_spec, supports_check_mode=False)
assets = module.params.get('assets')
if not HAS_EXPORTABLE_RESOURCES:
module.fail_json(msg="Your version of awxkit does not appear to have import/export")
# Currently the import process does not return anything on error
# It simply just logs to pythons logger
# Setup a log gobbler to get error messages from import_assets
logger = logging.getLogger('awxkit.api.pages.api')
logger.setLevel(logging.WARNING)
log_capture_string = StringIO()
ch = logging.StreamHandler(log_capture_string)
ch.setLevel(logging.WARNING)
logger.addHandler(ch)
log_contents = ''
# Run the import process
try:
module.json_output['changed'] = module.get_api_v2_object().import_assets(assets)
except Exception as e:
module.fail_json(msg="Failed to import assets {0}".format(e))
finally:
# Finally consume the logs incase there were any errors and die if there were
log_contents = log_capture_string.getvalue()
log_capture_string.close()
if log_contents != '':
module.fail_json(msg=log_contents)
module.exit_json(**module.json_output)
if __name__ == '__main__':
main()

View File

@@ -48,7 +48,11 @@ options:
type: str
host_filter:
description:
- The host_filter field. Only useful when C(kind=smart).
- The host_filter field. Only useful when C(kind=smart).
type: str
insights_credential:
description:
- Credentials to be used by hosts belonging to this inventory when accessing Red Hat Insights API.
type: str
state:
description:
@@ -71,7 +75,7 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
@@ -84,11 +88,12 @@ def main():
variables=dict(type='dict'),
kind=dict(choices=['', 'smart'], default=''),
host_filter=dict(),
insights_credential=dict(),
state=dict(choices=['present', 'absent'], default='present'),
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')
@@ -98,6 +103,7 @@ def main():
state = module.params.get('state')
kind = module.params.get('kind')
host_filter = module.params.get('host_filter')
insights_credential = module.params.get('insights_credential')
# Attempt to look up the related items the user specified (these will fail the module if not found)
org_id = module.resolve_name_to_id('organizations', organization)
@@ -125,6 +131,8 @@ def main():
inventory_fields['description'] = description
if variables is not None:
inventory_fields['variables'] = json.dumps(variables)
if insights_credential is not None:
inventory_fields['insights_credential'] = module.resolve_name_to_id('credentials', insights_credential)
# We need to perform a check to make sure you are not trying to convert a regular inventory into a smart one.
if inventory and inventory['kind'] == '' and inventory_fields['kind'] == 'smart':

View File

@@ -57,22 +57,22 @@ options:
description:
- The variables or environment fields to apply to this source type.
type: dict
enabled_var:
description:
- The variable to use to determine enabled state e.g., "status.power_state"
type: str
enabled_value:
description:
- Value when the host is considered enabled, e.g., "powered_on"
type: str
host_filter:
description:
- If specified, AWX will only import hosts that match this regular expression.
type: str
credential:
description:
- Credential to use for the source.
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.
@@ -144,7 +144,7 @@ EXAMPLES = '''
private: false
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
from json import dumps
@@ -164,10 +164,10 @@ def main():
source_path=dict(),
source_script=dict(),
source_vars=dict(type='dict'),
enabled_var=dict(),
enabled_value=dict(),
host_filter=dict(),
credential=dict(),
source_regions=dict(),
instance_filters=dict(),
group_by=dict(),
overwrite=dict(type='bool'),
overwrite_vars=dict(type='bool'),
custom_virtualenv=dict(),
@@ -184,7 +184,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')
@@ -245,10 +245,9 @@ def main():
OPTIONAL_VARS = (
'description', 'source', 'source_path', 'source_vars',
'source_regions', 'instance_filters', 'group_by',
'overwrite', 'overwrite_vars', 'custom_virtualenv',
'timeout', 'verbosity', 'update_on_launch', 'update_cache_timeout',
'update_on_project_update'
'update_on_project_update', 'enabled_var', 'enabled_value', 'host_filter',
)
# Layer in all remaining optional information

View File

@@ -50,7 +50,7 @@ id:
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -61,7 +61,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
job_id = module.params.get('job_id')

View File

@@ -81,6 +81,22 @@ options:
description:
- Passwords for credentials which are set to prompt on launch
type: dict
wait:
description:
- Wait for the job to complete.
default: False
type: bool
interval:
description:
- The interval to request an update from Tower.
required: False
default: 1
type: float
timeout:
description:
- If waiting for the job to complete this will abort after this
amount of seconds
type: int
extends_documentation_fragment: awx.awx.auth
'''
@@ -124,7 +140,7 @@ status:
sample: pending
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -143,10 +159,13 @@ def main():
verbosity=dict(type='int', choices=[0, 1, 2, 3, 4, 5]),
diff_mode=dict(type='bool'),
credential_passwords=dict(type='dict'),
wait=dict(default=False, type='bool'),
interval=dict(default=1.0, type='float'),
timeout=dict(default=None, type='int'),
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
optional_args = {}
# Extract our parameters
@@ -162,6 +181,9 @@ def main():
optional_args['verbosity'] = module.params.get('verbosity')
optional_args['diff_mode'] = module.params.get('diff_mode')
optional_args['credential_passwords'] = module.params.get('credential_passwords')
wait = module.params.get('wait')
interval = module.params.get('interval')
timeout = module.params.get('timeout')
# Create a datastructure to pass into our job launch
post_data = {}
@@ -216,6 +238,21 @@ def main():
if results['status_code'] != 201:
module.fail_json(msg="Failed to launch job, see response for details", **{'response': results})
if not wait:
module.exit_json(**{
'changed': True,
'id': results['json']['id'],
'status': results['json']['status'],
})
# Invoke wait function
results = module.wait_on_url(
url=results['json']['url'],
object_name=name,
object_type='Job',
timeout=timeout, interval=interval
)
module.exit_json(**{
'changed': True,
'id': results['json']['id'],

View File

@@ -80,7 +80,7 @@ results:
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -93,7 +93,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(
module = TowerAPIModule(
argument_spec=argument_spec,
mutually_exclusive=[
('page', 'all_pages'),

View File

@@ -317,7 +317,7 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
@@ -388,7 +388,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -55,7 +55,7 @@ EXAMPLES = '''
- name: Launch a job
tower_job_launch:
job_template: "My Job Template"
register: job
register: job
- name: Wait for job max 120s
tower_job_wait:
@@ -92,21 +92,7 @@ status:
'''
from ..module_utils.tower_api import TowerModule
import time
def check_job(module, job_url):
response = module.get_endpoint(job_url)
if response['status_code'] != 200:
module.fail_json(msg="Unable to read job from Tower {0}: {1}".format(response['status_code'], module.extract_errors_from_response(response)))
# Since we were successful, extract the fields we want to return
for k in ('id', 'status', 'elapsed', 'started', 'finished'):
module.json_output[k] = response['json'].get(k)
# And finally return the payload
return response['json']
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -120,7 +106,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
job_id = module.params.get('job_id')
@@ -153,31 +139,13 @@ def main():
if job is None:
module.fail_json(msg='Unable to wait on job {0}; that ID does not exist in Tower.'.format(job_id))
job_url = job['url']
# Grab our start time to compare against for the timeout
start = time.time()
# Get the initial job status from Tower, this will exit if there are any issues with the HTTP call
result = check_job(module, job_url)
# Loop while the job is not yet completed
while not result['finished']:
# If we are past our time out fail with a message
if timeout and timeout < time.time() - start:
module.json_output['msg'] = "Monitoring aborted due to timeout"
module.fail_json(**module.json_output)
# Put the process to sleep for our interval
time.sleep(interval)
# Check the job again
result = check_job(module, job_url)
# If the job has failed, we want to raise an Exception for that so we get a non-zero response.
if result['failed']:
module.json_output['msg'] = 'Job with id {0} failed'.format(job_id)
module.fail_json(**module.json_output)
# Invoke wait function
result = module.wait_on_url(
url=job['url'],
object_name=job_id,
object_type='legacy_job_wait',
timeout=timeout, interval=interval
)
module.exit_json(**module.json_output)

View File

@@ -54,7 +54,7 @@ EXAMPLES = '''
organization: My Organization
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -67,7 +67,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -43,12 +43,12 @@ EXAMPLES = '''
eula_accepted: True
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
module = TowerModule(
module = TowerAPIModule(
argument_spec=dict(
data=dict(type='dict', required=True),
eula_accepted=dict(type='bool', required=True),

View File

@@ -62,11 +62,11 @@ EXAMPLES = '''
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
module = TowerModule(argument_spec={})
module = TowerAPIModule(argument_spec={})
namespace = {
'awx': 'awx',
'tower': 'ansible'

View File

@@ -15,7 +15,7 @@ ANSIBLE_METADATA = {'metadata_version': '1.1',
DOCUMENTATION = '''
---
module: tower_notification
module: tower_notification_template
author: "Samuel Carpentier (@samcarpentier)"
short_description: create, update, or destroy Ansible Tower notification.
description:
@@ -203,7 +203,7 @@ extends_documentation_fragment: awx.awx.auth
EXAMPLES = '''
- name: Add Slack notification with custom messages
tower_notification:
tower_notification_template:
name: slack notification
organization: Default
notification_type: slack
@@ -222,7 +222,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Add webhook notification
tower_notification:
tower_notification_template:
name: webhook notification
notification_type: webhook
notification_configuration:
@@ -233,7 +233,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Add email notification
tower_notification:
tower_notification_template:
name: email notification
notification_type: email
notification_configuration:
@@ -250,7 +250,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Add twilio notification
tower_notification:
tower_notification_template:
name: twilio notification
notification_type: twilio
notification_configuration:
@@ -263,7 +263,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Add PagerDuty notification
tower_notification:
tower_notification_template:
name: pagerduty notification
notification_type: pagerduty
notification_configuration:
@@ -275,7 +275,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Add IRC notification
tower_notification:
tower_notification_template:
name: irc notification
notification_type: irc
notification_configuration:
@@ -290,7 +290,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
- name: Delete notification
tower_notification:
tower_notification_template:
name: old notification
state: absent
tower_config_file: "~/tower_cli.cfg"
@@ -300,7 +300,7 @@ EXAMPLES = '''
RETURN = ''' # '''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
OLD_INPUT_NAMES = (
'username', 'sender', 'recipients', 'use_tls',
@@ -355,7 +355,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -88,7 +88,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -106,7 +106,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -55,10 +55,12 @@ options:
- The refspec to use for the SCM resource.
type: str
default: ''
scm_credential:
credential:
description:
- Name of the credential to use with this SCM resource.
type: str
aliases:
- scm_credential
scm_clean:
description:
- Remove local modifications before updating.
@@ -86,11 +88,13 @@ options:
type: bool
aliases:
- scm_allow_override
job_timeout:
timeout:
description:
- The amount of time (in seconds) to run before the SCM Update is canceled. A value of 0 means no timeout.
default: 0
type: int
aliases:
- job_timeout
custom_virtualenv:
description:
- Local absolute file path containing a custom Python virtualenv to use
@@ -157,7 +161,7 @@ EXAMPLES = '''
import time
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def wait_for_project_update(module, last_request):
@@ -188,13 +192,13 @@ def main():
local_path=dict(),
scm_branch=dict(default=''),
scm_refspec=dict(default=''),
scm_credential=dict(),
credential=dict(aliases=['scm_credential']),
scm_clean=dict(type='bool', default=False),
scm_delete_on_update=dict(type='bool', default=False),
scm_update_on_launch=dict(type='bool', default=False),
scm_update_cache_timeout=dict(type='int', default=0),
allow_override=dict(type='bool', aliases=['scm_allow_override']),
job_timeout=dict(type='int', default=0),
timeout=dict(type='int', default=0, aliases=['job_timeout']),
custom_virtualenv=dict(),
organization=dict(required=True),
notification_templates_started=dict(type="list", elements='str'),
@@ -205,7 +209,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')
@@ -217,13 +221,13 @@ def main():
local_path = module.params.get('local_path')
scm_branch = module.params.get('scm_branch')
scm_refspec = module.params.get('scm_refspec')
scm_credential = module.params.get('scm_credential')
credential = module.params.get('credential')
scm_clean = module.params.get('scm_clean')
scm_delete_on_update = module.params.get('scm_delete_on_update')
scm_update_on_launch = module.params.get('scm_update_on_launch')
scm_update_cache_timeout = module.params.get('scm_update_cache_timeout')
allow_override = module.params.get('allow_override')
job_timeout = module.params.get('job_timeout')
timeout = module.params.get('timeout')
custom_virtualenv = module.params.get('custom_virtualenv')
organization = module.params.get('organization')
state = module.params.get('state')
@@ -231,8 +235,8 @@ def main():
# Attempt to look up the related items the user specified (these will fail the module if not found)
org_id = module.resolve_name_to_id('organizations', organization)
if scm_credential is not None:
scm_credential_id = module.resolve_name_to_id('credentials', scm_credential)
if credential is not None:
credential = module.resolve_name_to_id('credentials', credential)
# Attempt to look up project based on the provided name and org ID
project = module.get_one('projects', **{
@@ -276,7 +280,7 @@ def main():
'scm_refspec': scm_refspec,
'scm_clean': scm_clean,
'scm_delete_on_update': scm_delete_on_update,
'timeout': job_timeout,
'timeout': timeout,
'organization': org_id,
'scm_update_on_launch': scm_update_on_launch,
'scm_update_cache_timeout': scm_update_cache_timeout,
@@ -284,8 +288,8 @@ def main():
}
if description is not None:
project_fields['description'] = description
if scm_credential is not None:
project_fields['credential'] = scm_credential_id
if credential is not None:
project_fields['credential'] = credential
if allow_override is not None:
project_fields['allow_override'] = allow_override
if scm_type == '':

View File

@@ -134,7 +134,7 @@ assets:
sample: [ {}, {} ]
'''
from ..module_utils.ansible_tower import TowerModule, tower_auth_config, HAS_TOWER_CLI
from ..module_utils.tower_legacy import TowerLegacyModule, tower_auth_config, HAS_TOWER_CLI
try:
from tower_cli.cli.transfer.receive import Receiver
@@ -163,7 +163,7 @@ def main():
workflow=dict(type='list', default=[], elements='str'),
)
module = TowerModule(argument_spec=argument_spec, supports_check_mode=False)
module = TowerLegacyModule(argument_spec=argument_spec, supports_check_mode=False)
module.deprecate(msg="This module is deprecated and will be replaced by the AWX CLI export command.", version="awx.awx:14.0.0")

View File

@@ -89,7 +89,7 @@ EXAMPLES = '''
state: present
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -109,7 +109,7 @@ def main():
state=dict(choices=['present', 'absent'], default='present'),
)
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
role_type = module.params.pop('role')
role_field = role_type + '_role'
@@ -126,11 +126,10 @@ def main():
resource_data = {}
for param in resource_param_keys:
endpoint = module.param_to_endpoint(param)
name_field = 'username' if param == 'user' else 'name'
resource_name = params.get(param)
if resource_name:
resource = module.get_one(endpoint, **{'data': {name_field: resource_name}})
resource = module.get_one_by_name_or_id(module.param_to_endpoint(param), resource_name)
if not resource:
module.fail_json(
msg='Failed to update role, {0} not found in {1}'.format(param, endpoint),
@@ -170,14 +169,14 @@ def main():
if response['status_code'] == 204:
module.json_output['changed'] = True
else:
module.fail_json(msg="Failed to grant role {0}".format(response['json']['detail']))
module.fail_json(msg="Failed to grant role. {0}".format(response['json'].get('detail', response['json'].get('msg', 'unknown'))))
else:
for an_id in list(set(existing_associated_ids) & set(new_association_list)):
response = module.post_endpoint(association_endpoint, **{'data': {'id': int(an_id), 'disassociate': True}})
if response['status_code'] == 204:
module.json_output['changed'] = True
else:
module.fail_json(msg="Failed to revoke role {0}".format(response['json']['detail']))
module.fail_json(msg="Failed to revoke role. {0}".format(response['json'].get('detail', response['json'].get('msg', 'unknown'))))
module.exit_json(**module.json_output)

View File

@@ -136,7 +136,7 @@ EXAMPLES = '''
register: result
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -161,7 +161,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
rrule = module.params.get('rrule')

View File

@@ -81,7 +81,7 @@ import os
import sys
from ansible.module_utils.six.moves import StringIO
from ..module_utils.ansible_tower import TowerModule, tower_auth_config, HAS_TOWER_CLI
from ..module_utils.tower_legacy import TowerLegacyModule, tower_auth_config, HAS_TOWER_CLI
from tempfile import mkstemp
@@ -103,7 +103,7 @@ def main():
password_management=dict(default='default', choices=['default', 'random']),
)
module = TowerModule(argument_spec=argument_spec, supports_check_mode=False)
module = TowerLegacyModule(argument_spec=argument_spec, supports_check_mode=False)
module.deprecate(msg="This module is deprecated and will be replaced by the AWX CLI import command", version="awx.awx:14.0.0")

View File

@@ -70,7 +70,7 @@ EXAMPLES = '''
last_name: "surname"
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
try:
import yaml
@@ -111,7 +111,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(
module = TowerAPIModule(
argument_spec=argument_spec,
required_one_of=[['name', 'settings']],
mutually_exclusive=[['name', 'settings']],

View File

@@ -60,7 +60,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -74,7 +74,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -117,7 +117,7 @@ tower_token:
returned: on successful create
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def return_token(module, last_response):
@@ -143,7 +143,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(
module = TowerAPIModule(
argument_spec=argument_spec,
mutually_exclusive=[
('existing_token', 'existing_token_id'),

View File

@@ -102,7 +102,7 @@ EXAMPLES = '''
tower_config_file: "~/tower_cli.cfg"
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -119,7 +119,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
username = module.params.get('username')

View File

@@ -137,7 +137,7 @@ EXAMPLES = '''
organization: Default
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
@@ -176,7 +176,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')

View File

@@ -157,7 +157,7 @@ EXAMPLES = '''
- my-first-node
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
def main():
@@ -185,7 +185,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
identifier = module.params.get('identifier')

View File

@@ -91,9 +91,8 @@ EXAMPLES = '''
wait: False
'''
from ..module_utils.tower_api import TowerModule
from ..module_utils.tower_api import TowerAPIModule
import json
import time
def main():
@@ -111,7 +110,7 @@ def main():
)
# Create a module for ourselves
module = TowerModule(argument_spec=argument_spec)
module = TowerAPIModule(argument_spec=argument_spec)
optional_args = {}
# Extract our parameters
@@ -178,26 +177,13 @@ def main():
if not wait:
module.exit_json(**module.json_output)
# Grab our start time to compare against for the timeout
start = time.time()
job_url = result['json']['url']
while not result['json']['finished']:
# If we are past our time out fail with a message
if timeout and timeout < time.time() - start:
module.json_output['msg'] = "Monitoring aborted due to timeout"
module.fail_json(**module.json_output)
# Put the process to sleep for our interval
time.sleep(interval)
result = module.get_endpoint(job_url)
module.json_output['status'] = result['json']['status']
# If the job has failed, we want to raise a task failure for that so we get a non-zero response.
if result['json']['failed']:
module.json_output['msg'] = 'The workflow "{0}" failed'.format(name)
module.fail_json(**module.json_output)
# Invoke wait function
module.wait_on_url(
url=result['json']['url'],
object_name=name,
object_type='Workflow Job',
timeout=timeout, interval=interval
)
module.exit_json(**module.json_output)

View File

@@ -108,8 +108,8 @@ EXAMPLES = '''
RETURN = ''' # '''
from ..module_utils.ansible_tower import (
TowerModule,
from ..module_utils.tower_legacy import (
TowerLegacyModule,
tower_auth_config,
tower_check_mode
)
@@ -140,7 +140,7 @@ def main():
state=dict(choices=['present', 'absent'], default='present'),
)
module = TowerModule(
module = TowerLegacyModule(
argument_spec=argument_spec,
supports_check_mode=False
)