2014-10-07 20:59:49 +00:00
# (c) 2014 Michael DeHaan, <michael@ansible.com>
#
# 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/>.
2014-10-15 23:22:54 +00:00
# Make coding more python3-ish
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2014-10-15 22:08:28 +00:00
from six import iteritems , string_types
2014-10-07 20:59:49 +00:00
2014-10-08 23:53:09 +00:00
from ansible . errors import AnsibleParserError
2014-11-14 22:14:08 +00:00
from ansible . plugins import module_loader
2015-07-15 15:56:01 +00:00
from ansible . parsing . splitter import parse_kv , split_args
2015-07-24 14:31:14 +00:00
from ansible . template import Templar
2014-10-08 15:35:58 +00:00
2015-06-02 19:37:06 +00:00
# For filtering out modules correctly below
2015-06-03 03:34:57 +00:00
RAW_PARAM_MODULES = ( [
2015-06-02 19:37:06 +00:00
' command ' ,
' shell ' ,
' script ' ,
' include ' ,
' include_vars ' ,
' add_host ' ,
' group_by ' ,
' set_fact ' ,
' raw ' ,
' meta ' ,
2015-06-03 03:34:57 +00:00
] )
2015-06-02 19:37:06 +00:00
2014-10-15 23:37:29 +00:00
class ModuleArgsParser :
2014-10-07 20:59:49 +00:00
"""
There are several ways a module and argument set can be expressed :
# legacy form (for a shell command)
- action : shell echo hi
2014-10-08 15:35:58 +00:00
2014-10-07 20:59:49 +00:00
# common shorthand for local actions vs delegate_to
- local_action : shell echo hi
# most commonly:
- copy : src = a dest = b
2014-10-08 15:35:58 +00:00
2014-10-07 20:59:49 +00:00
# legacy form
- action : copy src = a dest = b
# complex args form, for passing structured data
2014-10-08 15:35:58 +00:00
- copy :
2014-10-07 20:59:49 +00:00
src : a
dest : b
# gross, but technically legal
- action :
module : copy
2014-10-08 15:35:58 +00:00
args :
2014-10-07 20:59:49 +00:00
src : a
dest : b
2015-01-15 07:13:45 +00:00
# extra gross, but also legal. in this case, the args specified
2015-04-28 13:36:42 +00:00
# will act as 'defaults' and will be overridden by any args specified
2015-01-15 07:13:45 +00:00
# in one of the other formats (complex args under the action, or
# parsed from the k=v string
- command : ' pwd '
args :
chdir : ' /tmp '
2014-10-08 23:46:34 +00:00
This class has some of the logic to canonicalize these into the form
- module : < module_name >
delegate_to : < optional >
args : < args >
Args may also be munged for certain shell command parameters .
2014-10-07 20:59:49 +00:00
"""
2014-11-05 14:00:00 +00:00
def __init__ ( self , task_ds = dict ( ) ) :
assert isinstance ( task_ds , dict )
self . _task_ds = task_ds
2014-10-08 15:35:58 +00:00
2014-10-08 23:46:34 +00:00
2015-07-15 15:56:01 +00:00
def _split_module_string ( self , module_string ) :
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
when module names are expressed like :
action : copy src = a dest = b
the first part of the string is the name of the module
and the rest are strings pertaining to the arguments .
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:53:09 +00:00
2015-07-15 15:56:01 +00:00
tokens = split_args ( module_string )
2014-10-08 23:46:34 +00:00
if len ( tokens ) > 1 :
return ( tokens [ 0 ] , " " . join ( tokens [ 1 : ] ) )
else :
return ( tokens [ 0 ] , " " )
2014-10-08 15:35:58 +00:00
2014-10-08 23:46:34 +00:00
def _handle_shell_weirdness ( self , action , args ) :
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
given an action name and an args dictionary , return the
proper action name and args dictionary . This mostly is due
to shell / command being treated special and nothing else
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
# don't handle non shell/command modules in this function
# TODO: in terms of the whole app, should 'raw' also fit here?
if action not in [ ' shell ' , ' command ' ] :
return ( action , args )
# the shell module really is the command module with an additional
# parameter
if action == ' shell ' :
action = ' command '
2014-10-21 18:27:01 +00:00
args [ ' _uses_shell ' ] = True
2014-10-08 23:46:34 +00:00
2014-10-21 18:27:01 +00:00
return ( action , args )
2014-10-08 23:46:34 +00:00
2015-01-15 07:13:45 +00:00
def _normalize_parameters ( self , thing , action = None , additional_args = dict ( ) ) :
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
arguments can be fuzzy . Deal with all the forms .
2014-10-08 15:35:58 +00:00
'''
2015-01-15 07:13:45 +00:00
# final args are the ones we'll eventually return, so first update
# them with any additional args specified, which have lower priority
# than those which may be parsed/normalized next
final_args = dict ( )
if additional_args :
final_args . update ( additional_args )
2014-10-08 23:46:34 +00:00
# how we normalize depends if we figured out what the module name is
# yet. If we have already figured it out, it's an 'old style' invocation.
# otherwise, it's not
if action is not None :
2015-01-15 07:13:45 +00:00
args = self . _normalize_old_style_args ( thing , action )
2014-10-08 15:35:58 +00:00
else :
2014-10-08 23:46:34 +00:00
( action , args ) = self . _normalize_new_style_args ( thing )
# this can occasionally happen, simplify
2014-11-14 22:14:08 +00:00
if args and ' args ' in args :
2015-05-03 03:17:02 +00:00
tmp_args = args [ ' args ' ]
del args [ ' args ' ]
if isinstance ( tmp_args , string_types ) :
tmp_args = parse_kv ( tmp_args )
args . update ( tmp_args )
2014-10-08 15:35:58 +00:00
2015-01-15 07:13:45 +00:00
# finally, update the args we're going to return with the ones
# which were normalized above
if args :
final_args . update ( args )
2014-10-08 15:35:58 +00:00
2015-01-15 07:13:45 +00:00
return ( action , final_args )
def _normalize_old_style_args ( self , thing , action ) :
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
deals with fuzziness in old - style ( action / local_action ) module invocations
returns tuple of ( module_name , dictionary_args )
possible example inputs :
{ ' local_action ' : ' shell echo hi ' }
{ ' action ' : ' shell echo hi ' }
{ ' local_action ' : { ' module ' : ' ec2 ' , ' x ' : 1 , ' y ' : 2 } }
standardized outputs like :
( ' command ' , { _raw_params : ' echo hi ' , _uses_shell : True }
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
if isinstance ( thing , dict ) :
# form is like: local_action: { module: 'xyz', x: 2, y: 3 } ... uncommon!
args = thing
2014-10-15 22:08:28 +00:00
elif isinstance ( thing , string_types ) :
2014-10-08 23:46:34 +00:00
# form is like: local_action: copy src=a dest=b ... pretty common
2015-08-02 15:57:32 +00:00
check_raw = action in ( ' command ' , ' shell ' , ' script ' , ' raw ' )
2015-01-15 07:13:45 +00:00
args = parse_kv ( thing , check_raw = check_raw )
2015-04-16 02:32:03 +00:00
elif thing is None :
2014-11-14 22:14:08 +00:00
# this can happen with modules which take no params, like ping:
args = None
2014-10-08 23:46:34 +00:00
else :
2014-11-14 22:14:08 +00:00
raise AnsibleParserError ( " unexpected parameter type in action: %s " % type ( thing ) , obj = self . _task_ds )
2014-10-08 15:35:58 +00:00
return args
2014-10-08 23:46:34 +00:00
def _normalize_new_style_args ( self , thing ) :
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
deals with fuzziness in new style module invocations
accepting key = value pairs and dictionaries , and always returning dictionaries
returns tuple of ( module_name , dictionary_args )
possible example inputs :
{ ' shell ' : ' echo hi ' }
{ ' ec2 ' : { ' region ' : ' xyz ' }
{ ' ec2 ' : ' region=xyz ' }
standardized outputs like :
( ' ec2 ' , { region : ' xyz ' } )
2014-10-08 15:35:58 +00:00
'''
2014-10-08 23:46:34 +00:00
action = None
args = None
if isinstance ( thing , dict ) :
# form is like: copy: { src: 'a', dest: 'b' } ... common for structured (aka "complex") args
thing = thing . copy ( )
if ' module ' in thing :
action = thing [ ' module ' ]
args = thing . copy ( )
del args [ ' module ' ]
2014-10-15 22:08:28 +00:00
elif isinstance ( thing , string_types ) :
2014-10-08 23:46:34 +00:00
# form is like: copy: src=a dest=b ... common shorthand throughout ansible
( action , args ) = self . _split_module_string ( thing )
2015-08-02 15:57:32 +00:00
check_raw = action in ( ' command ' , ' shell ' , ' script ' , ' raw ' )
2015-01-15 07:13:45 +00:00
args = parse_kv ( args , check_raw = check_raw )
2014-10-08 23:46:34 +00:00
2014-10-08 15:35:58 +00:00
else :
2014-10-08 23:46:34 +00:00
# need a dict or a string, so giving up
2014-11-14 22:14:08 +00:00
raise AnsibleParserError ( " unexpected parameter type in action: %s " % type ( thing ) , obj = self . _task_ds )
2014-10-08 15:35:58 +00:00
2014-10-08 23:46:34 +00:00
return ( action , args )
2014-10-08 15:35:58 +00:00
2014-11-05 14:00:00 +00:00
def parse ( self ) :
2014-10-08 15:35:58 +00:00
'''
Given a task in one of the supported forms , parses and returns
returns the action , arguments , and delegate_to values for the
2014-10-08 23:46:34 +00:00
task , dealing with all sorts of levels of fuzziness .
2014-10-08 15:35:58 +00:00
'''
2015-08-18 22:31:29 +00:00
thing = None
2014-10-08 23:46:34 +00:00
action = None
2015-08-18 22:31:29 +00:00
connection = self . _task_ds . get ( ' connection ' , None )
2014-10-08 23:46:34 +00:00
args = dict ( )
2014-10-21 21:04:49 +00:00
2015-01-15 07:13:45 +00:00
# this is the 'extra gross' scenario detailed above, so we grab
# the args and pass them in as additional arguments, which can/will
# be overwritten via dict updates from the other arg sources below
# FIXME: add test cases for this
additional_args = self . _task_ds . get ( ' args ' , dict ( ) )
2015-07-15 15:56:01 +00:00
# We can have one of action, local_action, or module specified
2014-10-21 21:04:49 +00:00
# action
2014-11-05 14:00:00 +00:00
if ' action ' in self . _task_ds :
2014-10-08 23:46:34 +00:00
# an old school 'action' statement
2014-11-05 14:00:00 +00:00
thing = self . _task_ds [ ' action ' ]
2015-01-15 07:13:45 +00:00
action , args = self . _normalize_parameters ( thing , additional_args = additional_args )
2014-10-08 23:46:34 +00:00
2014-10-21 21:04:49 +00:00
# local_action
2014-11-05 14:00:00 +00:00
if ' local_action ' in self . _task_ds :
2015-08-18 22:31:29 +00:00
# local_action is similar but also implies a connection='local'
2014-10-08 23:46:34 +00:00
if action is not None :
2014-11-05 14:00:00 +00:00
raise AnsibleParserError ( " action and local_action are mutually exclusive " , obj = self . _task_ds )
thing = self . _task_ds . get ( ' local_action ' , ' ' )
2015-08-18 22:31:29 +00:00
connection = ' local '
2015-01-15 07:13:45 +00:00
action , args = self . _normalize_parameters ( thing , additional_args = additional_args )
2014-10-08 23:46:34 +00:00
2014-10-21 21:04:49 +00:00
# module: <stuff> is the more new-style invocation
# walk the input dictionary to see we recognize a module name
2014-11-05 14:00:00 +00:00
for ( item , value ) in iteritems ( self . _task_ds ) :
2015-02-12 18:11:08 +00:00
if item in module_loader or item == ' meta ' or item == ' include ' :
2014-10-21 21:04:49 +00:00
# finding more than one module name is a problem
if action is not None :
2014-11-05 14:00:00 +00:00
raise AnsibleParserError ( " conflicting action statements " , obj = self . _task_ds )
2014-10-21 21:04:49 +00:00
action = item
thing = value
2015-01-15 07:13:45 +00:00
action , args = self . _normalize_parameters ( value , action = action , additional_args = additional_args )
2014-10-08 23:46:34 +00:00
# if we didn't see any module in the task at all, it's not a task really
if action is None :
2014-11-05 14:00:00 +00:00
raise AnsibleParserError ( " no action detected in task " , obj = self . _task_ds )
2015-05-13 16:27:12 +00:00
elif args . get ( ' _raw_params ' , ' ' ) != ' ' and action not in RAW_PARAM_MODULES :
2015-07-24 14:31:14 +00:00
templar = Templar ( loader = None )
raw_params = args . pop ( ' _raw_params ' )
if templar . _contains_vars ( raw_params ) :
args [ ' _variable_params ' ] = raw_params
else :
raise AnsibleParserError ( " this task ' %s ' has extra params, which is only allowed in the following modules: %s " % ( action , " , " . join ( RAW_PARAM_MODULES ) ) , obj = self . _task_ds )
2014-10-08 23:46:34 +00:00
# shell modules require special handling
( action , args ) = self . _handle_shell_weirdness ( action , args )
2015-08-18 22:31:29 +00:00
return ( action , args , connection )