2014-01-04 18:31:44 +00:00
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2012-05-26 04:37:34 +00: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/>.
2012-05-26 05:20:53 +00:00
from ansible import errors
2012-05-26 05:49:23 +00:00
from ansible import utils
2014-07-30 19:18:06 +00:00
from ansible . module_utils . splitter import split_args
2013-04-29 01:05:19 +00:00
import os
2013-04-11 16:15:13 +00:00
import ansible . utils . template as template
2013-10-11 22:37:39 +00:00
import sys
2012-08-19 20:52:55 +00:00
2012-05-26 04:37:34 +00:00
class Task ( object ) :
2014-11-24 21:36:31 +00:00
_t_common = [
' action ' , ' always_run ' , ' any_errors_fatal ' , ' args ' , ' become ' , ' become_method ' , ' become_pass ' ,
' become_user ' , ' changed_when ' , ' delay ' , ' delegate_to ' , ' environment ' , ' failed_when ' ,
' first_available_file ' , ' ignore_errors ' , ' local_action ' , ' meta ' , ' name ' , ' no_log ' ,
' notify ' , ' register ' , ' remote_user ' , ' retries ' , ' run_once ' , ' su ' , ' su_pass ' , ' su_user ' ,
' sudo ' , ' sudo_pass ' , ' sudo_user ' , ' tags ' , ' transport ' , ' until ' , ' when ' ,
2012-05-26 04:37:34 +00:00
]
2014-11-24 21:36:31 +00:00
__slots__ = [
' async_poll_interval ' , ' async_seconds ' , ' default_vars ' , ' first_available_file ' ,
' items_lookup_plugin ' , ' items_lookup_terms ' , ' module_args ' , ' module_name ' , ' module_vars ' ,
' notified_by ' , ' play ' , ' play_file_vars ' , ' play_vars ' , ' role_name ' , ' role_params ' , ' role_vars ' ,
] + _t_common
2012-07-17 23:09:36 +00:00
# to prevent typos and such
2014-11-24 21:36:31 +00:00
VALID_KEYS = frozenset ( [
' async ' , ' connection ' , ' include ' , ' poll ' ,
] + _t_common )
2013-10-12 21:39:28 +00:00
2014-11-20 18:20:32 +00:00
def __init__ ( self , play , ds , module_vars = None , play_vars = None , play_file_vars = None , role_vars = None , role_params = None , default_vars = None , additional_conditions = None , role_name = None ) :
2012-05-26 05:20:53 +00:00
''' constructor loads from a task or handler datastructure '''
2012-10-11 11:56:01 +00:00
2013-04-20 22:03:03 +00:00
# meta directives are used to tell things like ansible/playbook to run
# operations like handler execution. Meta tasks are not executed
# normally.
if ' meta ' in ds :
self . meta = ds [ ' meta ' ]
2013-04-21 21:11:48 +00:00
self . tags = [ ]
2013-04-20 22:03:03 +00:00
return
else :
self . meta = None
2013-04-29 01:05:19 +00:00
library = os . path . join ( play . basedir , ' library ' )
if os . path . exists ( library ) :
utils . plugins . module_finder . add_directory ( library )
2012-07-17 23:09:36 +00:00
for x in ds . keys ( ) :
2012-10-13 00:07:05 +00:00
# code to allow for saying "modulename: args" versus "action: modulename args"
2012-11-18 17:37:30 +00:00
if x in utils . plugins . module_finder :
2013-04-23 02:03:39 +00:00
2012-11-26 21:42:44 +00:00
if ' action ' in ds :
2014-04-20 18:28:26 +00:00
raise errors . AnsibleError ( " multiple actions specified in task: ' %s ' and ' %s ' " % ( x , ds . get ( ' name ' , ds [ ' action ' ] ) ) )
2013-02-28 11:09:29 +00:00
if isinstance ( ds [ x ] , dict ) :
if ' args ' in ds :
raise errors . AnsibleError ( " can ' t combine args: and a dict for %s : in task %s " % ( x , ds . get ( ' name ' , " %s : %s " % ( x , ds [ x ] ) ) ) )
ds [ ' args ' ] = ds [ x ]
ds [ x ] = ' '
elif ds [ x ] is None :
ds [ x ] = ' '
2013-02-06 15:41:34 +00:00
if not isinstance ( ds [ x ] , basestring ) :
raise errors . AnsibleError ( " action specified for task %s has invalid type %s " % ( ds . get ( ' name ' , " %s : %s " % ( x , ds [ x ] ) ) , type ( ds [ x ] ) ) )
2012-10-13 00:07:05 +00:00
ds [ ' action ' ] = x + " " + ds [ x ]
2012-10-09 04:21:13 +00:00
ds . pop ( x )
2012-10-13 00:07:05 +00:00
# code to allow "with_glob" and to reference a lookup plugin named glob
2012-10-25 13:10:33 +00:00
elif x . startswith ( " with_ " ) :
2014-09-29 22:06:48 +00:00
if isinstance ( ds [ x ] , basestring ) :
param = ds [ x ] . strip ( )
# Only a variable, no logic
if ( param . startswith ( ' {{ ' ) and
param . find ( ' }} ' ) == len ( ds [ x ] ) - 2 and
param . find ( ' | ' ) == - 1 ) :
utils . warning ( " It is unnecessary to use ' {{ ' in loops, leave variables in loop expressions bare. " )
2013-10-11 23:04:26 +00:00
2012-10-13 00:07:05 +00:00
plugin_name = x . replace ( " with_ " , " " )
2012-11-01 23:41:50 +00:00
if plugin_name in utils . plugins . lookup_loader :
2012-10-13 00:07:05 +00:00
ds [ ' items_lookup_plugin ' ] = plugin_name
ds [ ' items_lookup_terms ' ] = ds [ x ]
ds . pop ( x )
else :
raise errors . AnsibleError ( " cannot find lookup plugin named %s for usage in with_ %s " % ( plugin_name , plugin_name ) )
2013-09-09 23:34:01 +00:00
elif x in [ ' changed_when ' , ' failed_when ' , ' when ' ] :
2014-09-29 22:06:48 +00:00
if isinstance ( ds [ x ] , basestring ) :
param = ds [ x ] . strip ( )
# Only a variable, no logic
if ( param . startswith ( ' {{ ' ) and
param . find ( ' }} ' ) == len ( ds [ x ] ) - 2 and
param . find ( ' | ' ) == - 1 ) :
utils . warning ( " It is unnecessary to use ' {{ ' in conditionals, leave variables in loop expressions bare. " )
2012-10-27 21:55:35 +00:00
elif x . startswith ( " when_ " ) :
2014-01-28 16:38:52 +00:00
utils . deprecated ( " The ' when_ ' conditional has been removed. Switch to using the regular unified ' when ' statements as described on docs.ansible.com. " , " 1.5 " , removed = True )
2013-10-11 22:37:39 +00:00
2013-02-07 07:11:30 +00:00
if ' when ' in ds :
2013-10-19 17:02:57 +00:00
raise errors . AnsibleError ( " multiple when_* statements specified in task %s " % ( ds . get ( ' name ' , ds [ ' action ' ] ) ) )
2012-10-27 21:55:35 +00:00
when_name = x . replace ( " when_ " , " " )
ds [ ' when ' ] = " %s %s " % ( when_name , ds [ x ] )
ds . pop ( x )
2012-10-09 04:21:13 +00:00
elif not x in Task . VALID_KEYS :
2012-07-17 23:09:36 +00:00
raise errors . AnsibleError ( " %s is not a legal parameter in an Ansible task or handler " % x )
2014-11-17 21:30:22 +00:00
self . module_vars = module_vars
self . play_vars = play_vars
self . play_file_vars = play_file_vars
self . role_vars = role_vars
2014-11-20 18:20:32 +00:00
self . role_params = role_params
2014-11-17 21:30:22 +00:00
self . default_vars = default_vars
self . play = play
2012-07-15 13:17:04 +00:00
# load various attributes
2012-08-19 20:52:55 +00:00
self . name = ds . get ( ' name ' , None )
2015-02-09 03:51:18 +00:00
self . tags = [ ' untagged ' ]
2012-08-19 20:52:55 +00:00
self . register = ds . get ( ' register ' , None )
2014-08-17 02:47:59 +00:00
self . environment = ds . get ( ' environment ' , play . environment )
2013-09-20 20:46:34 +00:00
self . role_name = role_name
2014-08-19 15:35:01 +00:00
self . no_log = utils . boolean ( ds . get ( ' no_log ' , " false " ) ) or self . play . no_log
2014-05-15 15:47:17 +00:00
self . run_once = utils . boolean ( ds . get ( ' run_once ' , ' false ' ) )
2014-01-31 22:09:10 +00:00
2014-11-24 21:36:31 +00:00
#Code to allow do until feature in a Task
2013-09-24 08:29:38 +00:00
if ' until ' in ds :
if not ds . get ( ' register ' ) :
raise errors . AnsibleError ( " register keyword is mandatory when using do until feature " )
self . module_vars [ ' delay ' ] = ds . get ( ' delay ' , 5 )
self . module_vars [ ' retries ' ] = ds . get ( ' retries ' , 3 )
self . module_vars [ ' register ' ] = ds . get ( ' register ' , None )
2014-01-04 00:13:21 +00:00
self . until = ds . get ( ' until ' )
self . module_vars [ ' until ' ] = self . until
2012-10-27 20:46:33 +00:00
2013-02-17 20:01:49 +00:00
# rather than simple key=value args on the options line, these represent structured data and the values
# can be hashes and lists, not just scalars
self . args = ds . get ( ' args ' , { } )
2013-10-21 16:07:51 +00:00
# get remote_user for task, then play, then playbook
if ds . get ( ' remote_user ' ) is not None :
self . remote_user = ds . get ( ' remote_user ' )
elif ds . get ( ' remote_user ' , play . remote_user ) is not None :
self . remote_user = ds . get ( ' remote_user ' , play . remote_user )
else :
self . remote_user = ds . get ( ' remote_user ' , play . playbook . remote_user )
2013-08-23 21:20:13 +00:00
2014-11-24 21:36:31 +00:00
# Fail out if user specifies privilege escalation params in conflict
if ( ds . get ( ' become ' ) or ds . get ( ' become_user ' ) or ds . get ( ' become_pass ' ) ) and ( ds . get ( ' sudo ' ) or ds . get ( ' sudo_user ' ) or ds . get ( ' sudo_pass ' ) ) :
raise errors . AnsibleError ( ' incompatible parameters ( " become " , " become_user " , " become_pass " ) and sudo params " sudo " , " sudo_user " , " sudo_pass " in task: %s ' % self . name )
if ( ds . get ( ' become ' ) or ds . get ( ' become_user ' ) or ds . get ( ' become_pass ' ) ) and ( ds . get ( ' su ' ) or ds . get ( ' su_user ' ) or ds . get ( ' su_pass ' ) ) :
raise errors . AnsibleError ( ' incompatible parameters ( " become " , " become_user " , " become_pass " ) and su params " su " , " su_user " , " sudo_pass " in task: %s ' % self . name )
if ( ds . get ( ' sudo ' ) or ds . get ( ' sudo_user ' ) or ds . get ( ' sudo_pass ' ) ) and ( ds . get ( ' su ' ) or ds . get ( ' su_user ' ) or ds . get ( ' su_pass ' ) ) :
raise errors . AnsibleError ( ' incompatible parameters ( " su " , " su_user " , " su_pass " ) and sudo params " sudo " , " sudo_user " , " sudo_pass " in task: %s ' % self . name )
self . become = utils . boolean ( ds . get ( ' become ' , play . become ) )
self . become_method = ds . get ( ' become_method ' , play . become_method )
self . become_user = ds . get ( ' become_user ' , play . become_user )
self . become_pass = ds . get ( ' become_pass ' , play . playbook . become_pass )
# set only if passed in current task data
if ' sudo ' in ds or ' sudo_user ' in ds :
self . become = ds [ ' sudo ' ]
self . become_method = ' sudo '
if ' sudo_user ' in ds :
self . become_user = ds [ ' sudo_user ' ]
if ' sudo_pass ' in ds :
self . become_pass = ds [ ' sudo_pass ' ]
if ' su ' in ds or ' su_user ' in ds :
self . become = ds [ ' su ' ]
self . become_method = ' su '
if ' su_user ' in ds :
self . become_user = ds [ ' su_user ' ]
if ' su_pass ' in ds :
self . become_pass = ds [ ' su_pass ' ]
2014-01-21 20:20:33 +00:00
2012-08-19 20:52:55 +00:00
# Both are defined
if ( ' action ' in ds ) and ( ' local_action ' in ds ) :
raise errors . AnsibleError ( " the ' action ' and ' local_action ' attributes can not be used together " )
# Both are NOT defined
elif ( not ' action ' in ds ) and ( not ' local_action ' in ds ) :
2012-11-13 11:53:42 +00:00
raise errors . AnsibleError ( " ' action ' or ' local_action ' attribute missing in task \" %s \" " % ds . get ( ' name ' , ' <Unnamed> ' ) )
2012-08-19 20:52:55 +00:00
# Only one of them is defined
elif ' local_action ' in ds :
self . action = ds . get ( ' local_action ' , ' ' )
2012-09-17 18:28:14 +00:00
self . delegate_to = ' 127.0.0.1 '
2012-08-19 20:52:55 +00:00
else :
self . action = ds . get ( ' action ' , ' ' )
self . delegate_to = ds . get ( ' delegate_to ' , None )
2013-01-03 20:27:01 +00:00
self . transport = ds . get ( ' connection ' , ds . get ( ' transport ' , play . transport ) )
2012-07-11 23:51:26 +00:00
2013-02-28 13:12:36 +00:00
if isinstance ( self . action , dict ) :
if ' module ' not in self . action :
raise errors . AnsibleError ( " ' module ' attribute missing from action in task \" %s \" " % ds . get ( ' name ' , ' %s ' % self . action ) )
if self . args :
raise errors . AnsibleError ( " ' args ' cannot be combined with dict ' action ' in task \" %s \" " % ds . get ( ' name ' , ' %s ' % self . action ) )
self . args = self . action
self . action = self . args . pop ( ' module ' )
2012-09-25 19:47:17 +00:00
# delegate_to can use variables
if not ( self . delegate_to is None ) :
2012-10-31 00:42:07 +00:00
# delegate_to: localhost should use local transport
if self . delegate_to in [ ' 127.0.0.1 ' , ' localhost ' ] :
self . transport = ' local '
2012-09-25 19:47:17 +00:00
2012-07-15 13:17:04 +00:00
# notified by is used by Playbook code to flag which hosts
# need to run a notifier
2012-05-26 04:37:34 +00:00
self . notified_by = [ ]
2012-07-15 13:17:04 +00:00
# if no name is specified, use the action line as the name
2012-05-26 04:37:34 +00:00
if self . name is None :
self . name = self . action
2012-07-15 13:17:04 +00:00
# load various attributes
2012-10-27 21:55:35 +00:00
self . when = ds . get ( ' when ' , None )
2013-07-14 19:07:45 +00:00
self . changed_when = ds . get ( ' changed_when ' , None )
2013-09-09 23:34:01 +00:00
self . failed_when = ds . get ( ' failed_when ' , None )
2014-08-07 18:28:09 +00:00
# combine the default and module vars here for use in templating
all_vars = self . default_vars . copy ( )
2014-11-17 21:30:22 +00:00
all_vars = utils . combine_vars ( all_vars , self . play_vars )
all_vars = utils . combine_vars ( all_vars , self . play_file_vars )
all_vars = utils . combine_vars ( all_vars , self . role_vars )
2014-08-07 18:28:09 +00:00
all_vars = utils . combine_vars ( all_vars , self . module_vars )
2014-11-20 18:20:32 +00:00
all_vars = utils . combine_vars ( all_vars , self . role_params )
2014-08-07 18:28:09 +00:00
2014-03-20 20:09:58 +00:00
self . async_seconds = ds . get ( ' async ' , 0 ) # not async by default
2014-08-07 18:28:09 +00:00
self . async_seconds = template . template_from_string ( play . basedir , self . async_seconds , all_vars )
2014-03-20 20:09:58 +00:00
self . async_seconds = int ( self . async_seconds )
self . async_poll_interval = ds . get ( ' poll ' , 10 ) # default poll = 10 seconds
2014-08-07 18:28:09 +00:00
self . async_poll_interval = template . template_from_string ( play . basedir , self . async_poll_interval , all_vars )
2014-03-20 20:09:58 +00:00
self . async_poll_interval = int ( self . async_poll_interval )
2012-05-26 04:37:34 +00:00
self . notify = ds . get ( ' notify ' , [ ] )
2012-07-15 13:17:04 +00:00
self . first_available_file = ds . get ( ' first_available_file ' , None )
2012-08-18 12:46:51 +00:00
2012-10-13 00:07:05 +00:00
self . items_lookup_plugin = ds . get ( ' items_lookup_plugin ' , None )
self . items_lookup_terms = ds . get ( ' items_lookup_terms ' , None )
2012-08-01 16:13:07 +00:00
self . ignore_errors = ds . get ( ' ignore_errors ' , False )
2013-03-28 06:17:01 +00:00
self . any_errors_fatal = ds . get ( ' any_errors_fatal ' , play . any_errors_fatal )
2012-08-07 00:07:02 +00:00
2013-08-20 21:09:44 +00:00
self . always_run = ds . get ( ' always_run ' , False )
2012-11-07 13:59:39 +00:00
# action should be a string
if not isinstance ( self . action , basestring ) :
raise errors . AnsibleError ( " action is of type ' %s ' and not a string in task. name: %s " % ( type ( self . action ) . __name__ , self . name ) )
2012-07-15 13:17:04 +00:00
# notify can be a string or a list, store as a list
2012-05-26 04:37:34 +00:00
if isinstance ( self . notify , basestring ) :
self . notify = [ self . notify ]
2012-07-15 13:17:04 +00:00
# split the action line into a module name + arguments
2014-08-11 15:16:31 +00:00
try :
tokens = split_args ( self . action )
except Exception , e :
if " unbalanced " in str ( e ) :
raise errors . AnsibleError ( " There was an error while parsing the task %s . \n " % repr ( self . action ) + \
" Make sure quotes are matched or escaped properly " )
else :
raise
2012-05-26 04:37:34 +00:00
if len ( tokens ) < 1 :
2012-08-02 12:37:43 +00:00
raise errors . AnsibleError ( " invalid/missing action in task. name: %s " % self . name )
2012-05-26 04:37:34 +00:00
self . module_name = tokens [ 0 ]
self . module_args = ' '
if len ( tokens ) > 1 :
2014-07-30 19:18:06 +00:00
self . module_args = " " . join ( tokens [ 1 : ] )
2012-05-26 04:37:34 +00:00
2012-07-15 13:17:04 +00:00
import_tags = self . module_vars . get ( ' tags ' , [ ] )
2013-10-07 19:18:51 +00:00
if type ( import_tags ) in [ int , float ] :
import_tags = str ( import_tags )
elif type ( import_tags ) in [ str , unicode ] :
2012-07-15 13:17:04 +00:00
# allow the user to list comma delimited tags
import_tags = import_tags . split ( " , " )
2012-05-26 05:49:23 +00:00
2012-07-15 13:17:04 +00:00
# handle mutually incompatible options
2012-10-25 13:10:33 +00:00
incompatibles = [ x for x in [ self . first_available_file , self . items_lookup_plugin ] if x is not None ]
2012-10-13 00:07:05 +00:00
if len ( incompatibles ) > 1 :
2012-10-25 13:10:33 +00:00
raise errors . AnsibleError ( " with_(plugin), and first_available_file are mutually incompatible in a single task " )
2012-07-15 13:17:04 +00:00
2014-05-03 16:40:05 +00:00
# make first_available_file accessible to Runner code
2012-07-15 13:17:04 +00:00
if self . first_available_file :
self . module_vars [ ' first_available_file ' ] = self . first_available_file
2014-07-21 16:01:41 +00:00
# make sure that the 'item' variable is set when using
# first_available_file (issue #8220)
if ' item ' not in self . module_vars :
self . module_vars [ ' item ' ] = ' '
2012-08-07 00:07:02 +00:00
2012-10-13 00:07:05 +00:00
if self . items_lookup_plugin is not None :
self . module_vars [ ' items_lookup_plugin ' ] = self . items_lookup_plugin
self . module_vars [ ' items_lookup_terms ' ] = self . items_lookup_terms
2012-08-18 12:46:51 +00:00
# allow runner to see delegate_to option
self . module_vars [ ' delegate_to ' ] = self . delegate_to
2013-08-20 21:09:44 +00:00
# make some task attributes accessible to Runner code
2012-08-01 16:13:07 +00:00
self . module_vars [ ' ignore_errors ' ] = self . ignore_errors
2013-07-14 19:07:45 +00:00
self . module_vars [ ' register ' ] = self . register
self . module_vars [ ' changed_when ' ] = self . changed_when
2013-09-09 23:34:01 +00:00
self . module_vars [ ' failed_when ' ] = self . failed_when
2013-08-20 21:09:44 +00:00
self . module_vars [ ' always_run ' ] = self . always_run
2012-08-01 16:13:07 +00:00
2012-07-15 16:29:53 +00:00
# tags allow certain parts of a playbook to be run without running the whole playbook
2012-07-11 23:51:26 +00:00
apply_tags = ds . get ( ' tags ' , None )
if apply_tags is not None :
if type ( apply_tags ) in [ str , unicode ] :
self . tags . append ( apply_tags )
2013-10-07 19:18:51 +00:00
elif type ( apply_tags ) in [ int , float ] :
self . tags . append ( str ( apply_tags ) )
2012-07-11 23:51:26 +00:00
elif type ( apply_tags ) == list :
self . tags . extend ( apply_tags )
2012-07-12 00:30:30 +00:00
self . tags . extend ( import_tags )
2012-08-07 00:07:02 +00:00
2015-02-09 03:51:18 +00:00
if len ( self . tags ) > 1 :
self . tags . remove ( ' untagged ' )
2013-01-03 23:15:13 +00:00
if additional_conditions :
2014-08-11 18:13:48 +00:00
new_conditions = additional_conditions [ : ]
if self . when :
new_conditions . append ( self . when )
2014-01-04 00:13:21 +00:00
self . when = new_conditions