4545from ansible .executor import module_common
4646import ansible .errors
4747import ansible .module_utils
48+ import ansible .release
4849import mitogen .core
4950import mitogen .select
5051
5859NO_INTERPRETER_MSG = 'module (%s) is missing interpreter line'
5960NO_MODULE_MSG = 'The module %s was not found in configured module paths.'
6061
62+ _planner_by_path = {}
63+
6164
6265class Invocation (object ):
6366 """
@@ -92,7 +95,12 @@ def __init__(self, action, connection, module_name, module_args,
9295 self .module_path = None
9396 #: Initially ``None``, but set by :func:`invoke`. The raw source or
9497 #: binary contents of the module.
95- self .module_source = None
98+ self ._module_source = None
99+
100+ def get_module_source (self ):
101+ if self ._module_source is None :
102+ self ._module_source = read_file (self .module_path )
103+ return self ._module_source
96104
97105 def __repr__ (self ):
98106 return 'Invocation(module_name=%s)' % (self .module_name ,)
@@ -107,7 +115,8 @@ class Planner(object):
107115 def __init__ (self , invocation ):
108116 self ._inv = invocation
109117
110- def detect (self ):
118+ @classmethod
119+ def detect (cls , path , source ):
111120 """
112121 Return true if the supplied `invocation` matches the module type
113122 implemented by this planner.
@@ -171,8 +180,9 @@ class BinaryPlanner(Planner):
171180 """
172181 runner_name = 'BinaryRunner'
173182
174- def detect (self ):
175- return module_common ._is_binary (self ._inv .module_source )
183+ @classmethod
184+ def detect (cls , path , source ):
185+ return module_common ._is_binary (source )
176186
177187 def get_push_files (self ):
178188 return [mitogen .core .to_text (self ._inv .module_path )]
@@ -218,7 +228,7 @@ def _rewrite_interpreter(self, path):
218228
219229 def _get_interpreter (self ):
220230 path , arg = ansible_mitogen .parsing .parse_hashbang (
221- self ._inv .module_source
231+ self ._inv .get_module_source ()
222232 )
223233 if path is None :
224234 raise ansible .errors .AnsibleError (NO_INTERPRETER_MSG % (
@@ -247,8 +257,9 @@ class JsonArgsPlanner(ScriptPlanner):
247257 """
248258 runner_name = 'JsonArgsRunner'
249259
250- def detect (self ):
251- return module_common .REPLACER_JSONARGS in self ._inv .module_source
260+ @classmethod
261+ def detect (cls , path , source ):
262+ return module_common .REPLACER_JSONARGS in source
252263
253264
254265class WantJsonPlanner (ScriptPlanner ):
@@ -265,8 +276,9 @@ class WantJsonPlanner(ScriptPlanner):
265276 """
266277 runner_name = 'WantJsonRunner'
267278
268- def detect (self ):
269- return b'WANT_JSON' in self ._inv .module_source
279+ @classmethod
280+ def detect (cls , path , source ):
281+ return b'WANT_JSON' in source
270282
271283
272284class NewStylePlanner (ScriptPlanner ):
@@ -278,8 +290,9 @@ class NewStylePlanner(ScriptPlanner):
278290 runner_name = 'NewStyleRunner'
279291 marker = b'from ansible.module_utils.'
280292
281- def detect (self ):
282- return self .marker in self ._inv .module_source
293+ @classmethod
294+ def detect (cls , path , source ):
295+ return cls .marker in source
283296
284297 def _get_interpreter (self ):
285298 return None , None
@@ -323,7 +336,6 @@ def get_search_path(self):
323336 for path in ansible_mitogen .loaders .module_utils_loader ._get_paths (
324337 subdirs = False
325338 )
326- if os .path .isdir (path )
327339 )
328340
329341 _module_map = None
@@ -347,6 +359,10 @@ def get_module_map(self):
347359 def get_kwargs (self ):
348360 return super (NewStylePlanner , self ).get_kwargs (
349361 module_map = self .get_module_map (),
362+ py_module_name = py_modname_from_path (
363+ self ._inv .module_name ,
364+ self ._inv .module_path ,
365+ ),
350366 )
351367
352368
@@ -376,14 +392,16 @@ class ReplacerPlanner(NewStylePlanner):
376392 """
377393 runner_name = 'ReplacerRunner'
378394
379- def detect (self ):
380- return module_common .REPLACER in self ._inv .module_source
395+ @classmethod
396+ def detect (cls , path , source ):
397+ return module_common .REPLACER in source
381398
382399
383400class OldStylePlanner (ScriptPlanner ):
384401 runner_name = 'OldStyleRunner'
385402
386- def detect (self ):
403+ @classmethod
404+ def detect (cls , path , source ):
387405 # Everything else.
388406 return True
389407
@@ -398,14 +416,54 @@ def detect(self):
398416]
399417
400418
401- def get_module_data (name ):
402- path = ansible_mitogen .loaders .module_loader .find_plugin (name , '' )
403- if path is None :
404- raise ansible .errors .AnsibleError (NO_MODULE_MSG % (name ,))
419+ try :
420+ _get_ansible_module_fqn = module_common ._get_ansible_module_fqn
421+ except AttributeError :
422+ _get_ansible_module_fqn = None
423+
405424
406- with open (path , 'rb' ) as fp :
407- source = fp .read ()
408- return mitogen .core .to_text (path ), source
425+ def py_modname_from_path (name , path ):
426+ """
427+ Fetch the logical name of a new-style module as it might appear in
428+ :data:`sys.modules` of the target's Python interpreter.
429+
430+ * For Ansible <2.7, this is an unpackaged module named like
431+ "ansible_module_%s".
432+
433+ * For Ansible <2.9, this is an unpackaged module named like
434+ "ansible.modules.%s"
435+
436+ * Since Ansible 2.9, modules appearing within a package have the original
437+ package hierarchy approximated on the target, enabling relative imports
438+ to function correctly. For example, "ansible.modules.system.setup".
439+ """
440+ # 2.9+
441+ if _get_ansible_module_fqn :
442+ try :
443+ return _get_ansible_module_fqn (path )
444+ except ValueError :
445+ pass
446+
447+ if ansible .__version__ < '2.7' :
448+ return 'ansible_module_' + name
449+
450+ return 'ansible.modules.' + name
451+
452+
453+ def read_file (path ):
454+ fd = os .open (path , os .O_RDONLY )
455+ try :
456+ bits = []
457+ chunk = True
458+ while True :
459+ chunk = os .read (fd , 65536 )
460+ if not chunk :
461+ break
462+ bits .append (chunk )
463+ finally :
464+ os .close (fd )
465+
466+ return mitogen .core .b ('' ).join (bits )
409467
410468
411469def _propagate_deps (invocation , planner , context ):
@@ -466,14 +524,12 @@ def _invoke_isolated_task(invocation, planner):
466524 context .shutdown ()
467525
468526
469- def _get_planner (invocation ):
527+ def _get_planner (name , path , source ):
470528 for klass in _planners :
471- planner = klass (invocation )
472- if planner .detect ():
473- LOG .debug ('%r accepted %r (filename %r)' , planner ,
474- invocation .module_name , invocation .module_path )
475- return planner
476- LOG .debug ('%r rejected %r' , planner , invocation .module_name )
529+ if klass .detect (path , source ):
530+ LOG .debug ('%r accepted %r (filename %r)' , klass , name , path )
531+ return klass
532+ LOG .debug ('%r rejected %r' , klass , name )
477533 raise ansible .errors .AnsibleError (NO_METHOD_MSG + repr (invocation ))
478534
479535
@@ -488,10 +544,24 @@ def invoke(invocation):
488544 :raises ansible.errors.AnsibleError:
489545 Unrecognized/unsupported module type.
490546 """
491- (invocation .module_path ,
492- invocation .module_source ) = get_module_data (invocation .module_name )
493- planner = _get_planner (invocation )
547+ path = ansible_mitogen .loaders .module_loader .find_plugin (
548+ invocation .module_name ,
549+ '' ,
550+ )
551+ if path is None :
552+ raise ansible .errors .AnsibleError (NO_MODULE_MSG % (
553+ invocation .module_name ,
554+ ))
555+
556+ invocation .module_path = mitogen .core .to_text (path )
557+ if invocation .module_path not in _planner_by_path :
558+ _planner_by_path [invocation .module_path ] = _get_planner (
559+ invocation .module_name ,
560+ invocation .module_path ,
561+ invocation .get_module_source ()
562+ )
494563
564+ planner = _planner_by_path [invocation .module_path ](invocation )
495565 if invocation .wrap_async :
496566 response = _invoke_async_task (invocation , planner )
497567 elif planner .should_fork ():
0 commit comments