diff --git a/ansible_mitogen/planner.py b/ansible_mitogen/planner.py index 460e5be4..d070daeb 100644 --- a/ansible_mitogen/planner.py +++ b/ansible_mitogen/planner.py @@ -96,6 +96,9 @@ class Invocation(object): #: Initially ``None``, but set by :func:`invoke`. The raw source or #: binary contents of the module. self._module_source = None + #: Initially ``{}``, but set by :func:`invoke`. Optional source to send + #: to :func:`propagate_paths_and_modules` to fix Python3.5 relative import errors + self._overridden_sources = {} def get_module_source(self): if self._module_source is None: @@ -476,6 +479,7 @@ def _propagate_deps(invocation, planner, context): context=context, paths=planner.get_push_files(), modules=planner.get_module_deps(), + overridden_sources=invocation._overridden_sources ) @@ -533,6 +537,26 @@ def _get_planner(name, path, source): raise ansible.errors.AnsibleError(NO_METHOD_MSG + repr(invocation)) +def _fix_py35(invocation, module_source): + """ + super edge case with a relative import error in Python 3.5.1-3.5.3 + in Ansible's setup module when using Mitogen + https://github.com/dw/mitogen/issues/672#issuecomment-636408833 + We replace a relative import in the setup module with the actual full file path + This works in vanilla Ansible but not in Mitogen otherwise + """ + if invocation.module_name == 'setup' and \ + invocation.module_path not in invocation._overridden_sources: + # in-memory replacement of setup module's relative import + # would check for just python3.5 and run this then but we don't know the + # target python at this time yet + module_source = module_source.replace( + b"from ...module_utils.basic import AnsibleModule", + b"from ansible.module_utils.basic import AnsibleModule" + ) + invocation._overridden_sources[invocation.module_path] = module_source + + def invoke(invocation): """ Find a Planner subclass corresponding to `invocation` and use it to invoke @@ -555,10 +579,12 @@ def invoke(invocation): invocation.module_path = mitogen.core.to_text(path) if invocation.module_path not in _planner_by_path: + module_source = invocation.get_module_source() + _fix_py35(invocation, module_source) _planner_by_path[invocation.module_path] = _get_planner( invocation.module_name, invocation.module_path, - invocation.get_module_source() + module_source ) planner = _planner_by_path[invocation.module_path](invocation) diff --git a/mitogen/service.py b/mitogen/service.py index 3b244414..69376a80 100644 --- a/mitogen/service.py +++ b/mitogen/service.py @@ -746,13 +746,18 @@ class PushFileService(Service): 'paths': list, 'modules': list, }) - def propagate_paths_and_modules(self, context, paths, modules): + def propagate_paths_and_modules(self, context, paths, modules, overridden_sources=None): """ One size fits all method to ensure a target context has been preloaded with a set of small files and Python modules. + + overridden_sources: optional dict containing source code to override path's source code """ for path in paths: - self.propagate_to(context, mitogen.core.to_text(path)) + overridden_source = None + if overridden_sources is not None and path in overridden_sources: + overridden_source = overridden_sources[path] + self.propagate_to(context, mitogen.core.to_text(path), overridden_source) #self.router.responder.forward_modules(context, modules) TODO @expose(policy=AllowParents()) @@ -760,14 +765,22 @@ class PushFileService(Service): 'context': mitogen.core.Context, 'path': mitogen.core.FsPathTypes, }) - def propagate_to(self, context, path): + def propagate_to(self, context, path, overridden_source=None): + """ + If the optional parameter 'overridden_source' is passed, use + that instead of the path's code as source code. This works around some bugs + of source modules such as relative imports on unsupported Python versions + """ if path not in self._cache: LOG.debug('caching small file %s', path) - fp = open(path, 'rb') - try: - self._cache[path] = mitogen.core.Blob(fp.read()) - finally: - fp.close() + if overridden_source is None: + fp = open(path, 'rb') + try: + self._cache[path] = mitogen.core.Blob(fp.read()) + finally: + fp.close() + else: + self._cache[path] = mitogen.core.Blob(overridden_source) self._forward(context, path) @expose(policy=AllowParents())