@ -35,8 +35,10 @@ files/modules known missing.
"""
"""
from __future__ import absolute_import
from __future__ import absolute_import
import json
import logging
import logging
import os
import os
import random
from ansible . executor import module_common
from ansible . executor import module_common
import ansible . errors
import ansible . errors
@ -132,20 +134,36 @@ class Planner(object):
file , indicates whether or not it understands how to run the module , and
file , indicates whether or not it understands how to run the module , and
exports a method to run the module .
exports a method to run the module .
"""
"""
def detect ( self , invocation ) :
def __init__ ( self , invocation ) :
self . _inv = invocation
def detect ( self ) :
"""
"""
Return true if the supplied ` invocation ` matches the module type
Return true if the supplied ` invocation ` matches the module type
implemented by this planner .
implemented by this planner .
"""
"""
raise NotImplementedError ( )
raise NotImplementedError ( )
def get_ should_fork( self , invocation ) :
def should_fork( self ) :
"""
"""
Asynchronous tasks must always be forked .
Asynchronous tasks must always be forked .
"""
"""
return invocation . wrap_async
return self . _inv . wrap_async
def get_push_files ( self ) :
"""
Return a list of files that should be propagated to the target context
using PushFileService . The default implementation pushes nothing .
"""
return [ ]
def get_module_deps ( self ) :
"""
Return a list of the Python module names imported by the module .
"""
return [ ]
def plan ( self , invocation , * * kwargs ) :
def get_kwargs( self , * * kwargs ) :
"""
"""
If : meth : ` detect ` returned : data : ` True ` , plan for the module ' s
If : meth : ` detect ` returned : data : ` True ` , plan for the module ' s
execution , including granting access to or delivering any files to it
execution , including granting access to or delivering any files to it
@ -161,9 +179,7 @@ class Planner(object):
}
}
"""
"""
kwargs . setdefault ( ' emulate_tty ' , True )
kwargs . setdefault ( ' emulate_tty ' , True )
kwargs . setdefault ( ' service_context ' , invocation . connection . parent )
kwargs . setdefault ( ' service_context ' , self . _inv . connection . parent )
kwargs . setdefault ( ' should_fork ' , self . get_should_fork ( invocation ) )
kwargs . setdefault ( ' wrap_async ' , invocation . wrap_async )
return kwargs
return kwargs
def __repr__ ( self ) :
def __repr__ ( self ) :
@ -177,26 +193,19 @@ class BinaryPlanner(Planner):
"""
"""
runner_name = ' BinaryRunner '
runner_name = ' BinaryRunner '
def detect ( self , invocation ):
def detect ( self ):
return module_common . _is_binary ( invocation . module_source )
return module_common . _is_binary ( self . _ inv. module_source )
def _grant_file_service_access ( self , invocation ) :
def get_push_files ( self ) :
invocation . connection . parent . call_service (
return [ self . _inv . module_path ]
service_name = ' mitogen.service.PushFileService ' ,
method_name = ' propagate_to ' ,
path = invocation . module_path ,
context = invocation . connection . context ,
)
def plan ( self , invocation , * * kwargs ) :
def get_kwargs ( self , * * kwargs ) :
self . _grant_file_service_access ( invocation )
return super ( BinaryPlanner , self ) . get_kwargs (
return super ( BinaryPlanner , self ) . plan (
invocation = invocation ,
runner_name = self . runner_name ,
runner_name = self . runner_name ,
module = invocation . module_name ,
module = self . _inv . module_name ,
path = invocation . module_path ,
path = self . _ inv. module_path ,
args = invocation . module_args ,
args = self . _ inv. module_args ,
env = invocation . env ,
env = self . _ inv. env ,
* * kwargs
* * kwargs
)
)
@ -206,24 +215,25 @@ class ScriptPlanner(BinaryPlanner):
Common functionality for script module planners - - handle interpreter
Common functionality for script module planners - - handle interpreter
detection and rewrite .
detection and rewrite .
"""
"""
def _get_interpreter ( self , invocation ) :
def _get_interpreter ( self ) :
interpreter , arg = parse_script_interpreter ( invocation . module_source )
interpreter , arg = parse_script_interpreter (
self . _inv . module_source
)
if interpreter is None :
if interpreter is None :
raise ansible . errors . AnsibleError ( NO_INTERPRETER_MSG % (
raise ansible . errors . AnsibleError ( NO_INTERPRETER_MSG % (
invocation . module_name ,
self . _ inv. module_name ,
) )
) )
key = u ' ansible_ %s _interpreter ' % os . path . basename ( interpreter ) . strip ( )
key = u ' ansible_ %s _interpreter ' % os . path . basename ( interpreter ) . strip ( )
try :
try :
template = invocation . task_vars [ key ] . strip ( )
template = self . _ inv. task_vars [ key ] . strip ( )
return invocation . templar . template ( template ) , arg
return self . _ inv. templar . template ( template ) , arg
except KeyError :
except KeyError :
return interpreter , arg
return interpreter , arg
def plan ( self , invocation , * * kwargs ) :
def get_kwargs ( self , * * kwargs ) :
interpreter , arg = self . _get_interpreter ( invocation )
interpreter , arg = self . _get_interpreter ( )
return super ( ScriptPlanner , self ) . plan (
return super ( ScriptPlanner , self ) . get_kwargs (
invocation = invocation ,
interpreter_arg = arg ,
interpreter_arg = arg ,
interpreter = interpreter ,
interpreter = interpreter ,
* * kwargs
* * kwargs
@ -237,8 +247,8 @@ class JsonArgsPlanner(ScriptPlanner):
"""
"""
runner_name = ' JsonArgsRunner '
runner_name = ' JsonArgsRunner '
def detect ( self , invocation ):
def detect ( self ):
return module_common . REPLACER_JSONARGS in invocation . module_source
return module_common . REPLACER_JSONARGS in self . _ inv. module_source
class WantJsonPlanner ( ScriptPlanner ) :
class WantJsonPlanner ( ScriptPlanner ) :
@ -255,8 +265,8 @@ class WantJsonPlanner(ScriptPlanner):
"""
"""
runner_name = ' WantJsonRunner '
runner_name = ' WantJsonRunner '
def detect ( self , invocation ):
def detect ( self ):
return ' WANT_JSON ' in invocation . module_source
return ' WANT_JSON ' in self . _ inv. module_source
class NewStylePlanner ( ScriptPlanner ) :
class NewStylePlanner ( ScriptPlanner ) :
@ -267,56 +277,59 @@ class NewStylePlanner(ScriptPlanner):
"""
"""
runner_name = ' NewStyleRunner '
runner_name = ' NewStyleRunner '
def _get_interpreter ( self , invocation ) :
def detect ( self ) :
return ' from ansible.module_utils. ' in self . _inv . module_source
def _get_interpreter ( self ) :
return None , None
return None , None
def _grant_file_service_access ( self , invocation ) :
def get_push_files( self ) :
"""
return super ( NewStylePlanner , self ) . get_push_files ( ) + [
Stub out BinaryPlanner ' s method since ModuleDepService makes internal
path
calls to grant file access , avoiding 2 IPCs per task invocation .
for fullname , path , is_pkg in self . get_module_map ( ) [ ' custom ' ]
"""
]
def get_should_fork ( self , invocation ) :
def get_module_deps ( self ) :
return self . get_module_map ( ) [ ' builtin ' ]
def should_fork ( self ) :
"""
"""
In addition to asynchronous tasks , new - style modules should be forked
In addition to asynchronous tasks , new - style modules should be forked
if mitogen_task_isolation = fork .
if the user specifies mitogen_task_isolation = fork , or if the new - style
module has a custom module search path .
"""
"""
return (
return (
super ( NewStylePlanner , self ) . get_should_fork ( invocation ) or
super ( NewStylePlanner , self ) . should_fork ( ) or
( invocation . task_vars . get ( ' mitogen_task_isolation ' ) == ' fork ' )
( self . _inv . task_vars . get ( ' mitogen_task_isolation ' ) == ' fork ' ) or
( len ( self . get_module_map ( ) [ ' custom ' ] ) > 0 )
)
)
def detect ( self , invocation ) :
def get_search_path ( self ) :
return ' from ansible.module_utils. ' in invocation . module_source
def get_search_path ( self , invocation ) :
return tuple (
return tuple (
path
path
for path in module_utils_loader . _get_paths ( subdirs = False )
for path in module_utils_loader . _get_paths ( subdirs = False )
if os . path . isdir ( path )
if os . path . isdir ( path )
)
)
def get_module_map ( self , invocation ) :
_module_map = None
return invocation . connection . parent . call_service (
service_name = ' ansible_mitogen.services.ModuleDepService ' ,
method_name = ' scan ' ,
module_name = ' ansible_module_ %s ' % ( invocation . module_name , ) ,
def get_module_map ( self ) :
module_path = invocation . module_path ,
if self . _module_map is None :
search_path = self . get_search_path ( invocation ) ,
self . _module_map = self . _inv . connection . parent . call_service (
builtin_path = module_common . _MODULE_UTILS_PATH ,
service_name = ' ansible_mitogen.services.ModuleDepService ' ,
context = invocation . connection . context ,
method_name = ' scan ' ,
)
def plan ( self , invocation ) :
module_name = ' ansible_module_ %s ' % ( self . _inv . module_name , ) ,
module_map = self . get_module_map ( invocation )
module_path = self . _inv . module_path ,
return super ( NewStylePlanner , self ) . plan (
search_path = self . get_search_path ( ) ,
invocation ,
builtin_path = module_common . _MODULE_UTILS_PATH ,
module_map = module_map ,
context = self . _inv . connection . context ,
should_fork = (
self . get_should_fork ( invocation ) or
len ( module_map [ ' custom ' ] ) > 0
)
)
return self . _module_map
def get_kwargs ( self ) :
return super ( NewStylePlanner , self ) . get_kwargs (
module_map = self . get_module_map ( ) ,
)
)
@ -346,14 +359,14 @@ class ReplacerPlanner(NewStylePlanner):
"""
"""
runner_name = ' ReplacerRunner '
runner_name = ' ReplacerRunner '
def detect ( self , invocation ):
def detect ( self ):
return module_common . REPLACER in invocation . module_source
return module_common . REPLACER in self . _ inv. module_source
class OldStylePlanner ( ScriptPlanner ) :
class OldStylePlanner ( ScriptPlanner ) :
runner_name = ' OldStyleRunner '
runner_name = ' OldStyleRunner '
def detect ( self , invocation ):
def detect ( self ):
# Everything else.
# Everything else.
return True
return True
@ -375,24 +388,84 @@ def get_module_data(name):
return path , source
return path , source
def invoke ( invocation ) :
def _propagate_deps ( invocation , planner , context ) :
"""
invocation . connection . parent . call_service (
Find a suitable Planner that knows how to run ` invocation ` .
service_name = ' mitogen.service.PushFileService ' ,
"""
method_name = ' propagate_paths_and_modules ' ,
( invocation . module_path ,
context = context ,
invocation . module_source ) = get_module_data ( invocation . module_name )
paths = planner . get_push_files ( ) ,
modules = planner . get_module_deps ( ) ,
)
def _invoke_async_task ( invocation , planner ) :
job_id = ' %016x ' % random . randint ( 0 , 2 * * 64 )
context = invocation . connection . create_fork_child ( )
_propagate_deps ( invocation , planner , context )
context . call_no_reply (
ansible_mitogen . target . run_module_async ,
job_id = job_id ,
kwargs = planner . get_kwargs ( ) ,
)
return {
' stdout ' : json . dumps ( {
# modules/utilities/logic/async_wrapper.py::_run_module().
' changed ' : True ,
' started ' : 1 ,
' finished ' : 0 ,
' ansible_job_id ' : job_id ,
} )
}
def _invoke_forked_task ( invocation , planner ) :
context = invocation . connection . create_fork_child ( )
_propagate_deps ( invocation , planner , context )
try :
return context . call (
ansible_mitogen . target . run_module ,
kwargs = planner . get_kwargs ( ) ,
)
finally :
context . shutdown ( )
def _get_planner ( invocation ) :
for klass in _planners :
for klass in _planners :
planner = klass ( )
planner = klass ( invocation )
if planner . detect ( invocation ) :
if planner . detect ( ) :
LOG . debug ( ' %r accepted %r (filename %r ) ' , planner ,
LOG . debug ( ' %r accepted %r (filename %r ) ' , planner ,
invocation . module_name , invocation . module_path )
invocation . module_name , invocation . module_path )
return invocation . action . _postprocess_response (
return planner
invocation . connection . call (
ansible_mitogen . target . run_module ,
planner . plan ( invocation ) ,
)
)
LOG . debug ( ' %r rejected %r ' , planner , invocation . module_name )
LOG . debug ( ' %r rejected %r ' , planner , invocation . module_name )
raise ansible . errors . AnsibleError ( NO_METHOD_MSG + repr ( invocation ) )
raise ansible . errors . AnsibleError ( NO_METHOD_MSG + repr ( invocation ) )
def invoke ( invocation ) :
"""
Find a Planner subclass corresnding to ` invocation ` and use it to invoke
the module .
: param Invocation invocation :
: returns :
Module return dict .
: raises ansible . errors . AnsibleError :
Unrecognized / unsupported module type .
"""
( invocation . module_path ,
invocation . module_source ) = get_module_data ( invocation . module_name )
planner = _get_planner ( invocation )
if invocation . wrap_async :
response = _invoke_async_task ( invocation , planner )
elif planner . should_fork ( ) :
response = _invoke_forked_task ( invocation , planner )
else :
_propagate_deps ( invocation , planner , invocation . connection . context )
response = invocation . connection . call (
ansible_mitogen . target . run_module ,
kwargs = planner . get_kwargs ( ) ,
)
return invocation . action . _postprocess_response ( response )