Commit c83a8337 by James Cammarata

New v2 ModuleArgsParser code and fixing up tests/other task code

parent bbd9921d
......@@ -13,67 +13,69 @@ class TestModArgsDwim(unittest.TestCase):
pass
def test_action_to_shell(self):
mod, args, to = self.m.parse('action', 'shell echo hi')
assert mod == 'shell'
mod, args, to = self.m.parse(dict(action='shell echo hi'))
assert mod == 'command'
assert args == dict(
free_form = 'echo hi',
use_shell = True
_raw_params = 'echo hi',
_uses_shell = True,
)
assert to is None
def test_basic_shell(self):
mod, args, to = self.m.parse('shell', 'echo hi')
assert mod == 'shell'
mod, args, to = self.m.parse(dict(shell='echo hi'))
assert mod == 'command'
assert args == dict(
free_form = 'echo hi',
use_shell = True
_raw_params = 'echo hi',
_uses_shell = True,
)
assert to is None
def test_basic_command(self):
mod, args, to = self.m.parse('command', 'echo hi')
mod, args, to = self.m.parse(dict(command='echo hi'))
assert mod == 'command'
assert args == dict(
free_form = 'echo hi',
use_shell = False
_raw_params = 'echo hi',
)
assert to is None
def test_shell_with_modifiers(self):
mod, args, to = self.m.parse('shell', '/bin/foo creates=/tmp/baz removes=/tmp/bleep')
assert mod == 'shell'
mod, args, to = self.m.parse(dict(shell='/bin/foo creates=/tmp/baz removes=/tmp/bleep'))
assert mod == 'command'
assert args == dict(
free_form = 'echo hi',
use_shell = False,
creates = '/tmp/baz',
removes = '/tmp/bleep'
creates = '/tmp/baz',
removes = '/tmp/bleep',
_raw_params = '/bin/foo',
_uses_shell = True,
)
assert to is None
def test_normal_usage(self):
mod, args, to = self.m.parse('copy', 'src=a dest=b')
mod, args, to = self.m.parse(dict(copy='src=a dest=b'))
assert mod == 'copy'
assert args == dict(src='a', dest='b')
assert to is None
def test_complex_args(self):
mod, args, to = self.m.parse('copy', dict(src=a, dest=b))
mod, args, to = self.m.parse(dict(copy=dict(src='a', dest='b')))
assert mod == 'copy'
assert args == dict(src = 'a', dest = 'b')
assert args == dict(src='a', dest='b')
assert to is None
def test_action_with_complex(self):
mod, args, to = self.m.parse('action', dict(module='copy',src='a',dest='b'))
assert mod == 'action'
assert args == dict(src = 'a', dest = 'b')
mod, args, to = self.m.parse(dict(action=dict(module='copy', src='a', dest='b')))
assert mod == 'copy'
assert args == dict(src='a', dest='b')
assert to is None
def test_action_with_complex_and_complex_args(self):
mod, args, to = self.m.parse(dict(action=dict(module='copy', args=dict(src='a', dest='b'))))
assert mod == 'copy'
assert args == dict(src='a', dest='b')
assert to is None
def test_local_action_string(self):
mod, args, to = self.m.parse('local_action', 'copy src=a dest=b')
mod, args, to = self.m.parse(dict(local_action='copy src=a dest=b'))
assert mod == 'copy'
assert args == dict(src=a, dest=b)
assert args == dict(src='a', dest='b')
assert to is 'localhost'
......@@ -36,13 +36,14 @@ class TestTask(unittest.TestCase):
t = Task.load(basic_shell_task)
assert t is not None
assert t.name == basic_shell_task['name']
assert t.action == 'shell'
assert t.args == 'echo hi'
assert t.action == 'command'
assert t.args == dict(_raw_params='echo hi', _uses_shell=True)
def test_load_task_kv_form(self):
t = Task.load(kv_shell_task)
assert t.action == 'shell'
#assert t.args == 'echo hi'
print "task action is %s" % t.action
assert t.action == 'command'
assert t.args == dict(_raw_params='echo hi', _uses_shell=True)
def test_task_auto_name(self):
assert 'name' not in kv_shell_task
......
......@@ -15,8 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
def parse_kv(args):
''' convert a string of key/value items to a dict '''
def parse_kv(args, check_raw=False):
'''
Convert a string of key/value items to a dict. If any free-form params
are found and the check_raw option is set to True, they will be added
to a new parameter called '_raw_params'. If check_raw is not enabled,
they will simply be ignored.
'''
options = {}
if args is not None:
try:
......@@ -26,10 +32,31 @@ def parse_kv(args):
raise errors.AnsibleError("error parsing argument string, try quoting the entire line.")
else:
raise
raw_params = []
for x in vargs:
if "=" in x:
k, v = x.split("=",1)
options[k.strip()] = unquote(v.strip())
k, v = x.split("=", 1)
# only internal variables can start with an underscore, so
# we don't allow users to set them directy in arguments
if k.startswith('_'):
raise AnsibleError("invalid parameter specified: '%s'" % k)
# FIXME: make the retrieval of this list of shell/command
# options a function, so the list is centralized
if check_raw and k not in ('creates', 'removes', 'chdir', 'executable', 'warn'):
raw_params.append(x)
else:
options[k.strip()] = unquote(v.strip())
else:
raw_params.append(x)
# recombine the free-form params, if any were found, and assign
# them to a special option for use later by the shell/command module
if len(raw_params) > 0:
options['_raw_params'] = ' '.join(raw_params)
return options
def _get_quote_state(token, quote_char):
......
......@@ -18,13 +18,10 @@
from ansible.playbook.base import Base
from ansible.playbook.attribute import Attribute, FieldAttribute
# from ansible.playbook.conditional import Conditional
from ansible.errors import AnsibleError
# TODO: it would be fantastic (if possible) if a task new where in the YAML it was defined for describing
# it in error conditions
from ansible.parsing.splitter import parse_kv
from ansible.parsing.mod_args import ModuleArgsParser
from ansible.plugins import module_finder, lookup_finder
class Task(Base):
......@@ -45,7 +42,6 @@ class Task(Base):
# validate_<attribute_name>
# will be used if defined
# might be possible to define others
_args = FieldAttribute(isa='dict')
_action = FieldAttribute(isa='string')
......@@ -60,9 +56,6 @@ class Task(Base):
_first_available_file = FieldAttribute(isa='list')
_ignore_errors = FieldAttribute(isa='bool')
# FIXME: this should not be a Task
# include = FieldAttribute(isa='string')
_loop = FieldAttribute(isa='string', private=True)
_loop_args = FieldAttribute(isa='list', private=True)
_local_action = FieldAttribute(isa='string')
......@@ -102,16 +95,19 @@ class Task(Base):
elif self.name:
return self.name
else:
return "%s %s" % (self.action, self._merge_kv(self.args))
flattened_args = self._merge_kv(self.args)
return "%s %s" % (self.action, flattened_args)
def _merge_kv(self, ds):
if ds is None:
return ""
elif isinstance(ds, basestring):
return ds
elif instance(ds, dict):
elif isinstance(ds, dict):
buf = ""
for (k,v) in ds.iteritems():
if k.startswith('_'):
continue
buf = buf + "%s=%s " % (k,v)
buf = buf.strip()
return buf
......@@ -125,27 +121,6 @@ class Task(Base):
''' returns a human readable representation of the task '''
return "TASK: %s" % self.get_name()
def _parse_old_school_action(self, v):
''' given a action/local_action line, return the module and args '''
tokens = v.split()
if len(tokens) < 2:
return [v,{}]
else:
if v not in [ 'command', 'shell' ]:
joined = " ".join(tokens[1:])
return [tokens[0], parse_kv(joined)]
else:
return [tokens[0], joined]
def _munge_action(self, ds, new_ds, k, v):
''' take a module name and split into action and args '''
if self._action.value is not None or 'action' in ds or 'local_action' in ds:
raise AnsibleError("duplicate action in task: %s" % k)
new_ds['action'] = k
new_ds['args'] = v
def _munge_loop(self, ds, new_ds, k, v):
''' take a lookup plugin name and store it correctly '''
......@@ -154,22 +129,6 @@ class Task(Base):
new_ds['loop'] = k
new_ds['loop_args'] = v
def _munge_action2(self, ds, new_ds, k, v, local=False):
''' take an old school action/local_action and reformat it '''
if isinstance(v, basestring):
tokens = self._parse_old_school_action(v)
new_ds['action'] = tokens[0]
if 'args' in ds:
raise AnsibleError("unexpected and redundant 'args'")
new_ds['args'] = args
if local:
if 'delegate_to' in ds:
raise AnsbileError("local_action and action conflict")
new_ds['delegate_to'] = 'localhost'
else:
raise AnsibleError("unexpected use of 'action'")
def munge(self, ds):
'''
tasks are especially complex arguments so need pre-processing.
......@@ -178,18 +137,31 @@ class Task(Base):
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 = dict()
# 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()
(action, args, delegate_to) = args_parser.parse(ds)
new_ds['action'] = action
new_ds['args'] = args
new_ds['delegate_to'] = delegate_to
for (k,v) in ds.iteritems():
if k in module_finder:
self._munge_action(ds, new_ds, k, v)
if k in ('action', 'local_action', 'args', 'delegate_to') or k == action:
# we don't want to re-assign these values, which were
# determined by the ModuleArgsParser() above
continue
elif "with_%s" % k in lookup_finder:
self._munge_loop(ds, new_ds, k, v)
elif k == 'action':
self._munge_action2(ds, new_ds, k, v)
elif k == 'local_action':
self._munge_action2(ds, new_ds, k, v, local=True)
else:
new_ds[k] = v
return new_ds
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment