diff --git a/lib/ansible/module_utils/gcdns.py b/lib/ansible/module_utils/gcdns.py new file mode 100644 index 00000000000..7227e9be66f --- /dev/null +++ b/lib/ansible/module_utils/gcdns.py @@ -0,0 +1,52 @@ +# 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 , 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. +# + +from ansible.module_utils.gcp import gcp_connect +from ansible.module_utils.gcp import unexpected_error_msg as gcp_error + +try: + from libcloud.dns.types import Provider + from libcloud.dns.providers import get_driver + HAS_LIBCLOUD_BASE = True +except ImportError: + HAS_LIBCLOUD_BASE = False + +USER_AGENT_PRODUCT = "Ansible-gcdns" +USER_AGENT_VERSION = "v1" + +def gcdns_connect(module, provider=Provider.GOOGLE): + """Return a GCP connection for Google Cloud DNS.""" + if not HAS_LIBCLOUD_BASE: + module.fail_json(msg='libcloud must be installed to use this module') + + return gcp_connect(module, provider, get_driver, USER_AGENT_PRODUCT, USER_AGENT_VERSION) + +def unexpected_error_msg(error): + """Create an error string based on passed in error.""" + return gcp_error(error) diff --git a/lib/ansible/module_utils/gce.py b/lib/ansible/module_utils/gce.py index 0008681f1e4..85717bb5c5d 100644 --- a/lib/ansible/module_utils/gce.py +++ b/lib/ansible/module_utils/gce.py @@ -27,100 +27,26 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -import json -import os -import traceback -from distutils.version import LooseVersion +from ansible.module_utils.gcp import gcp_connect +from ansible.module_utils.gcp import unexpected_error_msg as gcp_error try: from libcloud.compute.types import Provider - import libcloud from libcloud.compute.providers import get_driver HAS_LIBCLOUD_BASE = True except ImportError: HAS_LIBCLOUD_BASE = False -USER_AGENT_PRODUCT="Ansible-gce" -USER_AGENT_VERSION="v1" +USER_AGENT_PRODUCT = "Ansible-gce" +USER_AGENT_VERSION = "v1" -def gce_connect(module, provider=None): - """Return a Google Cloud Engine connection.""" +def gce_connect(module, provider=Provider.GCE): + """Return a GCP connection for Google Compute Engine.""" if not HAS_LIBCLOUD_BASE: module.fail_json(msg='libcloud must be installed to use this module') - service_account_email = module.params.get('service_account_email', None) - credentials_file = module.params.get('credentials_file', 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 not credentials_file: - credentials_file = os.environ.get('GCE_CREDENTIALS_FILE_PATH', pem_file) - - # 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 credentials_file: - credentials_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 credentials_file is None or project_id is None: - module.fail_json(msg='Missing GCE connection parameters in libcloud ' - 'secrets file.') - return None - else: - # We have credentials but lets make sure that if they are JSON we have the minimum - # libcloud requirement met - try: - # Try to read credentials as JSON - with open(credentials_file) as credentials: - json.loads(credentials.read()) - # If the credentials are proper JSON and we do not have the minimum - # required libcloud version, bail out and return a descriptive error - if LooseVersion(libcloud.__version__) < '0.17.0': - module.fail_json(msg='Using JSON credentials but libcloud minimum version not met. ' - 'Upgrade to libcloud>=0.17.0.') - return None - except ValueError as e: - # Not JSON - pass - - # Allow for passing in libcloud Google DNS (e.g, Provider.GOOGLE) - if provider is None: - provider = Provider.GCE - - try: - gce = get_driver(provider)(service_account_email, credentials_file, - datacenter=module.params.get('zone', None), - project=project_id) - gce.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) - except (RuntimeError, ValueError) as e: - module.fail_json(msg=str(e), changed=False) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - return gce + return gcp_connect(module, provider, get_driver, USER_AGENT_PRODUCT, USER_AGENT_VERSION) def unexpected_error_msg(error): """Create an error string based on passed in error.""" - return 'Unexpected response: (%s). Detail: %s' % (str(error), traceback.format_exc(error)) + return gcp_error(error) diff --git a/lib/ansible/module_utils/gcp.py b/lib/ansible/module_utils/gcp.py new file mode 100644 index 00000000000..f097704486d --- /dev/null +++ b/lib/ansible/module_utils/gcp.py @@ -0,0 +1,117 @@ +# 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 , 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 json +import os +import traceback +from distutils.version import LooseVersion + +try: + import libcloud + HAS_LIBCLOUD_BASE = True +except ImportError: + HAS_LIBCLOUD_BASE = False + +def gcp_connect(module, provider, get_driver, user_agent_product, user_agent_version): + """Return a Google Cloud Platform connection.""" + if not HAS_LIBCLOUD_BASE: + module.fail_json(msg='libcloud must be installed to use this module') + + service_account_email = module.params.get('service_account_email', None) + credentials_file = module.params.get('credentials_file', 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 not credentials_file: + credentials_file = os.environ.get('GCE_CREDENTIALS_FILE_PATH', pem_file) + + # 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 credentials_file: + credentials_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 credentials_file is None or project_id is None: + module.fail_json(msg='Missing GCE connection parameters in libcloud ' + 'secrets file.') + return None + else: + # We have credentials but lets make sure that if they are JSON we have the minimum + # libcloud requirement met + try: + # Try to read credentials as JSON + with open(credentials_file) as credentials: + json.loads(credentials.read()) + # If the credentials are proper JSON and we do not have the minimum + # required libcloud version, bail out and return a descriptive error + if LooseVersion(libcloud.__version__) < '0.17.0': + module.fail_json(msg='Using JSON credentials but libcloud minimum version not met. ' + 'Upgrade to libcloud>=0.17.0.') + return None + except ValueError as e: + # Not JSON + pass + + try: + gcp = get_driver(provider)(service_account_email, credentials_file, + datacenter=module.params.get('zone', None), + project=project_id) + gcp.connection.user_agent_append("%s/%s" % ( + user_agent_product, user_agent_version)) + except (RuntimeError, ValueError) as e: + module.fail_json(msg=str(e), changed=False) + except Exception as e: + module.fail_json(msg=unexpected_error_msg(e), changed=False) + + return gcp + +def unexpected_error_msg(error): + """Create an error string based on passed in error.""" + return 'Unexpected response: (%s). Detail: %s' % (str(error), traceback.format_exc(error)) diff --git a/test/utils/run_tests.sh b/test/utils/run_tests.sh index 88c30122952..e8996b0ddab 100755 --- a/test/utils/run_tests.sh +++ b/test/utils/run_tests.sh @@ -22,7 +22,7 @@ if [ "${TARGET}" = "sanity" ]; then ./test/code-smell/boilerplate.sh ./test/code-smell/required-and-default-attributes.sh if test x"$TOXENV" != x'py24' ; then tox ; fi - if test x"$TOXENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce|docker_common|azure_rm_common|vca|vmware).py' lib/ansible/module_utils ; fi + if test x"$TOXENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce|docker_common|azure_rm_common|vca|vmware|gcp|gcdns).py' lib/ansible/module_utils ; fi else if [ ! -e /tmp/cid_httptester ]; then docker pull ansible/ansible:httptester diff --git a/test/utils/shippable/sanity.sh b/test/utils/shippable/sanity.sh index 46bc367a240..0d16c7ee23c 100755 --- a/test/utils/shippable/sanity.sh +++ b/test/utils/shippable/sanity.sh @@ -12,7 +12,7 @@ if [ "${TOXENV}" = 'py24' ]; then fi python2.4 -V - python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce|docker_common|azure_rm_common|vca|vmware).py' lib/ansible/module_utils + python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce|docker_common|azure_rm_common|vca|vmware|gcp|gcdns).py' lib/ansible/module_utils else if [ "${install_deps}" != "" ]; then pip install -r "${source_root}/test/utils/shippable/sanity-requirements.txt" --upgrade