2014-01-04 19:31:44 +01:00
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2012-05-26 06:37:34 +02:00
#
# 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 <http://www.gnu.org/licenses/>.
2015-05-04 04:47:26 +02:00
# Make coding more python3-ish
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2015-09-03 08:23:27 +02:00
from six import iteritems , string_types
2015-08-14 06:33:36 +02:00
2015-05-04 04:47:26 +02:00
from ansible . errors import AnsibleError
from ansible . parsing . mod_args import ModuleArgsParser
from ansible . parsing . splitter import parse_kv
2015-08-14 06:33:36 +02:00
from ansible . parsing . yaml . objects import AnsibleBaseYAMLObject , AnsibleMapping , AnsibleUnicode
2015-05-04 04:47:26 +02:00
from ansible . plugins import module_loader , lookup_loader
from ansible . playbook . attribute import Attribute , FieldAttribute
from ansible . playbook . base import Base
from ansible . playbook . become import Become
from ansible . playbook . block import Block
from ansible . playbook . conditional import Conditional
from ansible . playbook . role import Role
from ansible . playbook . taggable import Taggable
__all__ = [ ' Task ' ]
2015-08-26 18:03:13 +02:00
try :
from __main__ import display
display = display
except ImportError :
from ansible . utils . display import Display
display = Display ( )
2015-05-04 04:47:26 +02:00
class Task ( Base , Conditional , Taggable , Become ) :
"""
A task is a language feature that represents a call to a module , with given arguments and other parameters .
A handler is a subclass of a task .
Usage :
Task . load ( datastructure ) - > Task
Task . something ( . . . )
"""
# =================================================================================
# ATTRIBUTES
# load_<attribute_name> and
# validate_<attribute_name>
# will be used if defined
# might be possible to define others
_args = FieldAttribute ( isa = ' dict ' , default = dict ( ) )
_action = FieldAttribute ( isa = ' string ' )
_always_run = FieldAttribute ( isa = ' bool ' )
_any_errors_fatal = FieldAttribute ( isa = ' bool ' )
_async = FieldAttribute ( isa = ' int ' , default = 0 )
_changed_when = FieldAttribute ( isa = ' string ' )
_delay = FieldAttribute ( isa = ' int ' , default = 5 )
_delegate_to = FieldAttribute ( isa = ' string ' )
_failed_when = FieldAttribute ( isa = ' string ' )
_first_available_file = FieldAttribute ( isa = ' list ' )
_ignore_errors = FieldAttribute ( isa = ' bool ' )
_loop = FieldAttribute ( isa = ' string ' , private = True )
_loop_args = FieldAttribute ( isa = ' list ' , private = True )
_local_action = FieldAttribute ( isa = ' string ' )
_name = FieldAttribute ( isa = ' string ' , default = ' ' )
_notify = FieldAttribute ( isa = ' list ' )
_poll = FieldAttribute ( isa = ' int ' )
_register = FieldAttribute ( isa = ' string ' )
_retries = FieldAttribute ( isa = ' int ' , default = 1 )
_run_once = FieldAttribute ( isa = ' bool ' )
_until = FieldAttribute ( isa = ' list ' ) # ?
def __init__ ( self , block = None , role = None , task_include = None ) :
''' constructors a task, without the Task.load classmethod, it will be pretty blank '''
self . _block = block
self . _role = role
self . _task_include = task_include
2015-09-14 18:02:45 +02:00
# special flag for local_action: tasks, to make sure their
# connection type of local isn't overridden incorrectly
self . _local_action = False
2015-05-04 04:47:26 +02:00
super ( Task , self ) . __init__ ( )
def get_name ( self ) :
''' return the name of the task '''
2015-07-23 21:45:36 +02:00
if self . _role and self . name :
2015-05-04 04:47:26 +02:00
return " %s : %s " % ( self . _role . get_name ( ) , self . name )
elif self . name :
return self . name
else :
flattened_args = self . _merge_kv ( self . args )
if self . _role :
return " %s : %s %s " % ( self . _role . get_name ( ) , self . action , flattened_args )
else :
return " %s %s " % ( self . action , flattened_args )
def _merge_kv ( self , ds ) :
if ds is None :
return " "
elif isinstance ( ds , basestring ) :
return ds
elif isinstance ( ds , dict ) :
buf = " "
2015-09-03 08:23:27 +02:00
for ( k , v ) in iteritems ( ds ) :
2015-05-04 04:47:26 +02:00
if k . startswith ( ' _ ' ) :
continue
buf = buf + " %s = %s " % ( k , v )
buf = buf . strip ( )
return buf
@staticmethod
def load ( data , block = None , role = None , task_include = None , variable_manager = None , loader = None ) :
t = Task ( block = block , role = role , task_include = task_include )
return t . load_data ( data , variable_manager = variable_manager , loader = loader )
2015-09-14 18:02:45 +02:00
def load_data ( self , ds , variable_manager = None , loader = None ) :
'''
We override load_data for tasks so that we can pull special flags
out of the task args and set them internaly only so the user never
sees them .
'''
t = super ( Task , self ) . load_data ( ds = ds , variable_manager = variable_manager , loader = loader )
t . _local_action = t . args . pop ( ' _local_action ' , False )
return t
2015-05-04 04:47:26 +02:00
def __repr__ ( self ) :
''' returns a human readable representation of the task '''
return " TASK: %s " % self . get_name ( )
def _preprocess_loop ( self , ds , new_ds , k , v ) :
''' take a lookup plugin name and store it correctly '''
loop_name = k . replace ( " with_ " , " " )
if new_ds . get ( ' loop ' ) is not None :
2015-06-23 16:39:49 +02:00
raise AnsibleError ( " duplicate loop in task: %s " % loop_name , obj = ds )
if v is None :
raise AnsibleError ( " you must specify a value when using %s " % k , obj = ds )
2015-05-04 04:47:26 +02:00
new_ds [ ' loop ' ] = loop_name
new_ds [ ' loop_args ' ] = v
def preprocess_data ( self , ds ) :
'''
tasks are especially complex arguments so need pre - processing .
keep it short .
'''
assert isinstance ( ds , dict )
# the new, cleaned datastructure, which will have legacy
# items reduced to a standard structure suitable for the
# attributes of the task class
new_ds = AnsibleMapping ( )
if isinstance ( ds , AnsibleBaseYAMLObject ) :
new_ds . ansible_pos = ds . ansible_pos
# use the args parsing class to determine the action, args,
# and the delegate_to value from the various possible forms
# supported as legacy
args_parser = ModuleArgsParser ( task_ds = ds )
2015-08-19 00:31:29 +02:00
( action , args , connection ) = args_parser . parse ( )
2015-05-04 04:47:26 +02:00
new_ds [ ' action ' ] = action
new_ds [ ' args ' ] = args
2015-08-19 00:31:29 +02:00
new_ds [ ' connection ' ] = connection
2015-05-04 04:47:26 +02:00
2015-08-06 23:19:16 +02:00
# we handle any 'vars' specified in the ds here, as we may
# be adding things to them below (special handling for includes).
# When that deprecated feature is removed, this can be too.
if ' vars ' in ds :
2015-08-07 06:05:42 +02:00
# _load_vars is defined in Base, and is used to load a dictionary
# or list of dictionaries in a standard way
new_ds [ ' vars ' ] = self . _load_vars ( None , ds . pop ( ' vars ' ) )
2015-08-06 23:19:16 +02:00
else :
new_ds [ ' vars ' ] = dict ( )
2015-09-03 08:23:27 +02:00
for ( k , v ) in iteritems ( ds ) :
2015-08-19 00:31:29 +02:00
if k in ( ' action ' , ' local_action ' , ' args ' , ' connection ' ) or k == action or k == ' shell ' :
2015-05-04 04:47:26 +02:00
# we don't want to re-assign these values, which were
# determined by the ModuleArgsParser() above
continue
elif k . replace ( " with_ " , " " ) in lookup_loader :
self . _preprocess_loop ( ds , new_ds , k , v )
2015-03-11 17:18:53 +01:00
else :
2015-08-06 23:19:16 +02:00
# pre-2.0 syntax allowed variables for include statements at the
# top level of the task, so we move those into the 'vars' dictionary
# here, and show a deprecation message as we will remove this at
# some point in the future.
2015-09-10 04:15:09 +02:00
if action == ' include ' and k not in self . _get_base_attributes ( ) and k not in self . DEPRECATED_ATTRIBUTES :
2015-08-06 23:19:16 +02:00
self . _display . deprecated ( " Specifying include variables at the top-level of the task is deprecated. Please see: \n http://docs.ansible.com/ansible/playbooks_roles.html#task-include-files-and-encouraging-reuse \n \n for currently supported syntax regarding included files and variables " )
new_ds [ ' vars ' ] [ k ] = v
else :
new_ds [ k ] = v
2015-05-04 04:47:26 +02:00
return super ( Task , self ) . preprocess_data ( new_ds )
2015-08-26 18:03:13 +02:00
def _load_any_errors_fatal ( self , attr , value ) :
'''
Exists only to show a deprecation warning , as this attribute is not valid
at the task level .
'''
display . deprecated ( " Setting any_errors_fatal on a task is no longer supported. This should be set at the play level only " )
return None
2015-05-04 04:47:26 +02:00
def post_validate ( self , templar ) :
'''
Override of base class post_validate , to also do final validation on
the block and task include ( if any ) to which this task belongs .
'''
if self . _block :
self . _block . post_validate ( templar )
if self . _task_include :
self . _task_include . post_validate ( templar )
super ( Task , self ) . post_validate ( templar )
2015-08-04 07:12:27 +02:00
def _post_validate_loop_args ( self , attr , value , templar ) :
'''
Override post validation for the loop args field , which is templated
specially in the TaskExecutor class when evaluating loops .
'''
return value
2015-08-14 06:33:36 +02:00
def _post_validate_environment ( self , attr , value , templar ) :
'''
Override post validation of vars on the play , as we don ' t want to
template these too early .
'''
2015-08-25 16:15:32 +02:00
if value is None :
return dict ( )
2015-08-14 06:33:36 +02:00
for env_item in value :
if isinstance ( env_item , ( string_types , AnsibleUnicode ) ) and env_item in templar . _available_variables . keys ( ) :
self . _display . deprecated ( " Using bare variables for environment is deprecated. Update your playbooks so that the environment value uses the full variable syntax ( ' {{ foo}} ' ) " )
break
return templar . template ( value , convert_bare = True )
2015-05-04 04:47:26 +02:00
def get_vars ( self ) :
2015-08-12 16:12:05 +02:00
all_vars = dict ( )
2015-05-04 04:47:26 +02:00
if self . _block :
all_vars . update ( self . _block . get_vars ( ) )
if self . _task_include :
all_vars . update ( self . _task_include . get_vars ( ) )
2015-08-12 16:12:05 +02:00
all_vars . update ( self . vars )
2015-05-04 04:47:26 +02:00
if ' tags ' in all_vars :
del all_vars [ ' tags ' ]
if ' when ' in all_vars :
del all_vars [ ' when ' ]
2015-08-12 16:12:05 +02:00
2015-05-04 04:47:26 +02:00
return all_vars
def copy ( self , exclude_block = False ) :
new_me = super ( Task , self ) . copy ( )
2015-09-14 18:02:45 +02:00
new_me . _local_action = self . _local_action
2015-05-04 04:47:26 +02:00
new_me . _block = None
if self . _block and not exclude_block :
new_me . _block = self . _block . copy ( )
new_me . _role = None
if self . _role :
new_me . _role = self . _role
new_me . _task_include = None
if self . _task_include :
2015-08-25 23:51:51 +02:00
new_me . _task_include = self . _task_include . copy ( exclude_block = exclude_block )
2015-05-04 04:47:26 +02:00
return new_me
def serialize ( self ) :
data = super ( Task , self ) . serialize ( )
2015-09-14 18:02:45 +02:00
data [ ' _local_action ' ] = self . _local_action
2015-05-04 04:47:26 +02:00
if self . _block :
data [ ' block ' ] = self . _block . serialize ( )
if self . _role :
data [ ' role ' ] = self . _role . serialize ( )
if self . _task_include :
data [ ' task_include ' ] = self . _task_include . serialize ( )
return data
def deserialize ( self , data ) :
# import is here to avoid import loops
#from ansible.playbook.task_include import TaskInclude
block_data = data . get ( ' block ' )
2015-09-14 18:02:45 +02:00
self . _local_action = data . get ( ' _local_action ' , False )
2015-05-04 04:47:26 +02:00
if block_data :
b = Block ( )
b . deserialize ( block_data )
self . _block = b
del data [ ' block ' ]
role_data = data . get ( ' role ' )
if role_data :
r = Role ( )
r . deserialize ( role_data )
self . _role = r
del data [ ' role ' ]
ti_data = data . get ( ' task_include ' )
if ti_data :
#ti = TaskInclude()
ti = Task ( )
ti . deserialize ( ti_data )
self . _task_include = ti
del data [ ' task_include ' ]
super ( Task , self ) . deserialize ( data )
2015-05-04 08:33:10 +02:00
def evaluate_conditional ( self , templar , all_vars ) :
2015-05-04 04:47:26 +02:00
if self . _block is not None :
2015-05-04 08:33:10 +02:00
if not self . _block . evaluate_conditional ( templar , all_vars ) :
2015-05-04 04:47:26 +02:00
return False
if self . _task_include is not None :
2015-05-04 08:33:10 +02:00
if not self . _task_include . evaluate_conditional ( templar , all_vars ) :
2015-05-04 04:47:26 +02:00
return False
2015-05-04 08:33:10 +02:00
return super ( Task , self ) . evaluate_conditional ( templar , all_vars )
2015-05-04 04:47:26 +02:00
def set_loader ( self , loader ) :
'''
Sets the loader on this object and recursively on parent , child objects .
This is used primarily after the Task has been serialized / deserialized , which
does not preserve the loader .
'''
self . _loader = loader
if self . _block :
self . _block . set_loader ( loader )
if self . _task_include :
self . _task_include . set_loader ( loader )
2015-07-21 19:52:51 +02:00
def _get_parent_attribute ( self , attr , extend = False ) :
2015-05-04 04:47:26 +02:00
'''
Generic logic to get the attribute or parent attribute for a task value .
'''
value = self . _attributes [ attr ]
2015-07-21 19:52:51 +02:00
if self . _block and ( value is None or extend ) :
2015-05-04 04:47:26 +02:00
parent_value = getattr ( self . _block , attr )
if extend :
value = self . _extend_value ( value , parent_value )
2015-03-11 17:18:53 +01:00
else :
2015-05-04 04:47:26 +02:00
value = parent_value
2015-07-21 19:52:51 +02:00
if self . _task_include and ( value is None or extend ) :
2015-05-04 04:47:26 +02:00
parent_value = getattr ( self . _task_include , attr )
if extend :
value = self . _extend_value ( value , parent_value )
2014-08-11 17:16:31 +02:00
else :
2015-05-04 04:47:26 +02:00
value = parent_value
return value
2015-07-21 18:12:22 +02:00
def _get_attr_environment ( self ) :
'''
Override for the ' tags ' getattr fetcher , used from Base .
'''
2015-08-25 16:15:32 +02:00
environment = self . _attributes [ ' environment ' ]
2015-07-21 18:12:22 +02:00
if environment is None :
2015-08-25 16:15:32 +02:00
environment = self . _get_parent_attribute ( ' environment ' )
2015-07-21 18:12:22 +02:00
return environment