Adding new playbook objects for v2

* Playbook
* TaskInclude
This commit is contained in:
James Cammarata 2014-11-05 08:00:00 -06:00
commit 229d49fe36
42 changed files with 2041 additions and 81 deletions

View file

@ -63,8 +63,9 @@ class ModuleArgsParser:
Args may also be munged for certain shell command parameters.
"""
def __init__(self, task=None):
self._task = task
def __init__(self, task_ds=dict()):
assert isinstance(task_ds, dict)
self._task_ds = task_ds
def _split_module_string(self, str):
@ -144,7 +145,7 @@ class ModuleArgsParser:
# form is like: local_action: copy src=a dest=b ... pretty common
args = parse_kv(thing)
else:
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task)
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
return args
def _normalize_new_style_args(self, thing):
@ -179,19 +180,17 @@ class ModuleArgsParser:
else:
# need a dict or a string, so giving up
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task)
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
return (action, args)
def parse(self, ds):
def parse(self):
'''
Given a task in one of the supported forms, parses and returns
returns the action, arguments, and delegate_to values for the
task, dealing with all sorts of levels of fuzziness.
'''
assert isinstance(ds, dict)
thing = None
action = None
@ -204,38 +203,38 @@ class ModuleArgsParser:
#
# action
if 'action' in ds:
if 'action' in self._task_ds:
# an old school 'action' statement
thing = ds['action']
thing = self._task_ds['action']
delegate_to = None
action, args = self._normalize_parameters(thing)
# local_action
if 'local_action' in ds:
if 'local_action' in self._task_ds:
# local_action is similar but also implies a delegate_to
if action is not None:
raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task)
thing = ds.get('local_action', '')
raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task_ds)
thing = self._task_ds.get('local_action', '')
delegate_to = 'localhost'
action, args = self._normalize_parameters(thing)
# module: <stuff> is the more new-style invocation
# walk the input dictionary to see we recognize a module name
for (item, value) in iteritems(ds):
for (item, value) in iteritems(self._task_ds):
if item in module_finder:
# finding more than one module name is a problem
if action is not None:
raise AnsibleParserError("conflicting action statements", obj=self._task)
raise AnsibleParserError("conflicting action statements", obj=self._task_ds)
action = item
thing = value
action, args = self._normalize_parameters(value, action=action)
# if we didn't see any module in the task at all, it's not a task really
if action is None:
raise AnsibleParserError("no action detected in task", obj=self._task)
raise AnsibleParserError("no action detected in task", obj=self._task_ds)
# shell modules require special handling
(action, args) = self._handle_shell_weirdness(action, args)

View file

@ -27,6 +27,7 @@ from yaml import load, YAMLError
from ansible.errors import AnsibleParserError
from ansible.parsing.vault import VaultLib
from ansible.parsing.splitter import unquote
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
from ansible.parsing.yaml.strings import YAML_SYNTAX_ERROR
@ -55,6 +56,7 @@ class DataLoader():
_FILE_CACHE = dict()
def __init__(self, vault_password=None):
self._basedir = '.'
self._vault = VaultLib(password=vault_password)
def load(self, data, file_name='<string>', show_content=True):
@ -70,13 +72,15 @@ class DataLoader():
try:
# if loading JSON failed for any reason, we go ahead
# and try to parse it as YAML instead
return self._safe_load(data)
return self._safe_load(data, file_name=file_name)
except YAMLError as yaml_exc:
self._handle_error(yaml_exc, file_name, show_content)
def load_from_file(self, file_name):
''' Loads data from a file, which can contain either JSON or YAML. '''
file_name = self.path_dwim(file_name)
# if the file has already been read in and cached, we'll
# return those results to avoid more file/vault operations
if file_name in self._FILE_CACHE:
@ -100,9 +104,14 @@ class DataLoader():
def is_file(self, path):
return os.path.isfile(path)
def _safe_load(self, stream):
def _safe_load(self, stream, file_name=None):
''' Implements yaml.safe_load(), except using our custom loader class. '''
return load(stream, AnsibleLoader)
loader = AnsibleLoader(stream, file_name)
try:
return loader.get_single_data()
finally:
loader.dispose()
def _get_file_contents(self, file_name):
'''
@ -139,3 +148,23 @@ class DataLoader():
raise AnsibleParserError(YAML_SYNTAX_ERROR, obj=err_obj, show_content=show_content)
def set_basedir(self, basedir):
''' sets the base directory, used to find files when a relative path is given '''
if basedir is not None:
self._basedir = basedir
def path_dwim(self, given):
'''
make relative paths work like folks expect.
'''
given = unquote(given)
if given.startswith("/"):
return os.path.abspath(given)
elif given.startswith("~"):
return os.path.abspath(os.path.expanduser(given))
else:
return os.path.abspath(os.path.join(self._basedir, given))

View file

@ -23,6 +23,10 @@ from yaml.constructor import Constructor
from ansible.parsing.yaml.objects import AnsibleMapping
class AnsibleConstructor(Constructor):
def __init__(self, file_name=None):
self._ansible_file_name = file_name
super(AnsibleConstructor, self).__init__()
def construct_yaml_map(self, node):
data = AnsibleMapping()
yield data
@ -36,7 +40,16 @@ class AnsibleConstructor(Constructor):
ret = AnsibleMapping(super(Constructor, self).construct_mapping(node, deep))
ret._line_number = node.__line__
ret._column_number = node.__column__
ret._data_source = node.__datasource__
# in some cases, we may have pre-read the data and then
# passed it to the load() call for YAML, in which case we
# want to override the default datasource (which would be
# '<string>') to the actual filename we read in
if self._ansible_file_name:
ret._data_source = self._ansible_file_name
else:
ret._data_source = node.__datasource__
return ret
AnsibleConstructor.add_constructor(

View file

@ -28,11 +28,11 @@ from ansible.parsing.yaml.composer import AnsibleComposer
from ansible.parsing.yaml.constructor import AnsibleConstructor
class AnsibleLoader(Reader, Scanner, Parser, AnsibleComposer, AnsibleConstructor, Resolver):
def __init__(self, stream):
def __init__(self, stream, file_name=None):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
AnsibleComposer.__init__(self)
AnsibleConstructor.__init__(self)
AnsibleConstructor.__init__(self, file_name=file_name)
Resolver.__init__(self)

View file

@ -26,8 +26,8 @@ class AnsibleBaseYAMLObject:
'''
_data_source = None
_line_number = None
_column_number = None
_line_number = 0
_column_number = 0
def get_position_info(self):
return (self._data_source, self._line_number, self._column_number)

View file

@ -34,8 +34,8 @@ Syntax Error while loading YAML.
"""
YAML_POSITION_DETAILS = """\
The error appears to have been in '%s': line %s, column %s,
but may actually be before there depending on the exact syntax problem.
The error appears to have been in '%s': line %s, column %s, but may
be elsewhere in the file depending on the exact syntax problem.
"""
YAML_COMMON_DICT_ERROR = """\