From 1e5708514b9d5530f883679e09057b9e37fca70f Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 28 Apr 2016 17:36:09 -0700 Subject: [PATCH] Fix ziploader for the cornercase of ansible invoking ansible. * Make ziploader's ansible and ansible.module_utils libraries into namespace packages. * Move __version__ and __author__ from ansible/__init__ to ansible/release.py. This is because namespace packages only load one __init__.py. If that is not the __init__.py with the author and version info then those won't be available. * In ziploader, move the version ito ANSIBLE_CONSTANTS. * Change PluginLoader to properly construct the path to the plugins even when namespace packages are present. --- lib/ansible/__init__.py | 9 +++++++-- lib/ansible/cli/__init__.py | 2 +- lib/ansible/executor/module_common.py | 7 ++++--- lib/ansible/module_utils/__init__.py | 3 +++ lib/ansible/module_utils/basic.py | 4 ---- lib/ansible/module_utils/rax.py | 3 +-- lib/ansible/plugins/__init__.py | 8 ++++---- lib/ansible/release.py | 23 +++++++++++++++++++++++ setup.py | 2 +- test/units/plugins/action/test_action.py | 2 +- test/units/plugins/test_plugins.py | 12 ++++++++---- 11 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 lib/ansible/release.py diff --git a/lib/ansible/__init__.py b/lib/ansible/__init__.py index b71c84790cf..d8aeac5e583 100644 --- a/lib/ansible/__init__.py +++ b/lib/ansible/__init__.py @@ -19,5 +19,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.1.0.0' -__author__ = 'Ansible, Inc.' +# Note: Do not add any code to this file. The ansible module may be +# a namespace package when using Ansible-2.1+ Anything in this file may not be +# available if one of the other packages in the namespace is loaded first. +# +# This is for backwards compat. Code should be ported to get these from +# ansible.release instead of from here. +from ansible.release import __version__, __author__ diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py index 5493d49b19b..00c29524e78 100644 --- a/lib/ansible/cli/__init__.py +++ b/lib/ansible/cli/__init__.py @@ -30,7 +30,7 @@ import getpass import signal import subprocess -from ansible import __version__ +from ansible.release import __version__ from ansible import constants as C from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.utils.unicode import to_bytes, to_unicode diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py index faf8035226c..42b6fccec05 100644 --- a/lib/ansible/executor/module_common.py +++ b/lib/ansible/executor/module_common.py @@ -30,7 +30,7 @@ import zipfile from io import BytesIO # from Ansible -from ansible import __version__ +from ansible.release import __version__, __author__ from ansible import constants as C from ansible.errors import AnsibleError from ansible.utils.unicode import to_bytes, to_unicode @@ -533,6 +533,7 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta constants = dict( SELINUX_SPECIAL_FS=C.DEFAULT_SELINUX_SPECIAL_FS, SYSLOG_FACILITY=_get_facility(task_vars), + ANSIBLE_VERSION=__version__, ) params = dict(ANSIBLE_MODULE_ARGS=module_args, ANSIBLE_MODULE_CONSTANTS=constants, @@ -562,8 +563,8 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta # Create the module zip data zipoutput = BytesIO() zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method) - zf.writestr('ansible/__init__.py', b''.join((b"__version__ = '", to_bytes(__version__), b"'\n"))) - zf.writestr('ansible/module_utils/__init__.py', b'') + zf.writestr('ansible/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\ntry:\n from ansible.release import __version__,__author__\nexcept ImportError:\n __version__="' + to_bytes(__version__) + b'"\n __author__="' + to_bytes(__author__) + b'"\n') + zf.writestr('ansible/module_utils/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\n') zf.writestr('ansible_module_%s.py' % module_name, module_data) diff --git a/lib/ansible/module_utils/__init__.py b/lib/ansible/module_utils/__init__.py index 266d06a6137..9cde27b5223 100644 --- a/lib/ansible/module_utils/__init__.py +++ b/lib/ansible/module_utils/__init__.py @@ -15,3 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# Note: Do not add any code to this file. module_utils may be a namespace +# package when using Ansible-2.1+ Anything in this file may not be available +# if one of the other packages in the namespace is loaded first. diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 7ae2cb941cc..10ae75ac808 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -219,10 +219,6 @@ except ImportError: _literal_eval = literal_eval -from ansible import __version__ -# Backwards compat. New code should just import and use __version__ -ANSIBLE_VERSION = __version__ - # Internal global holding passed in params and constants. This is consulted # in case multiple AnsibleModules are created. Otherwise each AnsibleModule # would attempt to read from stdin. Other code should not use this directly diff --git a/lib/ansible/module_utils/rax.py b/lib/ansible/module_utils/rax.py index d9eb74f071a..37ce66b40df 100644 --- a/lib/ansible/module_utils/rax.py +++ b/lib/ansible/module_utils/rax.py @@ -32,7 +32,6 @@ import os import re from uuid import UUID -from ansible import __version__ from ansible.module_utils.basic import BOOLEANS FINAL_STATUSES = ('ACTIVE', 'ERROR') @@ -264,7 +263,7 @@ def rax_required_together(): def setup_rax_module(module, rax_module, region_required=True): """Set up pyrax in a standard way for all modules""" - rax_module.USER_AGENT = 'ansible/%s %s' % (__version__, + rax_module.USER_AGENT = 'ansible/%s %s' % (module.constants['ANSIBLE_VERSION'], rax_module.USER_AGENT) api_key = module.params.get('api_key') diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index 3216a9235b2..e8558fef0a4 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -145,15 +145,15 @@ class PluginLoader: def _get_package_paths(self): ''' Gets the path of a Python package ''' - paths = [] if not self.package: return [] if not hasattr(self, 'package_path'): m = __import__(self.package) parts = self.package.split('.')[1:] - self.package_path = os.path.join(os.path.dirname(m.__file__), *parts) - paths.extend(self._all_directories(self.package_path)) - return paths + for parent_mod in parts: + m = getattr(m, parent_mod) + self.package_path = os.path.dirname(m.__file__) + return self._all_directories(self.package_path) def _get_paths(self): ''' Return a list of paths to search for plugins in ''' diff --git a/lib/ansible/release.py b/lib/ansible/release.py new file mode 100644 index 00000000000..a55a40daa01 --- /dev/null +++ b/lib/ansible/release.py @@ -0,0 +1,23 @@ +# (c) 2012-2014, Michael DeHaan +# +# 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +__version__ = '2.1.0' +__author__ = 'Ansible, Inc.' diff --git a/setup.py b/setup.py index b9678e2ab9b..df6f65c4533 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import os import sys sys.path.insert(0, os.path.abspath('lib')) -from ansible import __version__, __author__ +from ansible.release import __version__, __author__ try: from setuptools import setup, find_packages except ImportError: diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py index d0ea6ac789a..e9b608790f5 100644 --- a/test/units/plugins/action/test_action.py +++ b/test/units/plugins/action/test_action.py @@ -33,7 +33,7 @@ try: except ImportError: import __builtin__ as builtins -from ansible import __version__ as ansible_version +from ansible.release import __version__ as ansible_version from ansible import constants as C from ansible.compat.six import text_type from ansible.compat.tests import unittest diff --git a/test/units/plugins/test_plugins.py b/test/units/plugins/test_plugins.py index e8b48afcf1f..682a5ea8ce0 100644 --- a/test/units/plugins/test_plugins.py +++ b/test/units/plugins/test_plugins.py @@ -53,11 +53,15 @@ class TestErrors(unittest.TestCase): # python library, and then uses the __file__ attribute of # the result for that to get the library path, so we mock # that here and patch the builtin to use our mocked result - m = MagicMock() - m.return_value.__file__ = '/path/to/my/test.py' + foo = MagicMock() + bar = MagicMock() + bam = MagicMock() + bam.__file__ = '/path/to/my/foo/bar/bam/__init__.py' + bar.bam = bam + foo.return_value.bar = bar pl = PluginLoader('test', 'foo.bar.bam', 'test', 'test_plugin') - with patch('{0}.__import__'.format(BUILTINS), m): - self.assertEqual(pl._get_package_paths(), ['/path/to/my/bar/bam']) + with patch('{0}.__import__'.format(BUILTINS), foo): + self.assertEqual(pl._get_package_paths(), ['/path/to/my/foo/bar/bam']) def test_plugins__get_paths(self): pl = PluginLoader('test', '', 'test', 'test_plugin')