mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 05:42:50 +00:00
Creating playbook executor and dependent classes
This commit is contained in:
17
v2/ansible/module_utils/__init__.py
Normal file
17
v2/ansible/module_utils/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2013, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
103
v2/ansible/module_utils/a10.py
Normal file
103
v2/ansible/module_utils/a10.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
AXAPI_PORT_PROTOCOLS = {
|
||||
'tcp': 2,
|
||||
'udp': 3,
|
||||
}
|
||||
|
||||
AXAPI_VPORT_PROTOCOLS = {
|
||||
'tcp': 2,
|
||||
'udp': 3,
|
||||
'fast-http': 9,
|
||||
'http': 11,
|
||||
'https': 12,
|
||||
}
|
||||
|
||||
def a10_argument_spec():
|
||||
return dict(
|
||||
host=dict(type='str', required=True),
|
||||
username=dict(type='str', aliases=['user', 'admin'], required=True),
|
||||
password=dict(type='str', aliases=['pass', 'pwd'], required=True, no_log=True),
|
||||
write_config=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
def axapi_failure(result):
|
||||
if 'response' in result and result['response'].get('status') == 'fail':
|
||||
return True
|
||||
return False
|
||||
|
||||
def axapi_call(module, url, post=None):
|
||||
'''
|
||||
Returns a datastructure based on the result of the API call
|
||||
'''
|
||||
rsp, info = fetch_url(module, url, data=post)
|
||||
if not rsp or info['status'] >= 400:
|
||||
module.fail_json(msg="failed to connect (status code %s), error was %s" % (info['status'], info.get('msg', 'no error given')))
|
||||
try:
|
||||
raw_data = rsp.read()
|
||||
data = json.loads(raw_data)
|
||||
except ValueError:
|
||||
# at least one API call (system.action.write_config) returns
|
||||
# XML even when JSON is requested, so do some minimal handling
|
||||
# here to prevent failing even when the call succeeded
|
||||
if 'status="ok"' in raw_data.lower():
|
||||
data = {"response": {"status": "OK"}}
|
||||
else:
|
||||
data = {"response": {"status": "fail", "err": {"msg": raw_data}}}
|
||||
except:
|
||||
module.fail_json(msg="could not read the result from the host")
|
||||
finally:
|
||||
rsp.close()
|
||||
return data
|
||||
|
||||
def axapi_authenticate(module, base_url, username, password):
|
||||
url = '%s&method=authenticate&username=%s&password=%s' % (base_url, username, password)
|
||||
result = axapi_call(module, url)
|
||||
if axapi_failure(result):
|
||||
return module.fail_json(msg=result['response']['err']['msg'])
|
||||
sessid = result['session_id']
|
||||
return base_url + '&session_id=' + sessid
|
||||
|
||||
def axapi_enabled_disabled(flag):
|
||||
'''
|
||||
The axapi uses 0/1 integer values for flags, rather than strings
|
||||
or booleans, so convert the given flag to a 0 or 1. For now, params
|
||||
are specified as strings only so thats what we check.
|
||||
'''
|
||||
if flag == 'enabled':
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def axapi_get_port_protocol(protocol):
|
||||
return AXAPI_PORT_PROTOCOLS.get(protocol.lower(), None)
|
||||
|
||||
def axapi_get_vport_protocol(protocol):
|
||||
return AXAPI_VPORT_PROTOCOLS.get(protocol.lower(), None)
|
||||
|
||||
1556
v2/ansible/module_utils/basic.py
Normal file
1556
v2/ansible/module_utils/basic.py
Normal file
File diff suppressed because it is too large
Load Diff
194
v2/ansible/module_utils/ec2.py
Normal file
194
v2/ansible/module_utils/ec2.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
try:
|
||||
from distutils.version import LooseVersion
|
||||
HAS_LOOSE_VERSION = True
|
||||
except:
|
||||
HAS_LOOSE_VERSION = False
|
||||
|
||||
AWS_REGIONS = [
|
||||
'ap-northeast-1',
|
||||
'ap-southeast-1',
|
||||
'ap-southeast-2',
|
||||
'cn-north-1',
|
||||
'eu-central-1',
|
||||
'eu-west-1',
|
||||
'sa-east-1',
|
||||
'us-east-1',
|
||||
'us-west-1',
|
||||
'us-west-2',
|
||||
'us-gov-west-1',
|
||||
]
|
||||
|
||||
|
||||
def aws_common_argument_spec():
|
||||
return dict(
|
||||
ec2_url=dict(),
|
||||
aws_secret_key=dict(aliases=['ec2_secret_key', 'secret_key'], no_log=True),
|
||||
aws_access_key=dict(aliases=['ec2_access_key', 'access_key']),
|
||||
validate_certs=dict(default=True, type='bool'),
|
||||
security_token=dict(no_log=True),
|
||||
profile=dict(),
|
||||
)
|
||||
|
||||
|
||||
def ec2_argument_spec():
|
||||
spec = aws_common_argument_spec()
|
||||
spec.update(
|
||||
dict(
|
||||
region=dict(aliases=['aws_region', 'ec2_region'], choices=AWS_REGIONS),
|
||||
)
|
||||
)
|
||||
return spec
|
||||
|
||||
|
||||
def boto_supports_profile_name():
|
||||
return hasattr(boto.ec2.EC2Connection, 'profile_name')
|
||||
|
||||
|
||||
def get_aws_connection_info(module):
|
||||
|
||||
# Check module args for credentials, then check environment vars
|
||||
# access_key
|
||||
|
||||
ec2_url = module.params.get('ec2_url')
|
||||
access_key = module.params.get('aws_access_key')
|
||||
secret_key = module.params.get('aws_secret_key')
|
||||
security_token = module.params.get('security_token')
|
||||
region = module.params.get('region')
|
||||
profile_name = module.params.get('profile')
|
||||
validate_certs = module.params.get('validate_certs')
|
||||
|
||||
if not ec2_url:
|
||||
if 'EC2_URL' in os.environ:
|
||||
ec2_url = os.environ['EC2_URL']
|
||||
elif 'AWS_URL' in os.environ:
|
||||
ec2_url = os.environ['AWS_URL']
|
||||
|
||||
if not access_key:
|
||||
if 'EC2_ACCESS_KEY' in os.environ:
|
||||
access_key = os.environ['EC2_ACCESS_KEY']
|
||||
elif 'AWS_ACCESS_KEY_ID' in os.environ:
|
||||
access_key = os.environ['AWS_ACCESS_KEY_ID']
|
||||
elif 'AWS_ACCESS_KEY' in os.environ:
|
||||
access_key = os.environ['AWS_ACCESS_KEY']
|
||||
else:
|
||||
# in case access_key came in as empty string
|
||||
access_key = None
|
||||
|
||||
if not secret_key:
|
||||
if 'EC2_SECRET_KEY' in os.environ:
|
||||
secret_key = os.environ['EC2_SECRET_KEY']
|
||||
elif 'AWS_SECRET_ACCESS_KEY' in os.environ:
|
||||
secret_key = os.environ['AWS_SECRET_ACCESS_KEY']
|
||||
elif 'AWS_SECRET_KEY' in os.environ:
|
||||
secret_key = os.environ['AWS_SECRET_KEY']
|
||||
else:
|
||||
# in case secret_key came in as empty string
|
||||
secret_key = None
|
||||
|
||||
if not region:
|
||||
if 'EC2_REGION' in os.environ:
|
||||
region = os.environ['EC2_REGION']
|
||||
elif 'AWS_REGION' in os.environ:
|
||||
region = os.environ['AWS_REGION']
|
||||
else:
|
||||
# boto.config.get returns None if config not found
|
||||
region = boto.config.get('Boto', 'aws_region')
|
||||
if not region:
|
||||
region = boto.config.get('Boto', 'ec2_region')
|
||||
|
||||
if not security_token:
|
||||
if 'AWS_SECURITY_TOKEN' in os.environ:
|
||||
security_token = os.environ['AWS_SECURITY_TOKEN']
|
||||
else:
|
||||
# in case security_token came in as empty string
|
||||
security_token = None
|
||||
|
||||
boto_params = dict(aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
security_token=security_token)
|
||||
|
||||
# profile_name only works as a key in boto >= 2.24
|
||||
# so only set profile_name if passed as an argument
|
||||
if profile_name:
|
||||
if not boto_supports_profile_name():
|
||||
module.fail_json("boto does not support profile_name before 2.24")
|
||||
boto_params['profile_name'] = profile_name
|
||||
|
||||
if validate_certs and HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"):
|
||||
boto_params['validate_certs'] = validate_certs
|
||||
|
||||
return region, ec2_url, boto_params
|
||||
|
||||
|
||||
def get_ec2_creds(module):
|
||||
''' for compatibility mode with old modules that don't/can't yet
|
||||
use ec2_connect method '''
|
||||
region, ec2_url, boto_params = get_aws_connection_info(module)
|
||||
return ec2_url, boto_params['aws_access_key_id'], boto_params['aws_secret_access_key'], region
|
||||
|
||||
|
||||
def boto_fix_security_token_in_profile(conn, profile_name):
|
||||
''' monkey patch for boto issue boto/boto#2100 '''
|
||||
profile = 'profile ' + profile_name
|
||||
if boto.config.has_option(profile, 'aws_security_token'):
|
||||
conn.provider.set_security_token(boto.config.get(profile, 'aws_security_token'))
|
||||
return conn
|
||||
|
||||
|
||||
def connect_to_aws(aws_module, region, **params):
|
||||
conn = aws_module.connect_to_region(region, **params)
|
||||
if params.get('profile_name'):
|
||||
conn = boto_fix_security_token_in_profile(conn, params['profile_name'])
|
||||
return conn
|
||||
|
||||
|
||||
def ec2_connect(module):
|
||||
|
||||
""" Return an ec2 connection"""
|
||||
|
||||
region, ec2_url, boto_params = get_aws_connection_info(module)
|
||||
|
||||
# If we have a region specified, connect to its endpoint.
|
||||
if region:
|
||||
try:
|
||||
ec2 = connect_to_aws(boto.ec2, region, **boto_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
# Otherwise, no region so we fallback to the old connection method
|
||||
elif ec2_url:
|
||||
try:
|
||||
ec2 = boto.connect_ec2_endpoint(ec2_url, **boto_params)
|
||||
except boto.exception.NoAuthHandlerFound, e:
|
||||
module.fail_json(msg=str(e))
|
||||
else:
|
||||
module.fail_json(msg="Either region or ec2_url must be specified")
|
||||
|
||||
return ec2
|
||||
2451
v2/ansible/module_utils/facts.py
Normal file
2451
v2/ansible/module_utils/facts.py
Normal file
File diff suppressed because it is too large
Load Diff
87
v2/ansible/module_utils/gce.py
Normal file
87
v2/ansible/module_utils/gce.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Franck Cuny <franck.cuny@gmail.com>, 2014
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
import pprint
|
||||
|
||||
USER_AGENT_PRODUCT="Ansible-gce"
|
||||
USER_AGENT_VERSION="v1"
|
||||
|
||||
def gce_connect(module):
|
||||
"""Return a Google Cloud Engine connection."""
|
||||
service_account_email = module.params.get('service_account_email', None)
|
||||
pem_file = module.params.get('pem_file', None)
|
||||
project_id = module.params.get('project_id', None)
|
||||
|
||||
# If any of the values are not given as parameters, check the appropriate
|
||||
# environment variables.
|
||||
if not service_account_email:
|
||||
service_account_email = os.environ.get('GCE_EMAIL', None)
|
||||
if not project_id:
|
||||
project_id = os.environ.get('GCE_PROJECT', None)
|
||||
if not pem_file:
|
||||
pem_file = os.environ.get('GCE_PEM_FILE_PATH', None)
|
||||
|
||||
# If we still don't have one or more of our credentials, attempt to
|
||||
# get the remaining values from the libcloud secrets file.
|
||||
if service_account_email is None or pem_file is None:
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
secrets = None
|
||||
|
||||
if hasattr(secrets, 'GCE_PARAMS'):
|
||||
if not service_account_email:
|
||||
service_account_email = secrets.GCE_PARAMS[0]
|
||||
if not pem_file:
|
||||
pem_file = secrets.GCE_PARAMS[1]
|
||||
keyword_params = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||
if not project_id:
|
||||
project_id = keyword_params.get('project', None)
|
||||
|
||||
# If we *still* don't have the credentials we need, then it's time to
|
||||
# just fail out.
|
||||
if service_account_email is None or pem_file is None or project_id is None:
|
||||
module.fail_json(msg='Missing GCE connection parameters in libcloud '
|
||||
'secrets file.')
|
||||
return None
|
||||
|
||||
try:
|
||||
gce = get_driver(Provider.GCE)(service_account_email, pem_file, datacenter=module.params.get('zone'), project=project_id)
|
||||
gce.connection.user_agent_append("%s/%s" % (
|
||||
USER_AGENT_PRODUCT, USER_AGENT_VERSION))
|
||||
except (RuntimeError, ValueError), e:
|
||||
module.fail_json(msg=str(e), changed=False)
|
||||
except Exception, e:
|
||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||
|
||||
return gce
|
||||
|
||||
def unexpected_error_msg(error):
|
||||
"""Create an error string based on passed in error."""
|
||||
return 'Unexpected response: ' + pprint.pformat(vars(error))
|
||||
176
v2/ansible/module_utils/known_hosts.py
Normal file
176
v2/ansible/module_utils/known_hosts.py
Normal file
@@ -0,0 +1,176 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import hmac
|
||||
import urlparse
|
||||
|
||||
try:
|
||||
from hashlib import sha1
|
||||
except ImportError:
|
||||
import sha as sha1
|
||||
|
||||
HASHED_KEY_MAGIC = "|1|"
|
||||
|
||||
def add_git_host_key(module, url, accept_hostkey=True, create_dir=True):
|
||||
|
||||
""" idempotently add a git url hostkey """
|
||||
|
||||
fqdn = get_fqdn(module.params['repo'])
|
||||
|
||||
if fqdn:
|
||||
known_host = check_hostkey(module, fqdn)
|
||||
if not known_host:
|
||||
if accept_hostkey:
|
||||
rc, out, err = add_host_key(module, fqdn, create_dir=create_dir)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="failed to add %s hostkey: %s" % (fqdn, out + err))
|
||||
else:
|
||||
module.fail_json(msg="%s has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module" % fqdn)
|
||||
|
||||
def get_fqdn(repo_url):
|
||||
|
||||
""" chop the hostname out of a giturl """
|
||||
|
||||
result = None
|
||||
if "@" in repo_url and "://" not in repo_url:
|
||||
# most likely a git@ or ssh+git@ type URL
|
||||
repo_url = repo_url.split("@", 1)[1]
|
||||
if ":" in repo_url:
|
||||
repo_url = repo_url.split(":")[0]
|
||||
result = repo_url
|
||||
elif "/" in repo_url:
|
||||
repo_url = repo_url.split("/")[0]
|
||||
result = repo_url
|
||||
elif "://" in repo_url:
|
||||
# this should be something we can parse with urlparse
|
||||
parts = urlparse.urlparse(repo_url)
|
||||
if 'ssh' not in parts[0] and 'git' not in parts[0]:
|
||||
# don't try and scan a hostname that's not ssh
|
||||
return None
|
||||
# parts[1] will be empty on python2.4 on ssh:// or git:// urls, so
|
||||
# ensure we actually have a parts[1] before continuing.
|
||||
if parts[1] != '':
|
||||
result = parts[1]
|
||||
if ":" in result:
|
||||
result = result.split(":")[0]
|
||||
if "@" in result:
|
||||
result = result.split("@", 1)[1]
|
||||
|
||||
return result
|
||||
|
||||
def check_hostkey(module, fqdn):
|
||||
return not not_in_host_file(module, fqdn)
|
||||
|
||||
# this is a variant of code found in connection_plugins/paramiko.py and we should modify
|
||||
# the paramiko code to import and use this.
|
||||
|
||||
def not_in_host_file(self, host):
|
||||
|
||||
|
||||
if 'USER' in os.environ:
|
||||
user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts")
|
||||
else:
|
||||
user_host_file = "~/.ssh/known_hosts"
|
||||
user_host_file = os.path.expanduser(user_host_file)
|
||||
|
||||
host_file_list = []
|
||||
host_file_list.append(user_host_file)
|
||||
host_file_list.append("/etc/ssh/ssh_known_hosts")
|
||||
host_file_list.append("/etc/ssh/ssh_known_hosts2")
|
||||
|
||||
hfiles_not_found = 0
|
||||
for hf in host_file_list:
|
||||
if not os.path.exists(hf):
|
||||
hfiles_not_found += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
host_fh = open(hf)
|
||||
except IOError, e:
|
||||
hfiles_not_found += 1
|
||||
continue
|
||||
else:
|
||||
data = host_fh.read()
|
||||
host_fh.close()
|
||||
|
||||
for line in data.split("\n"):
|
||||
if line is None or " " not in line:
|
||||
continue
|
||||
tokens = line.split()
|
||||
if tokens[0].find(HASHED_KEY_MAGIC) == 0:
|
||||
# this is a hashed known host entry
|
||||
try:
|
||||
(kn_salt,kn_host) = tokens[0][len(HASHED_KEY_MAGIC):].split("|",2)
|
||||
hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1)
|
||||
hash.update(host)
|
||||
if hash.digest() == kn_host.decode('base64'):
|
||||
return False
|
||||
except:
|
||||
# invalid hashed host key, skip it
|
||||
continue
|
||||
else:
|
||||
# standard host file entry
|
||||
if host in tokens[0]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def add_host_key(module, fqdn, key_type="rsa", create_dir=False):
|
||||
|
||||
""" use ssh-keyscan to add the hostkey """
|
||||
|
||||
result = False
|
||||
keyscan_cmd = module.get_bin_path('ssh-keyscan', True)
|
||||
|
||||
if 'USER' in os.environ:
|
||||
user_ssh_dir = os.path.expandvars("~${USER}/.ssh/")
|
||||
user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts")
|
||||
else:
|
||||
user_ssh_dir = "~/.ssh/"
|
||||
user_host_file = "~/.ssh/known_hosts"
|
||||
user_ssh_dir = os.path.expanduser(user_ssh_dir)
|
||||
|
||||
if not os.path.exists(user_ssh_dir):
|
||||
if create_dir:
|
||||
try:
|
||||
os.makedirs(user_ssh_dir, 0700)
|
||||
except:
|
||||
module.fail_json(msg="failed to create host key directory: %s" % user_ssh_dir)
|
||||
else:
|
||||
module.fail_json(msg="%s does not exist" % user_ssh_dir)
|
||||
elif not os.path.isdir(user_ssh_dir):
|
||||
module.fail_json(msg="%s is not a directory" % user_ssh_dir)
|
||||
|
||||
this_cmd = "%s -t %s %s" % (keyscan_cmd, key_type, fqdn)
|
||||
|
||||
rc, out, err = module.run_command(this_cmd)
|
||||
module.append_to_file(user_host_file, out)
|
||||
|
||||
return rc, out, err
|
||||
|
||||
69
v2/ansible/module_utils/openstack.py
Normal file
69
v2/ansible/module_utils/openstack.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def openstack_argument_spec():
|
||||
# Consume standard OpenStack environment variables.
|
||||
# This is mainly only useful for ad-hoc command line operation as
|
||||
# in playbooks one would assume variables would be used appropriately
|
||||
OS_AUTH_URL=os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/')
|
||||
OS_PASSWORD=os.environ.get('OS_PASSWORD', None)
|
||||
OS_REGION_NAME=os.environ.get('OS_REGION_NAME', None)
|
||||
OS_USERNAME=os.environ.get('OS_USERNAME', 'admin')
|
||||
OS_TENANT_NAME=os.environ.get('OS_TENANT_NAME', OS_USERNAME)
|
||||
|
||||
spec = dict(
|
||||
login_username = dict(default=OS_USERNAME),
|
||||
auth_url = dict(default=OS_AUTH_URL),
|
||||
region_name = dict(default=OS_REGION_NAME),
|
||||
availability_zone = dict(default=None),
|
||||
)
|
||||
if OS_PASSWORD:
|
||||
spec['login_password'] = dict(default=OS_PASSWORD)
|
||||
else:
|
||||
spec['login_password'] = dict(required=True)
|
||||
if OS_TENANT_NAME:
|
||||
spec['login_tenant_name'] = dict(default=OS_TENANT_NAME)
|
||||
else:
|
||||
spec['login_tenant_name'] = dict(required=True)
|
||||
return spec
|
||||
|
||||
def openstack_find_nova_addresses(addresses, ext_tag, key_name=None):
|
||||
|
||||
ret = []
|
||||
for (k, v) in addresses.iteritems():
|
||||
if key_name and k == key_name:
|
||||
ret.extend([addrs['addr'] for addrs in v])
|
||||
else:
|
||||
for interface_spec in v:
|
||||
if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag:
|
||||
ret.append(interface_spec['addr'])
|
||||
return ret
|
||||
|
||||
144
v2/ansible/module_utils/powershell.ps1
Normal file
144
v2/ansible/module_utils/powershell.ps1
Normal file
@@ -0,0 +1,144 @@
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2014, and others
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
# Helper function to parse Ansible JSON arguments from a file passed as
|
||||
# the single argument to the module
|
||||
# Example: $params = Parse-Args $args
|
||||
Function Parse-Args($arguments)
|
||||
{
|
||||
$parameters = New-Object psobject;
|
||||
If ($arguments.Length -gt 0)
|
||||
{
|
||||
$parameters = Get-Content $arguments[0] | ConvertFrom-Json;
|
||||
}
|
||||
$parameters;
|
||||
}
|
||||
|
||||
# Helper function to set an "attribute" on a psobject instance in powershell.
|
||||
# This is a convenience to make adding Members to the object easier and
|
||||
# slightly more pythonic
|
||||
# Example: Set-Attr $result "changed" $true
|
||||
Function Set-Attr($obj, $name, $value)
|
||||
{
|
||||
# If the provided $obj is undefined, define one to be nice
|
||||
If (-not $obj.GetType)
|
||||
{
|
||||
$obj = New-Object psobject
|
||||
}
|
||||
|
||||
$obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value
|
||||
}
|
||||
|
||||
# Helper function to convert a powershell object to JSON to echo it, exiting
|
||||
# the script
|
||||
# Example: Exit-Json $result
|
||||
Function Exit-Json($obj)
|
||||
{
|
||||
# If the provided $obj is undefined, define one to be nice
|
||||
If (-not $obj.GetType)
|
||||
{
|
||||
$obj = New-Object psobject
|
||||
}
|
||||
|
||||
echo $obj | ConvertTo-Json -Depth 99
|
||||
Exit
|
||||
}
|
||||
|
||||
# Helper function to add the "msg" property and "failed" property, convert the
|
||||
# powershell object to JSON and echo it, exiting the script
|
||||
# Example: Fail-Json $result "This is the failure message"
|
||||
Function Fail-Json($obj, $message = $null)
|
||||
{
|
||||
# If we weren't given 2 args, and the only arg was a string, create a new
|
||||
# psobject and use the arg as the failure message
|
||||
If ($message -eq $null -and $obj.GetType().Name -eq "String")
|
||||
{
|
||||
$message = $obj
|
||||
$obj = New-Object psobject
|
||||
}
|
||||
# If the first args is undefined or not an object, make it an object
|
||||
ElseIf (-not $obj.GetType -or $obj.GetType().Name -ne "PSCustomObject")
|
||||
{
|
||||
$obj = New-Object psobject
|
||||
}
|
||||
|
||||
Set-Attr $obj "msg" $message
|
||||
Set-Attr $obj "failed" $true
|
||||
echo $obj | ConvertTo-Json -Depth 99
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# Helper function to get an "attribute" from a psobject instance in powershell.
|
||||
# This is a convenience to make getting Members from an object easier and
|
||||
# slightly more pythonic
|
||||
# Example: $attr = Get-Attr $response "code" -default "1"
|
||||
#Note that if you use the failifempty option, you do need to specify resultobject as well.
|
||||
Function Get-Attr($obj, $name, $default = $null,$resultobj, $failifempty=$false, $emptyattributefailmessage)
|
||||
{
|
||||
# Check if the provided Member $name exists in $obj and return it or the
|
||||
# default
|
||||
If ($obj.$name.GetType)
|
||||
{
|
||||
$obj.$name
|
||||
}
|
||||
Elseif($failifempty -eq $false)
|
||||
{
|
||||
$default
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!$emptyattributefailmessage) {$emptyattributefailmessage = "Missing required argument: $name"}
|
||||
Fail-Json -obj $resultobj -message $emptyattributefailmessage
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Helper filter/pipeline function to convert a value to boolean following current
|
||||
# Ansible practices
|
||||
# Example: $is_true = "true" | ConvertTo-Bool
|
||||
Function ConvertTo-Bool
|
||||
{
|
||||
param(
|
||||
[parameter(valuefrompipeline=$true)]
|
||||
$obj
|
||||
)
|
||||
|
||||
$boolean_strings = "yes", "on", "1", "true", 1
|
||||
$obj_string = [string]$obj
|
||||
|
||||
if (($obj.GetType().Name -eq "Boolean" -and $obj) -or $boolean_strings -contains $obj_string.ToLower())
|
||||
{
|
||||
$true
|
||||
}
|
||||
Else
|
||||
{
|
||||
$false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
277
v2/ansible/module_utils/rax.py
Normal file
277
v2/ansible/module_utils/rax.py
Normal file
@@ -0,0 +1,277 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by
|
||||
# Ansible still belong to the author of the module, and may assign their own
|
||||
# license to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
FINAL_STATUSES = ('ACTIVE', 'ERROR')
|
||||
VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use',
|
||||
'error', 'error_deleting')
|
||||
|
||||
CLB_ALGORITHMS = ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN',
|
||||
'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN']
|
||||
CLB_PROTOCOLS = ['DNS_TCP', 'DNS_UDP', 'FTP', 'HTTP', 'HTTPS', 'IMAPS',
|
||||
'IMAPv4', 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP',
|
||||
'TCP', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP']
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
|
||||
PUBLIC_NET_ID = "00000000-0000-0000-0000-000000000000"
|
||||
SERVICE_NET_ID = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
|
||||
def rax_slugify(value):
|
||||
"""Prepend a key with rax_ and normalize the key name"""
|
||||
return 'rax_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_'))
|
||||
|
||||
|
||||
def rax_clb_node_to_dict(obj):
|
||||
"""Function to convert a CLB Node object to a dict"""
|
||||
if not obj:
|
||||
return {}
|
||||
node = obj.to_dict()
|
||||
node['id'] = obj.id
|
||||
node['weight'] = obj.weight
|
||||
return node
|
||||
|
||||
|
||||
def rax_to_dict(obj, obj_type='standard'):
|
||||
"""Generic function to convert a pyrax object to a dict
|
||||
|
||||
obj_type values:
|
||||
standard
|
||||
clb
|
||||
server
|
||||
|
||||
"""
|
||||
instance = {}
|
||||
for key in dir(obj):
|
||||
value = getattr(obj, key)
|
||||
if obj_type == 'clb' and key == 'nodes':
|
||||
instance[key] = []
|
||||
for node in value:
|
||||
instance[key].append(rax_clb_node_to_dict(node))
|
||||
elif (isinstance(value, list) and len(value) > 0 and
|
||||
not isinstance(value[0], NON_CALLABLES)):
|
||||
instance[key] = []
|
||||
for item in value:
|
||||
instance[key].append(rax_to_dict(item))
|
||||
elif (isinstance(value, NON_CALLABLES) and not key.startswith('_')):
|
||||
if obj_type == 'server':
|
||||
key = rax_slugify(key)
|
||||
instance[key] = value
|
||||
|
||||
if obj_type == 'server':
|
||||
for attr in ['id', 'accessIPv4', 'name', 'status']:
|
||||
instance[attr] = instance.get(rax_slugify(attr))
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
def rax_find_image(module, rax_module, image):
|
||||
cs = rax_module.cloudservers
|
||||
try:
|
||||
UUID(image)
|
||||
except ValueError:
|
||||
try:
|
||||
image = cs.images.find(human_id=image)
|
||||
except(cs.exceptions.NotFound,
|
||||
cs.exceptions.NoUniqueMatch):
|
||||
try:
|
||||
image = cs.images.find(name=image)
|
||||
except (cs.exceptions.NotFound,
|
||||
cs.exceptions.NoUniqueMatch):
|
||||
module.fail_json(msg='No matching image found (%s)' %
|
||||
image)
|
||||
|
||||
return rax_module.utils.get_id(image)
|
||||
|
||||
|
||||
def rax_find_volume(module, rax_module, name):
|
||||
cbs = rax_module.cloud_blockstorage
|
||||
try:
|
||||
UUID(name)
|
||||
volume = cbs.get(name)
|
||||
except ValueError:
|
||||
try:
|
||||
volume = cbs.find(name=name)
|
||||
except rax_module.exc.NotFound:
|
||||
volume = None
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
return volume
|
||||
|
||||
|
||||
def rax_find_network(module, rax_module, network):
|
||||
cnw = rax_module.cloud_networks
|
||||
try:
|
||||
UUID(network)
|
||||
except ValueError:
|
||||
if network.lower() == 'public':
|
||||
return cnw.get_server_networks(PUBLIC_NET_ID)
|
||||
elif network.lower() == 'private':
|
||||
return cnw.get_server_networks(SERVICE_NET_ID)
|
||||
else:
|
||||
try:
|
||||
network_obj = cnw.find_network_by_label(network)
|
||||
except (rax_module.exceptions.NetworkNotFound,
|
||||
rax_module.exceptions.NetworkLabelNotUnique):
|
||||
module.fail_json(msg='No matching network found (%s)' %
|
||||
network)
|
||||
else:
|
||||
return cnw.get_server_networks(network_obj)
|
||||
else:
|
||||
return cnw.get_server_networks(network)
|
||||
|
||||
|
||||
def rax_find_server(module, rax_module, server):
|
||||
cs = rax_module.cloudservers
|
||||
try:
|
||||
UUID(server)
|
||||
server = cs.servers.get(server)
|
||||
except ValueError:
|
||||
servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
|
||||
if not servers:
|
||||
module.fail_json(msg='No Server was matched by name, '
|
||||
'try using the Server ID instead')
|
||||
if len(servers) > 1:
|
||||
module.fail_json(msg='Multiple servers matched by name, '
|
||||
'try using the Server ID instead')
|
||||
|
||||
# We made it this far, grab the first and hopefully only server
|
||||
# in the list
|
||||
server = servers[0]
|
||||
return server
|
||||
|
||||
|
||||
def rax_find_loadbalancer(module, rax_module, loadbalancer):
|
||||
clb = rax_module.cloud_loadbalancers
|
||||
try:
|
||||
found = clb.get(loadbalancer)
|
||||
except:
|
||||
found = []
|
||||
for lb in clb.list():
|
||||
if loadbalancer == lb.name:
|
||||
found.append(lb)
|
||||
|
||||
if not found:
|
||||
module.fail_json(msg='No loadbalancer was matched')
|
||||
|
||||
if len(found) > 1:
|
||||
module.fail_json(msg='Multiple loadbalancers matched')
|
||||
|
||||
# We made it this far, grab the first and hopefully only item
|
||||
# in the list
|
||||
found = found[0]
|
||||
|
||||
return found
|
||||
|
||||
|
||||
def rax_argument_spec():
|
||||
return dict(
|
||||
api_key=dict(type='str', aliases=['password'], no_log=True),
|
||||
auth_endpoint=dict(type='str'),
|
||||
credentials=dict(type='str', aliases=['creds_file']),
|
||||
env=dict(type='str'),
|
||||
identity_type=dict(type='str', default='rackspace'),
|
||||
region=dict(type='str'),
|
||||
tenant_id=dict(type='str'),
|
||||
tenant_name=dict(type='str'),
|
||||
username=dict(type='str'),
|
||||
verify_ssl=dict(choices=BOOLEANS, type='bool'),
|
||||
)
|
||||
|
||||
|
||||
def rax_required_together():
|
||||
return [['api_key', 'username']]
|
||||
|
||||
|
||||
def setup_rax_module(module, rax_module, region_required=True):
|
||||
rax_module.USER_AGENT = 'ansible/%s %s' % (ANSIBLE_VERSION,
|
||||
rax_module.USER_AGENT)
|
||||
|
||||
api_key = module.params.get('api_key')
|
||||
auth_endpoint = module.params.get('auth_endpoint')
|
||||
credentials = module.params.get('credentials')
|
||||
env = module.params.get('env')
|
||||
identity_type = module.params.get('identity_type')
|
||||
region = module.params.get('region')
|
||||
tenant_id = module.params.get('tenant_id')
|
||||
tenant_name = module.params.get('tenant_name')
|
||||
username = module.params.get('username')
|
||||
verify_ssl = module.params.get('verify_ssl')
|
||||
|
||||
if env is not None:
|
||||
rax_module.set_environment(env)
|
||||
|
||||
rax_module.set_setting('identity_type', identity_type)
|
||||
if verify_ssl is not None:
|
||||
rax_module.set_setting('verify_ssl', verify_ssl)
|
||||
if auth_endpoint is not None:
|
||||
rax_module.set_setting('auth_endpoint', auth_endpoint)
|
||||
if tenant_id is not None:
|
||||
rax_module.set_setting('tenant_id', tenant_id)
|
||||
if tenant_name is not None:
|
||||
rax_module.set_setting('tenant_name', tenant_name)
|
||||
|
||||
try:
|
||||
username = username or os.environ.get('RAX_USERNAME')
|
||||
if not username:
|
||||
username = rax_module.get_setting('keyring_username')
|
||||
if username:
|
||||
api_key = 'USE_KEYRING'
|
||||
if not api_key:
|
||||
api_key = os.environ.get('RAX_API_KEY')
|
||||
credentials = (credentials or os.environ.get('RAX_CREDENTIALS') or
|
||||
os.environ.get('RAX_CREDS_FILE'))
|
||||
region = (region or os.environ.get('RAX_REGION') or
|
||||
rax_module.get_setting('region'))
|
||||
except KeyError, e:
|
||||
module.fail_json(msg='Unable to load %s' % e.message)
|
||||
|
||||
try:
|
||||
if api_key and username:
|
||||
if api_key == 'USE_KEYRING':
|
||||
rax_module.keyring_auth(username, region=region)
|
||||
else:
|
||||
rax_module.set_credentials(username, api_key=api_key,
|
||||
region=region)
|
||||
elif credentials:
|
||||
credentials = os.path.expanduser(credentials)
|
||||
rax_module.set_credential_file(credentials, region=region)
|
||||
else:
|
||||
raise Exception('No credentials supplied!')
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
||||
if region_required and region not in rax_module.regions:
|
||||
module.fail_json(msg='%s is not a valid region, must be one of: %s' %
|
||||
(region, ','.join(rax_module.regions)))
|
||||
|
||||
return rax_module
|
||||
280
v2/ansible/module_utils/redhat.py
Normal file
280
v2/ansible/module_utils/redhat.py
Normal file
@@ -0,0 +1,280 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), James Laska
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import re
|
||||
import types
|
||||
import ConfigParser
|
||||
import shlex
|
||||
|
||||
|
||||
class RegistrationBase(object):
|
||||
def __init__(self, module, username=None, password=None):
|
||||
self.module = module
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def configure(self):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
def enable(self):
|
||||
# Remove any existing redhat.repo
|
||||
redhat_repo = '/etc/yum.repos.d/redhat.repo'
|
||||
if os.path.isfile(redhat_repo):
|
||||
os.unlink(redhat_repo)
|
||||
|
||||
def register(self):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
def unregister(self):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
def unsubscribe(self):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
def update_plugin_conf(self, plugin, enabled=True):
|
||||
plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin
|
||||
if os.path.isfile(plugin_conf):
|
||||
cfg = ConfigParser.ConfigParser()
|
||||
cfg.read([plugin_conf])
|
||||
if enabled:
|
||||
cfg.set('main', 'enabled', 1)
|
||||
else:
|
||||
cfg.set('main', 'enabled', 0)
|
||||
fd = open(plugin_conf, 'rwa+')
|
||||
cfg.write(fd)
|
||||
fd.close()
|
||||
|
||||
def subscribe(self, **kwargs):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
|
||||
class Rhsm(RegistrationBase):
|
||||
def __init__(self, module, username=None, password=None):
|
||||
RegistrationBase.__init__(self, module, username, password)
|
||||
self.config = self._read_config()
|
||||
self.module = module
|
||||
|
||||
def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'):
|
||||
'''
|
||||
Load RHSM configuration from /etc/rhsm/rhsm.conf.
|
||||
Returns:
|
||||
* ConfigParser object
|
||||
'''
|
||||
|
||||
# Read RHSM defaults ...
|
||||
cp = ConfigParser.ConfigParser()
|
||||
cp.read(rhsm_conf)
|
||||
|
||||
# Add support for specifying a default value w/o having to standup some configuration
|
||||
# Yeah, I know this should be subclassed ... but, oh well
|
||||
def get_option_default(self, key, default=''):
|
||||
sect, opt = key.split('.', 1)
|
||||
if self.has_section(sect) and self.has_option(sect, opt):
|
||||
return self.get(sect, opt)
|
||||
else:
|
||||
return default
|
||||
|
||||
cp.get_option = types.MethodType(get_option_default, cp, ConfigParser.ConfigParser)
|
||||
|
||||
return cp
|
||||
|
||||
def enable(self):
|
||||
'''
|
||||
Enable the system to receive updates from subscription-manager.
|
||||
This involves updating affected yum plugins and removing any
|
||||
conflicting yum repositories.
|
||||
'''
|
||||
RegistrationBase.enable(self)
|
||||
self.update_plugin_conf('rhnplugin', False)
|
||||
self.update_plugin_conf('subscription-manager', True)
|
||||
|
||||
def configure(self, **kwargs):
|
||||
'''
|
||||
Configure the system as directed for registration with RHN
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'config']
|
||||
|
||||
# Pass supplied **kwargs as parameters to subscription-manager. Ignore
|
||||
# non-configuration parameters and replace '_' with '.'. For example,
|
||||
# 'server_hostname' becomes '--system.hostname'.
|
||||
for k,v in kwargs.items():
|
||||
if re.search(r'^(system|rhsm)_', k):
|
||||
args.append('--%s=%s' % (k.replace('_','.'), v))
|
||||
|
||||
self.module.run_command(args, check_rc=True)
|
||||
|
||||
@property
|
||||
def is_registered(self):
|
||||
'''
|
||||
Determine whether the current system
|
||||
Returns:
|
||||
* Boolean - whether the current system is currently registered to
|
||||
RHN.
|
||||
'''
|
||||
# Quick version...
|
||||
if False:
|
||||
return os.path.isfile('/etc/pki/consumer/cert.pem') and \
|
||||
os.path.isfile('/etc/pki/consumer/key.pem')
|
||||
|
||||
args = ['subscription-manager', 'identity']
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=False)
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def register(self, username, password, autosubscribe, activationkey):
|
||||
'''
|
||||
Register the current system to the provided RHN server
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'register']
|
||||
|
||||
# Generate command arguments
|
||||
if activationkey:
|
||||
args.append('--activationkey "%s"' % activationkey)
|
||||
else:
|
||||
if autosubscribe:
|
||||
args.append('--autosubscribe')
|
||||
if username:
|
||||
args.extend(['--username', username])
|
||||
if password:
|
||||
args.extend(['--password', password])
|
||||
|
||||
# Do the needful...
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
|
||||
def unsubscribe(self):
|
||||
'''
|
||||
Unsubscribe a system from all subscribed channels
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'unsubscribe', '--all']
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
|
||||
def unregister(self):
|
||||
'''
|
||||
Unregister a currently registered system
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'unregister']
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
|
||||
def subscribe(self, regexp):
|
||||
'''
|
||||
Subscribe current system to available pools matching the specified
|
||||
regular expression
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
|
||||
# Available pools ready for subscription
|
||||
available_pools = RhsmPools(self.module)
|
||||
|
||||
for pool in available_pools.filter(regexp):
|
||||
pool.subscribe()
|
||||
|
||||
|
||||
class RhsmPool(object):
|
||||
'''
|
||||
Convenience class for housing subscription information
|
||||
'''
|
||||
|
||||
def __init__(self, module, **kwargs):
|
||||
self.module = module
|
||||
for k,v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__getattribute__('_name'))
|
||||
|
||||
def subscribe(self):
|
||||
args = "subscription-manager subscribe --pool %s" % self.PoolId
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=True)
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class RhsmPools(object):
|
||||
"""
|
||||
This class is used for manipulating pools subscriptions with RHSM
|
||||
"""
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.products = self._load_product_list()
|
||||
|
||||
def __iter__(self):
|
||||
return self.products.__iter__()
|
||||
|
||||
def _load_product_list(self):
|
||||
"""
|
||||
Loads list of all available pools for system in data structure
|
||||
"""
|
||||
args = "subscription-manager list --available"
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=True)
|
||||
|
||||
products = []
|
||||
for line in stdout.split('\n'):
|
||||
# Remove leading+trailing whitespace
|
||||
line = line.strip()
|
||||
# An empty line implies the end of an output group
|
||||
if len(line) == 0:
|
||||
continue
|
||||
# If a colon ':' is found, parse
|
||||
elif ':' in line:
|
||||
(key, value) = line.split(':',1)
|
||||
key = key.strip().replace(" ", "") # To unify
|
||||
value = value.strip()
|
||||
if key in ['ProductName', 'SubscriptionName']:
|
||||
# Remember the name for later processing
|
||||
products.append(RhsmPool(self.module, _name=value, key=value))
|
||||
elif products:
|
||||
# Associate value with most recently recorded product
|
||||
products[-1].__setattr__(key, value)
|
||||
# FIXME - log some warning?
|
||||
#else:
|
||||
# warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value))
|
||||
return products
|
||||
|
||||
def filter(self, regexp='^$'):
|
||||
'''
|
||||
Return a list of RhsmPools whose name matches the provided regular expression
|
||||
'''
|
||||
r = re.compile(regexp)
|
||||
for product in self.products:
|
||||
if r.search(product._name):
|
||||
yield product
|
||||
|
||||
201
v2/ansible/module_utils/splitter.py
Normal file
201
v2/ansible/module_utils/splitter.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# (c) 2014 James Cammarata, <jcammarata@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
def _get_quote_state(token, quote_char):
|
||||
'''
|
||||
the goal of this block is to determine if the quoted string
|
||||
is unterminated in which case it needs to be put back together
|
||||
'''
|
||||
# the char before the current one, used to see if
|
||||
# the current character is escaped
|
||||
prev_char = None
|
||||
for idx, cur_char in enumerate(token):
|
||||
if idx > 0:
|
||||
prev_char = token[idx-1]
|
||||
if cur_char in '"\'' and prev_char != '\\':
|
||||
if quote_char:
|
||||
if cur_char == quote_char:
|
||||
quote_char = None
|
||||
else:
|
||||
quote_char = cur_char
|
||||
return quote_char
|
||||
|
||||
def _count_jinja2_blocks(token, cur_depth, open_token, close_token):
|
||||
'''
|
||||
this function counts the number of opening/closing blocks for a
|
||||
given opening/closing type and adjusts the current depth for that
|
||||
block based on the difference
|
||||
'''
|
||||
num_open = token.count(open_token)
|
||||
num_close = token.count(close_token)
|
||||
if num_open != num_close:
|
||||
cur_depth += (num_open - num_close)
|
||||
if cur_depth < 0:
|
||||
cur_depth = 0
|
||||
return cur_depth
|
||||
|
||||
def split_args(args):
|
||||
'''
|
||||
Splits args on whitespace, but intelligently reassembles
|
||||
those that may have been split over a jinja2 block or quotes.
|
||||
|
||||
When used in a remote module, we won't ever have to be concerned about
|
||||
jinja2 blocks, however this function is/will be used in the
|
||||
core portions as well before the args are templated.
|
||||
|
||||
example input: a=b c="foo bar"
|
||||
example output: ['a=b', 'c="foo bar"']
|
||||
|
||||
Basically this is a variation shlex that has some more intelligence for
|
||||
how Ansible needs to use it.
|
||||
'''
|
||||
|
||||
# the list of params parsed out of the arg string
|
||||
# this is going to be the result value when we are donei
|
||||
params = []
|
||||
|
||||
# here we encode the args, so we have a uniform charset to
|
||||
# work with, and split on white space
|
||||
args = args.strip()
|
||||
try:
|
||||
args = args.encode('utf-8')
|
||||
do_decode = True
|
||||
except UnicodeDecodeError:
|
||||
do_decode = False
|
||||
items = args.split('\n')
|
||||
|
||||
# iterate over the tokens, and reassemble any that may have been
|
||||
# split on a space inside a jinja2 block.
|
||||
# ex if tokens are "{{", "foo", "}}" these go together
|
||||
|
||||
# These variables are used
|
||||
# to keep track of the state of the parsing, since blocks and quotes
|
||||
# may be nested within each other.
|
||||
|
||||
quote_char = None
|
||||
inside_quotes = False
|
||||
print_depth = 0 # used to count nested jinja2 {{ }} blocks
|
||||
block_depth = 0 # used to count nested jinja2 {% %} blocks
|
||||
comment_depth = 0 # used to count nested jinja2 {# #} blocks
|
||||
|
||||
# now we loop over each split chunk, coalescing tokens if the white space
|
||||
# split occurred within quotes or a jinja2 block of some kind
|
||||
for itemidx,item in enumerate(items):
|
||||
|
||||
# we split on spaces and newlines separately, so that we
|
||||
# can tell which character we split on for reassembly
|
||||
# inside quotation characters
|
||||
tokens = item.strip().split(' ')
|
||||
|
||||
line_continuation = False
|
||||
for idx,token in enumerate(tokens):
|
||||
|
||||
# if we hit a line continuation character, but
|
||||
# we're not inside quotes, ignore it and continue
|
||||
# on to the next token while setting a flag
|
||||
if token == '\\' and not inside_quotes:
|
||||
line_continuation = True
|
||||
continue
|
||||
|
||||
# store the previous quoting state for checking later
|
||||
was_inside_quotes = inside_quotes
|
||||
quote_char = _get_quote_state(token, quote_char)
|
||||
inside_quotes = quote_char is not None
|
||||
|
||||
# multiple conditions may append a token to the list of params,
|
||||
# so we keep track with this flag to make sure it only happens once
|
||||
# append means add to the end of the list, don't append means concatenate
|
||||
# it to the end of the last token
|
||||
appended = False
|
||||
|
||||
# if we're inside quotes now, but weren't before, append the token
|
||||
# to the end of the list, since we'll tack on more to it later
|
||||
# otherwise, if we're inside any jinja2 block, inside quotes, or we were
|
||||
# inside quotes (but aren't now) concat this token to the last param
|
||||
if inside_quotes and not was_inside_quotes:
|
||||
params.append(token)
|
||||
appended = True
|
||||
elif print_depth or block_depth or comment_depth or inside_quotes or was_inside_quotes:
|
||||
if idx == 0 and not inside_quotes and was_inside_quotes:
|
||||
params[-1] = "%s%s" % (params[-1], token)
|
||||
elif len(tokens) > 1:
|
||||
spacer = ''
|
||||
if idx > 0:
|
||||
spacer = ' '
|
||||
params[-1] = "%s%s%s" % (params[-1], spacer, token)
|
||||
else:
|
||||
spacer = ''
|
||||
if not params[-1].endswith('\n') and idx == 0:
|
||||
spacer = '\n'
|
||||
params[-1] = "%s%s%s" % (params[-1], spacer, token)
|
||||
appended = True
|
||||
|
||||
# if the number of paired block tags is not the same, the depth has changed, so we calculate that here
|
||||
# and may append the current token to the params (if we haven't previously done so)
|
||||
prev_print_depth = print_depth
|
||||
print_depth = _count_jinja2_blocks(token, print_depth, "{{", "}}")
|
||||
if print_depth != prev_print_depth and not appended:
|
||||
params.append(token)
|
||||
appended = True
|
||||
|
||||
prev_block_depth = block_depth
|
||||
block_depth = _count_jinja2_blocks(token, block_depth, "{%", "%}")
|
||||
if block_depth != prev_block_depth and not appended:
|
||||
params.append(token)
|
||||
appended = True
|
||||
|
||||
prev_comment_depth = comment_depth
|
||||
comment_depth = _count_jinja2_blocks(token, comment_depth, "{#", "#}")
|
||||
if comment_depth != prev_comment_depth and not appended:
|
||||
params.append(token)
|
||||
appended = True
|
||||
|
||||
# finally, if we're at zero depth for all blocks and not inside quotes, and have not
|
||||
# yet appended anything to the list of params, we do so now
|
||||
if not (print_depth or block_depth or comment_depth) and not inside_quotes and not appended and token != '':
|
||||
params.append(token)
|
||||
|
||||
# if this was the last token in the list, and we have more than
|
||||
# one item (meaning we split on newlines), add a newline back here
|
||||
# to preserve the original structure
|
||||
if len(items) > 1 and itemidx != len(items) - 1 and not line_continuation:
|
||||
if not params[-1].endswith('\n') or item == '':
|
||||
params[-1] += '\n'
|
||||
|
||||
# always clear the line continuation flag
|
||||
line_continuation = False
|
||||
|
||||
# If we're done and things are not at zero depth or we're still inside quotes,
|
||||
# raise an error to indicate that the args were unbalanced
|
||||
if print_depth or block_depth or comment_depth or inside_quotes:
|
||||
raise Exception("error while splitting arguments, either an unbalanced jinja2 block or quotes")
|
||||
|
||||
# finally, we decode each param back to the unicode it was in the arg string
|
||||
if do_decode:
|
||||
params = [x.decode('utf-8') for x in params]
|
||||
|
||||
return params
|
||||
|
||||
def is_quoted(data):
|
||||
return len(data) > 0 and (data[0] == '"' and data[-1] == '"' or data[0] == "'" and data[-1] == "'")
|
||||
|
||||
def unquote(data):
|
||||
''' removes first and last quotes from a string, if the string starts and ends with the same quotes '''
|
||||
if is_quoted(data):
|
||||
return data[1:-1]
|
||||
return data
|
||||
|
||||
456
v2/ansible/module_utils/urls.py
Normal file
456
v2/ansible/module_utils/urls.py
Normal file
@@ -0,0 +1,456 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
try:
|
||||
import urllib
|
||||
HAS_URLLIB = True
|
||||
except:
|
||||
HAS_URLLIB = False
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
HAS_URLLIB2 = True
|
||||
except:
|
||||
HAS_URLLIB2 = False
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
HAS_URLPARSE = True
|
||||
except:
|
||||
HAS_URLPARSE = False
|
||||
|
||||
try:
|
||||
import ssl
|
||||
HAS_SSL=True
|
||||
except:
|
||||
HAS_SSL=False
|
||||
|
||||
import httplib
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import tempfile
|
||||
|
||||
|
||||
# This is a dummy cacert provided for Mac OS since you need at least 1
|
||||
# ca cert, regardless of validity, for Python on Mac OS to use the
|
||||
# keychain functionality in OpenSSL for validating SSL certificates.
|
||||
# See: http://mercurial.selenic.com/wiki/CACertificates#Mac_OS_X_10.6_and_higher
|
||||
DUMMY_CA_CERT = """-----BEGIN CERTIFICATE-----
|
||||
MIICvDCCAiWgAwIBAgIJAO8E12S7/qEpMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
|
||||
BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEPMA0GA1UEBxMGRHVyaGFt
|
||||
MRAwDgYDVQQKEwdBbnNpYmxlMB4XDTE0MDMxODIyMDAyMloXDTI0MDMxNTIyMDAy
|
||||
MlowSTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYD
|
||||
VQQHEwZEdXJoYW0xEDAOBgNVBAoTB0Fuc2libGUwgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||
gY0AMIGJAoGBANtvpPq3IlNlRbCHhZAcP6WCzhc5RbsDqyh1zrkmLi0GwcQ3z/r9
|
||||
gaWfQBYhHpobK2Tiq11TfraHeNB3/VfNImjZcGpN8Fl3MWwu7LfVkJy3gNNnxkA1
|
||||
4Go0/LmIvRFHhbzgfuo9NFgjPmmab9eqXJceqZIlz2C8xA7EeG7ku0+vAgMBAAGj
|
||||
gaswgagwHQYDVR0OBBYEFPnN1nPRqNDXGlCqCvdZchRNi/FaMHkGA1UdIwRyMHCA
|
||||
FPnN1nPRqNDXGlCqCvdZchRNi/FaoU2kSzBJMQswCQYDVQQGEwJVUzEXMBUGA1UE
|
||||
CBMOTm9ydGggQ2Fyb2xpbmExDzANBgNVBAcTBkR1cmhhbTEQMA4GA1UEChMHQW5z
|
||||
aWJsZYIJAO8E12S7/qEpMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
|
||||
MUB80IR6knq9K/tY+hvPsZer6eFMzO3JGkRFBh2kn6JdMDnhYGX7AXVHGflrwNQH
|
||||
qFy+aenWXsC0ZvrikFxbQnX8GVtDADtVznxOi7XzFw7JOxdsVrpXgSN0eh0aMzvV
|
||||
zKPZsZ2miVGclicJHzm5q080b1p/sZtuKIEZk6vZqEg=
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
|
||||
class CustomHTTPSConnection(httplib.HTTPSConnection):
|
||||
def connect(self):
|
||||
"Connect to a host on a given (SSL) port."
|
||||
|
||||
if hasattr(self, 'source_address'):
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout, self.source_address)
|
||||
else:
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
self._tunnel()
|
||||
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, certfile=self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
|
||||
|
||||
class CustomHTTPSHandler(urllib2.HTTPSHandler):
|
||||
|
||||
def https_open(self, req):
|
||||
return self.do_open(CustomHTTPSConnection, req)
|
||||
|
||||
https_request = urllib2.AbstractHTTPHandler.do_request_
|
||||
|
||||
def generic_urlparse(parts):
|
||||
'''
|
||||
Returns a dictionary of url parts as parsed by urlparse,
|
||||
but accounts for the fact that older versions of that
|
||||
library do not support named attributes (ie. .netloc)
|
||||
'''
|
||||
generic_parts = dict()
|
||||
if hasattr(parts, 'netloc'):
|
||||
# urlparse is newer, just read the fields straight
|
||||
# from the parts object
|
||||
generic_parts['scheme'] = parts.scheme
|
||||
generic_parts['netloc'] = parts.netloc
|
||||
generic_parts['path'] = parts.path
|
||||
generic_parts['params'] = parts.params
|
||||
generic_parts['query'] = parts.query
|
||||
generic_parts['fragment'] = parts.fragment
|
||||
generic_parts['username'] = parts.username
|
||||
generic_parts['password'] = parts.password
|
||||
generic_parts['hostname'] = parts.hostname
|
||||
generic_parts['port'] = parts.port
|
||||
else:
|
||||
# we have to use indexes, and then parse out
|
||||
# the other parts not supported by indexing
|
||||
generic_parts['scheme'] = parts[0]
|
||||
generic_parts['netloc'] = parts[1]
|
||||
generic_parts['path'] = parts[2]
|
||||
generic_parts['params'] = parts[3]
|
||||
generic_parts['query'] = parts[4]
|
||||
generic_parts['fragment'] = parts[5]
|
||||
# get the username, password, etc.
|
||||
try:
|
||||
netloc_re = re.compile(r'^((?:\w)+(?::(?:\w)+)?@)?([A-Za-z0-9.-]+)(:\d+)?$')
|
||||
(auth, hostname, port) = netloc_re.match(parts[1])
|
||||
if port:
|
||||
# the capture group for the port will include the ':',
|
||||
# so remove it and convert the port to an integer
|
||||
port = int(port[1:])
|
||||
if auth:
|
||||
# the capture group above inclues the @, so remove it
|
||||
# and then split it up based on the first ':' found
|
||||
auth = auth[:-1]
|
||||
username, password = auth.split(':', 1)
|
||||
generic_parts['username'] = username
|
||||
generic_parts['password'] = password
|
||||
generic_parts['hostname'] = hostnme
|
||||
generic_parts['port'] = port
|
||||
except:
|
||||
generic_parts['username'] = None
|
||||
generic_parts['password'] = None
|
||||
generic_parts['hostname'] = None
|
||||
generic_parts['port'] = None
|
||||
return generic_parts
|
||||
|
||||
class RequestWithMethod(urllib2.Request):
|
||||
'''
|
||||
Workaround for using DELETE/PUT/etc with urllib2
|
||||
Originally contained in library/net_infrastructure/dnsmadeeasy
|
||||
'''
|
||||
|
||||
def __init__(self, url, method, data=None, headers={}):
|
||||
self._method = method
|
||||
urllib2.Request.__init__(self, url, data, headers)
|
||||
|
||||
def get_method(self):
|
||||
if self._method:
|
||||
return self._method
|
||||
else:
|
||||
return urllib2.Request.get_method(self)
|
||||
|
||||
|
||||
class SSLValidationHandler(urllib2.BaseHandler):
|
||||
'''
|
||||
A custom handler class for SSL validation.
|
||||
|
||||
Based on:
|
||||
http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-python
|
||||
http://techknack.net/python-urllib2-handlers/
|
||||
'''
|
||||
CONNECT_COMMAND = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n"
|
||||
|
||||
def __init__(self, module, hostname, port):
|
||||
self.module = module
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
|
||||
def get_ca_certs(self):
|
||||
# tries to find a valid CA cert in one of the
|
||||
# standard locations for the current distribution
|
||||
|
||||
ca_certs = []
|
||||
paths_checked = []
|
||||
platform = get_platform()
|
||||
distribution = get_distribution()
|
||||
|
||||
# build a list of paths to check for .crt/.pem files
|
||||
# based on the platform type
|
||||
paths_checked.append('/etc/ssl/certs')
|
||||
if platform == 'Linux':
|
||||
paths_checked.append('/etc/pki/ca-trust/extracted/pem')
|
||||
paths_checked.append('/etc/pki/tls/certs')
|
||||
paths_checked.append('/usr/share/ca-certificates/cacert.org')
|
||||
elif platform == 'FreeBSD':
|
||||
paths_checked.append('/usr/local/share/certs')
|
||||
elif platform == 'OpenBSD':
|
||||
paths_checked.append('/etc/ssl')
|
||||
elif platform == 'NetBSD':
|
||||
ca_certs.append('/etc/openssl/certs')
|
||||
elif platform == 'SunOS':
|
||||
paths_checked.append('/opt/local/etc/openssl/certs')
|
||||
|
||||
# fall back to a user-deployed cert in a standard
|
||||
# location if the OS platform one is not available
|
||||
paths_checked.append('/etc/ansible')
|
||||
|
||||
tmp_fd, tmp_path = tempfile.mkstemp()
|
||||
|
||||
# Write the dummy ca cert if we are running on Mac OS X
|
||||
if platform == 'Darwin':
|
||||
os.write(tmp_fd, DUMMY_CA_CERT)
|
||||
# Default Homebrew path for OpenSSL certs
|
||||
paths_checked.append('/usr/local/etc/openssl')
|
||||
|
||||
# for all of the paths, find any .crt or .pem files
|
||||
# and compile them into single temp file for use
|
||||
# in the ssl check to speed up the test
|
||||
for path in paths_checked:
|
||||
if os.path.exists(path) and os.path.isdir(path):
|
||||
dir_contents = os.listdir(path)
|
||||
for f in dir_contents:
|
||||
full_path = os.path.join(path, f)
|
||||
if os.path.isfile(full_path) and os.path.splitext(f)[1] in ('.crt','.pem'):
|
||||
try:
|
||||
cert_file = open(full_path, 'r')
|
||||
os.write(tmp_fd, cert_file.read())
|
||||
os.write(tmp_fd, '\n')
|
||||
cert_file.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
return (tmp_path, paths_checked)
|
||||
|
||||
def validate_proxy_response(self, response, valid_codes=[200]):
|
||||
'''
|
||||
make sure we get back a valid code from the proxy
|
||||
'''
|
||||
try:
|
||||
(http_version, resp_code, msg) = re.match(r'(HTTP/\d\.\d) (\d\d\d) (.*)', response).groups()
|
||||
if int(resp_code) not in valid_codes:
|
||||
raise Exception
|
||||
except:
|
||||
self.module.fail_json(msg='Connection to proxy failed')
|
||||
|
||||
def http_request(self, req):
|
||||
tmp_ca_cert_path, paths_checked = self.get_ca_certs()
|
||||
https_proxy = os.environ.get('https_proxy')
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
if https_proxy:
|
||||
proxy_parts = generic_urlparse(urlparse.urlparse(https_proxy))
|
||||
s.connect((proxy_parts.get('hostname'), proxy_parts.get('port')))
|
||||
if proxy_parts.get('scheme') == 'http':
|
||||
s.sendall(self.CONNECT_COMMAND % (self.hostname, self.port))
|
||||
if proxy_parts.get('username'):
|
||||
credentials = "%s:%s" % (proxy_parts.get('username',''), proxy_parts.get('password',''))
|
||||
s.sendall('Proxy-Authorization: Basic %s\r\n' % credentials.encode('base64').strip())
|
||||
s.sendall('\r\n')
|
||||
connect_result = s.recv(4096)
|
||||
self.validate_proxy_response(connect_result)
|
||||
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED)
|
||||
else:
|
||||
self.module.fail_json(msg='Unsupported proxy scheme: %s. Currently ansible only supports HTTP proxies.' % proxy_parts.get('scheme'))
|
||||
else:
|
||||
s.connect((self.hostname, self.port))
|
||||
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED)
|
||||
# close the ssl connection
|
||||
#ssl_s.unwrap()
|
||||
s.close()
|
||||
except (ssl.SSLError, socket.error), e:
|
||||
# fail if we tried all of the certs but none worked
|
||||
if 'connection refused' in str(e).lower():
|
||||
self.module.fail_json(msg='Failed to connect to %s:%s.' % (self.hostname, self.port))
|
||||
else:
|
||||
self.module.fail_json(
|
||||
msg='Failed to validate the SSL certificate for %s:%s. ' % (self.hostname, self.port) + \
|
||||
'Use validate_certs=no or make sure your managed systems have a valid CA certificate installed. ' + \
|
||||
'Paths checked for this platform: %s' % ", ".join(paths_checked)
|
||||
)
|
||||
try:
|
||||
# cleanup the temp file created, don't worry
|
||||
# if it fails for some reason
|
||||
os.remove(tmp_ca_cert_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
return req
|
||||
|
||||
https_request = http_request
|
||||
|
||||
|
||||
def url_argument_spec():
|
||||
'''
|
||||
Creates an argument spec that can be used with any module
|
||||
that will be requesting content via urllib/urllib2
|
||||
'''
|
||||
return dict(
|
||||
url = dict(),
|
||||
force = dict(default='no', aliases=['thirsty'], type='bool'),
|
||||
http_agent = dict(default='ansible-httpget'),
|
||||
use_proxy = dict(default='yes', type='bool'),
|
||||
validate_certs = dict(default='yes', type='bool'),
|
||||
url_username = dict(required=False),
|
||||
url_password = dict(required=False),
|
||||
)
|
||||
|
||||
|
||||
def fetch_url(module, url, data=None, headers=None, method=None,
|
||||
use_proxy=True, force=False, last_mod_time=None, timeout=10):
|
||||
'''
|
||||
Fetches a file from an HTTP/FTP server using urllib2
|
||||
'''
|
||||
|
||||
if not HAS_URLLIB:
|
||||
module.fail_json(msg='urllib is not installed')
|
||||
if not HAS_URLLIB2:
|
||||
module.fail_json(msg='urllib2 is not installed')
|
||||
elif not HAS_URLPARSE:
|
||||
module.fail_json(msg='urlparse is not installed')
|
||||
|
||||
r = None
|
||||
handlers = []
|
||||
info = dict(url=url)
|
||||
|
||||
distribution = get_distribution()
|
||||
# Get validate_certs from the module params
|
||||
validate_certs = module.params.get('validate_certs', True)
|
||||
|
||||
# FIXME: change the following to use the generic_urlparse function
|
||||
# to remove the indexed references for 'parsed'
|
||||
parsed = urlparse.urlparse(url)
|
||||
if parsed[0] == 'https':
|
||||
if not HAS_SSL and validate_certs:
|
||||
if distribution == 'Redhat':
|
||||
module.fail_json(msg='SSL validation is not available in your version of python. You can use validate_certs=no, however this is unsafe and not recommended. You can also install python-ssl from EPEL')
|
||||
else:
|
||||
module.fail_json(msg='SSL validation is not available in your version of python. You can use validate_certs=no, however this is unsafe and not recommended')
|
||||
|
||||
elif validate_certs:
|
||||
# do the cert validation
|
||||
netloc = parsed[1]
|
||||
if '@' in netloc:
|
||||
netloc = netloc.split('@', 1)[1]
|
||||
if ':' in netloc:
|
||||
hostname, port = netloc.split(':', 1)
|
||||
else:
|
||||
hostname = netloc
|
||||
port = 443
|
||||
# create the SSL validation handler and
|
||||
# add it to the list of handlers
|
||||
ssl_handler = SSLValidationHandler(module, hostname, port)
|
||||
handlers.append(ssl_handler)
|
||||
|
||||
if parsed[0] != 'ftp':
|
||||
username = module.params.get('url_username', '')
|
||||
if username:
|
||||
password = module.params.get('url_password', '')
|
||||
netloc = parsed[1]
|
||||
elif '@' in parsed[1]:
|
||||
credentials, netloc = parsed[1].split('@', 1)
|
||||
if ':' in credentials:
|
||||
username, password = credentials.split(':', 1)
|
||||
else:
|
||||
username = credentials
|
||||
password = ''
|
||||
|
||||
parsed = list(parsed)
|
||||
parsed[1] = netloc
|
||||
|
||||
# reconstruct url without credentials
|
||||
url = urlparse.urlunparse(parsed)
|
||||
|
||||
if username:
|
||||
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
|
||||
# this creates a password manager
|
||||
passman.add_password(None, netloc, username, password)
|
||||
|
||||
# because we have put None at the start it will always
|
||||
# use this username/password combination for urls
|
||||
# for which `theurl` is a super-url
|
||||
authhandler = urllib2.HTTPBasicAuthHandler(passman)
|
||||
|
||||
# create the AuthHandler
|
||||
handlers.append(authhandler)
|
||||
|
||||
if not use_proxy:
|
||||
proxyhandler = urllib2.ProxyHandler({})
|
||||
handlers.append(proxyhandler)
|
||||
|
||||
# pre-2.6 versions of python cannot use the custom https
|
||||
# handler, since the socket class is lacking this method
|
||||
if hasattr(socket, 'create_connection'):
|
||||
handlers.append(CustomHTTPSHandler)
|
||||
|
||||
opener = urllib2.build_opener(*handlers)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
if method:
|
||||
if method.upper() not in ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT'):
|
||||
module.fail_json(msg='invalid HTTP request method; %s' % method.upper())
|
||||
request = RequestWithMethod(url, method.upper(), data)
|
||||
else:
|
||||
request = urllib2.Request(url, data)
|
||||
|
||||
# add the custom agent header, to help prevent issues
|
||||
# with sites that block the default urllib agent string
|
||||
request.add_header('User-agent', module.params.get('http_agent'))
|
||||
|
||||
# if we're ok with getting a 304, set the timestamp in the
|
||||
# header, otherwise make sure we don't get a cached copy
|
||||
if last_mod_time and not force:
|
||||
tstamp = last_mod_time.strftime('%a, %d %b %Y %H:%M:%S +0000')
|
||||
request.add_header('If-Modified-Since', tstamp)
|
||||
else:
|
||||
request.add_header('cache-control', 'no-cache')
|
||||
|
||||
# user defined headers now, which may override things we've set above
|
||||
if headers:
|
||||
if not isinstance(headers, dict):
|
||||
module.fail_json("headers provided to fetch_url() must be a dict")
|
||||
for header in headers:
|
||||
request.add_header(header, headers[header])
|
||||
|
||||
try:
|
||||
if sys.version_info < (2,6,0):
|
||||
# urlopen in python prior to 2.6.0 did not
|
||||
# have a timeout parameter
|
||||
r = urllib2.urlopen(request, None)
|
||||
else:
|
||||
r = urllib2.urlopen(request, None, timeout)
|
||||
info.update(r.info())
|
||||
info['url'] = r.geturl() # The URL goes in too, because of redirects.
|
||||
info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), status=200))
|
||||
except urllib2.HTTPError, e:
|
||||
info.update(dict(msg=str(e), status=e.code))
|
||||
except urllib2.URLError, e:
|
||||
code = int(getattr(e, 'code', -1))
|
||||
info.update(dict(msg="Request failed: %s" % str(e), status=code))
|
||||
except socket.error, e:
|
||||
info.update(dict(msg="Connection failure: %s" % str(e), status=-1))
|
||||
except Exception, e:
|
||||
info.update(dict(msg="An unknown error occurred: %s" % str(e), status=-1))
|
||||
|
||||
return r, info
|
||||
|
||||
Reference in New Issue
Block a user