mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Adding new playbook objects for v2
* Playbook * TaskInclude
This commit is contained in:
parent
cbad867f24
commit
229d49fe36
42 changed files with 2041 additions and 81 deletions
|
@ -48,10 +48,13 @@ class AnsibleError(Exception):
|
|||
if isinstance(self._obj, AnsibleBaseYAMLObject):
|
||||
extended_error = self._get_extended_error()
|
||||
if extended_error:
|
||||
self.message = '%s\n%s' % (message, extended_error)
|
||||
self.message = '%s\n\n%s' % (message, extended_error)
|
||||
else:
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __repr__(self):
|
||||
return self.message
|
||||
|
||||
|
@ -129,7 +132,7 @@ class AnsibleError(Exception):
|
|||
if unbalanced:
|
||||
error_message += YAML_COMMON_UNBALANCED_QUOTES_ERROR
|
||||
|
||||
except IOError:
|
||||
except (IOError, TypeError):
|
||||
error_message += '\n(could not open file to display line)'
|
||||
except IndexError:
|
||||
error_message += '\n(specified line no longer in file, maybe it changed?)'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = """\
|
||||
|
|
|
@ -19,14 +19,60 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.parsing.yaml import DataLoader
|
||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.plugins import push_basedir
|
||||
|
||||
|
||||
__all__ = ['Playbook']
|
||||
|
||||
|
||||
class Playbook:
|
||||
def __init__(self, filename):
|
||||
self.ds = v2.utils.load_yaml_from_file(filename)
|
||||
self.plays = []
|
||||
|
||||
def load(self):
|
||||
# loads a list of plays from the parsed ds
|
||||
self.plays = []
|
||||
def __init__(self, loader=None):
|
||||
# Entries in the datastructure of a playbook may
|
||||
# be either a play or an include statement
|
||||
self._entries = []
|
||||
self._basedir = '.'
|
||||
|
||||
if loader:
|
||||
self._loader = loader
|
||||
else:
|
||||
self._loader = DataLoader()
|
||||
|
||||
@staticmethod
|
||||
def load(file_name, loader=None):
|
||||
pb = Playbook(loader=loader)
|
||||
pb._load_playbook_data(file_name)
|
||||
return pb
|
||||
|
||||
def _load_playbook_data(self, file_name):
|
||||
|
||||
# add the base directory of the file to the data loader,
|
||||
# so that it knows where to find relatively pathed files
|
||||
basedir = os.path.dirname(file_name)
|
||||
self._loader.set_basedir(basedir)
|
||||
|
||||
ds = self._loader.load_from_file(file_name)
|
||||
if not isinstance(ds, list):
|
||||
raise AnsibleParserError("playbooks must be a list of plays", obj=ds)
|
||||
|
||||
# Parse the playbook entries. For plays, we simply parse them
|
||||
# using the Play() object, and includes are parsed using the
|
||||
# PlaybookInclude() object
|
||||
for entry in ds:
|
||||
if not isinstance(entry, dict):
|
||||
raise AnsibleParserError("playbook entries must be either a valid play or an include statement", obj=entry)
|
||||
|
||||
if 'include' in entry:
|
||||
entry_obj = PlaybookInclude.load(entry, loader=self._loader)
|
||||
else:
|
||||
entry_obj = Play.load(entry, loader=self._loader)
|
||||
|
||||
self._entries.append(entry_obj)
|
||||
|
||||
|
||||
def get_plays(self):
|
||||
return self.plays
|
||||
|
|
|
@ -110,7 +110,7 @@ class Base:
|
|||
valid_attrs = [name for (name, attribute) in iteritems(self._get_base_attributes())]
|
||||
for key in ds:
|
||||
if key not in valid_attrs:
|
||||
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (key, self.__class__), obj=ds)
|
||||
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (key, self.__class__.__name__), obj=ds)
|
||||
|
||||
def validate(self):
|
||||
''' validation that is done at parse time, not load time '''
|
||||
|
|
|
@ -67,15 +67,15 @@ class Block(Base):
|
|||
return ds
|
||||
|
||||
def _load_block(self, attr, ds):
|
||||
return load_list_of_tasks(ds)
|
||||
return load_list_of_tasks(ds, block=self, loader=self._loader)
|
||||
|
||||
def _load_rescue(self, attr, ds):
|
||||
return load_list_of_tasks(ds)
|
||||
return load_list_of_tasks(ds, block=self, loader=self._loader)
|
||||
|
||||
def _load_always(self, attr, ds):
|
||||
return load_list_of_tasks(ds)
|
||||
return load_list_of_tasks(ds, block=self, loader=self._loader)
|
||||
|
||||
# not currently used
|
||||
#def _load_otherwise(self, attr, ds):
|
||||
# return self._load_list_of_tasks(ds)
|
||||
# return self._load_list_of_tasks(ds, block=self, loader=self._loader)
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
from types import NoneType
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
|
||||
def load_list_of_blocks(ds, role=None, loader=None):
|
||||
'''
|
||||
|
@ -38,24 +39,34 @@ def load_list_of_blocks(ds, role=None, loader=None):
|
|||
|
||||
return block_list
|
||||
|
||||
def load_list_of_tasks(ds, block=None, role=None, loader=None):
|
||||
|
||||
def load_list_of_tasks(ds, block=None, role=None, task_include=None, loader=None):
|
||||
'''
|
||||
Given a list of task datastructures (parsed from YAML),
|
||||
return a list of Task() objects.
|
||||
return a list of Task() or TaskInclude() objects.
|
||||
'''
|
||||
|
||||
# we import here to prevent a circular dependency with imports
|
||||
from ansible.playbook.task import Task
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
|
||||
assert type(ds) == list
|
||||
|
||||
task_list = []
|
||||
for task in ds:
|
||||
t = Task.load(task, block=block, role=role, loader=loader)
|
||||
if not isinstance(task, dict):
|
||||
raise AnsibleParserError("task/handler entries must be dictionaries (got a %s)" % type(task), obj=ds)
|
||||
|
||||
if 'include' in task:
|
||||
t = TaskInclude.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
||||
else:
|
||||
t = Task.load(task, block=block, role=role, task_include=task_include, loader=loader)
|
||||
|
||||
task_list.append(t)
|
||||
|
||||
return task_list
|
||||
|
||||
|
||||
def load_list_of_roles(ds, loader=None):
|
||||
'''
|
||||
Loads and returns a list of RoleInclude objects from the datastructure
|
||||
|
|
|
@ -124,28 +124,28 @@ class Play(Base):
|
|||
Loads a list of blocks from a list which may be mixed tasks/blocks.
|
||||
Bare tasks outside of a block are given an implicit block.
|
||||
'''
|
||||
return load_list_of_blocks(ds)
|
||||
return load_list_of_blocks(ds, loader=self._loader)
|
||||
|
||||
def _load_pre_tasks(self, attr, ds):
|
||||
'''
|
||||
Loads a list of blocks from a list which may be mixed tasks/blocks.
|
||||
Bare tasks outside of a block are given an implicit block.
|
||||
'''
|
||||
return load_list_of_blocks(ds)
|
||||
return load_list_of_blocks(ds, loader=self._loader)
|
||||
|
||||
def _load_post_tasks(self, attr, ds):
|
||||
'''
|
||||
Loads a list of blocks from a list which may be mixed tasks/blocks.
|
||||
Bare tasks outside of a block are given an implicit block.
|
||||
'''
|
||||
return load_list_of_blocks(ds)
|
||||
return load_list_of_blocks(ds, loader=self._loader)
|
||||
|
||||
def _load_handlers(self, attr, ds):
|
||||
'''
|
||||
Loads a list of blocks from a list which may be mixed handlers/blocks.
|
||||
Bare handlers outside of a block are given an implicit block.
|
||||
'''
|
||||
return load_list_of_blocks(ds)
|
||||
return load_list_of_blocks(ds, loader=self._loader)
|
||||
|
||||
def _load_roles(self, attr, ds):
|
||||
'''
|
||||
|
|
|
@ -95,11 +95,11 @@ class Role:
|
|||
|
||||
task_data = self._load_role_yaml('tasks')
|
||||
if task_data:
|
||||
self._task_blocks = load_list_of_blocks(task_data)
|
||||
self._task_blocks = load_list_of_blocks(task_data, role=self, loader=self._loader)
|
||||
|
||||
handler_data = self._load_role_yaml('handlers')
|
||||
if handler_data:
|
||||
self._handler_blocks = load_list_of_blocks(handler_data)
|
||||
self._handler_blocks = load_list_of_blocks(handler_data, role=self, loader=self._loader)
|
||||
|
||||
# vars and default vars are regular dictionaries
|
||||
self._role_vars = self._load_role_yaml('vars')
|
||||
|
|
|
@ -27,6 +27,7 @@ from ansible.errors import AnsibleError
|
|||
from ansible.parsing.splitter import parse_kv
|
||||
from ansible.parsing.mod_args import ModuleArgsParser
|
||||
from ansible.parsing.yaml import DataLoader
|
||||
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
||||
from ansible.plugins import module_finder, lookup_finder
|
||||
|
||||
class Task(Base):
|
||||
|
@ -54,6 +55,7 @@ class Task(Base):
|
|||
_always_run = FieldAttribute(isa='bool')
|
||||
_any_errors_fatal = FieldAttribute(isa='bool')
|
||||
_async = FieldAttribute(isa='int')
|
||||
_changed_when = FieldAttribute(isa='string')
|
||||
_connection = FieldAttribute(isa='string')
|
||||
_delay = FieldAttribute(isa='int')
|
||||
_delegate_to = FieldAttribute(isa='string')
|
||||
|
@ -88,10 +90,13 @@ class Task(Base):
|
|||
_until = FieldAttribute(isa='list') # ?
|
||||
_when = FieldAttribute(isa='list', default=[])
|
||||
|
||||
def __init__(self, block=None, role=None):
|
||||
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._block = block
|
||||
self._role = role
|
||||
self._task_include = task_include
|
||||
|
||||
super(Task, self).__init__()
|
||||
|
||||
def get_name(self):
|
||||
|
@ -120,8 +125,8 @@ class Task(Base):
|
|||
return buf
|
||||
|
||||
@staticmethod
|
||||
def load(data, block=None, role=None, loader=None):
|
||||
t = Task(block=block, role=role)
|
||||
def load(data, block=None, role=None, task_include=None, loader=None):
|
||||
t = Task(block=block, role=role, task_include=task_include)
|
||||
return t.load_data(data, loader=loader)
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -131,9 +136,10 @@ class Task(Base):
|
|||
def _munge_loop(self, ds, new_ds, k, v):
|
||||
''' take a lookup plugin name and store it correctly '''
|
||||
|
||||
if self._loop.value is not None:
|
||||
raise AnsibleError("duplicate loop in task: %s" % k)
|
||||
new_ds['loop'] = k
|
||||
loop_name = k.replace("with_", "")
|
||||
if new_ds.get('loop') is not None:
|
||||
raise AnsibleError("duplicate loop in task: %s" % loop_name)
|
||||
new_ds['loop'] = loop_name
|
||||
new_ds['loop_args'] = v
|
||||
|
||||
def munge(self, ds):
|
||||
|
@ -147,13 +153,15 @@ class Task(Base):
|
|||
# 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()
|
||||
new_ds = AnsibleMapping()
|
||||
if isinstance(ds, AnsibleBaseYAMLObject):
|
||||
new_ds.copy_position_info(ds)
|
||||
|
||||
# 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)
|
||||
args_parser = ModuleArgsParser(task_ds=ds)
|
||||
(action, args, delegate_to) = args_parser.parse()
|
||||
|
||||
new_ds['action'] = action
|
||||
new_ds['args'] = args
|
||||
|
@ -164,7 +172,7 @@ class Task(Base):
|
|||
# we don't want to re-assign these values, which were
|
||||
# determined by the ModuleArgsParser() above
|
||||
continue
|
||||
elif "with_%s" % k in lookup_finder:
|
||||
elif k.replace("with_", "") in lookup_finder:
|
||||
self._munge_loop(ds, new_ds, k, v)
|
||||
else:
|
||||
new_ds[k] = v
|
||||
|
|
|
@ -19,3 +19,128 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.parsing.splitter import split_args, parse_kv
|
||||
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
||||
from ansible.playbook.attribute import Attribute, FieldAttribute
|
||||
from ansible.playbook.base import Base
|
||||
from ansible.playbook.helpers import load_list_of_tasks
|
||||
from ansible.plugins import lookup_finder
|
||||
|
||||
|
||||
__all__ = ['TaskInclude']
|
||||
|
||||
|
||||
class TaskInclude(Base):
|
||||
|
||||
'''
|
||||
A class used to wrap the use of `include: /some/other/file.yml`
|
||||
within a task list, which may return a list of Task objects and/or
|
||||
more TaskInclude objects.
|
||||
'''
|
||||
|
||||
# the description field is used mainly internally to
|
||||
# show a nice reprsentation of this class, rather than
|
||||
# simply using __class__.__name__
|
||||
|
||||
__desc__ = "task include statement"
|
||||
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
# Attributes
|
||||
|
||||
_include = FieldAttribute(isa='string')
|
||||
_loop = FieldAttribute(isa='string', private=True)
|
||||
_loop_args = FieldAttribute(isa='list', private=True)
|
||||
_tags = FieldAttribute(isa='list', default=[])
|
||||
_vars = FieldAttribute(isa='dict', default=dict())
|
||||
_when = FieldAttribute(isa='list', default=[])
|
||||
|
||||
def __init__(self, block=None, role=None, task_include=None):
|
||||
self._tasks = []
|
||||
self._block = block
|
||||
self._role = role
|
||||
self._task_include = task_include
|
||||
|
||||
super(TaskInclude, self).__init__()
|
||||
|
||||
@staticmethod
|
||||
def load(data, block=None, role=None, task_include=None, loader=None):
|
||||
ti = TaskInclude(block=block, role=role, task_include=None)
|
||||
return ti.load_data(data, loader=loader)
|
||||
|
||||
def munge(self, ds):
|
||||
'''
|
||||
Regorganizes the data for a TaskInclude datastructure to line
|
||||
up with what we expect the proper attributes to be
|
||||
'''
|
||||
|
||||
assert isinstance(ds, dict)
|
||||
|
||||
# the new, cleaned datastructure, which will have legacy
|
||||
# items reduced to a standard structure
|
||||
new_ds = AnsibleMapping()
|
||||
if isinstance(ds, AnsibleBaseYAMLObject):
|
||||
new_ds.copy_position_info(ds)
|
||||
|
||||
for (k,v) in ds.iteritems():
|
||||
if k == 'include':
|
||||
self._munge_include(ds, new_ds, k, v)
|
||||
elif k.replace("with_", "") in lookup_finder:
|
||||
self._munge_loop(ds, new_ds, k, v)
|
||||
else:
|
||||
# some basic error checking, to make sure vars are properly
|
||||
# formatted and do not conflict with k=v parameters
|
||||
# FIXME: we could merge these instead, but controlling the order
|
||||
# in which they're encountered could be difficult
|
||||
if k == 'vars':
|
||||
if 'vars' in new_ds:
|
||||
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
|
||||
elif not isinstance(v, dict):
|
||||
raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds)
|
||||
new_ds[k] = v
|
||||
|
||||
return new_ds
|
||||
|
||||
def _munge_include(self, ds, new_ds, k, v):
|
||||
'''
|
||||
Splits the include line up into filename and parameters
|
||||
'''
|
||||
|
||||
# The include line must include at least one item, which is the filename
|
||||
# to include. Anything after that should be regarded as a parameter to the include
|
||||
items = split_args(v)
|
||||
if len(items) == 0:
|
||||
raise AnsibleParserError("include statements must specify the file name to include", obj=ds)
|
||||
else:
|
||||
# FIXME/TODO: validate that items[0] is a file, which also
|
||||
# exists and is readable
|
||||
new_ds['include'] = items[0]
|
||||
if len(items) > 1:
|
||||
# rejoin the parameter portion of the arguments and
|
||||
# then use parse_kv() to get a dict of params back
|
||||
params = parse_kv(" ".join(items[1:]))
|
||||
if 'vars' in new_ds:
|
||||
# FIXME: see fixme above regarding merging vars
|
||||
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
|
||||
new_ds['vars'] = params
|
||||
|
||||
def _munge_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:
|
||||
raise AnsibleError("duplicate loop in task: %s" % loop_name)
|
||||
new_ds['loop'] = loop_name
|
||||
new_ds['loop_args'] = v
|
||||
|
||||
|
||||
def _load_include(self, attr, ds):
|
||||
''' loads the file name specified in the ds and returns a list of tasks '''
|
||||
|
||||
data = self._loader.load_from_file(ds)
|
||||
if not isinstance(data, list):
|
||||
raise AnsibleParsingError("included task files must contain a list of tasks", obj=ds)
|
||||
|
||||
self._tasks = load_list_of_tasks(data, task_include=self, loader=self._loader)
|
||||
return ds
|
||||
|
|
82
v2/ansible/plugins/lookup/csvfile.py
Normal file
82
v2/ansible/plugins/lookup/csvfile.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
# (c) 2013, Jan-Piet Mens <jpmens(at)gmail.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
import codecs
|
||||
import csv
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def read_csv(self, filename, key, delimiter, dflt=None, col=1):
|
||||
|
||||
try:
|
||||
f = codecs.open(filename, 'r', encoding='utf-8')
|
||||
creader = csv.reader(f, delimiter=delimiter)
|
||||
|
||||
for row in creader:
|
||||
if row[0] == key:
|
||||
return row[int(col)]
|
||||
except Exception, e:
|
||||
raise errors.AnsibleError("csvfile: %s" % str(e))
|
||||
|
||||
return dflt
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
params = term.split()
|
||||
key = params[0]
|
||||
|
||||
paramvals = {
|
||||
'file' : 'ansible.csv',
|
||||
'default' : None,
|
||||
'delimiter' : "TAB",
|
||||
'col' : "1", # column to return
|
||||
}
|
||||
|
||||
# parameters specified?
|
||||
try:
|
||||
for param in params[1:]:
|
||||
name, value = param.split('=')
|
||||
assert(name in paramvals)
|
||||
paramvals[name] = value
|
||||
except (ValueError, AssertionError), e:
|
||||
raise errors.AnsibleError(e)
|
||||
|
||||
if paramvals['delimiter'] == 'TAB':
|
||||
paramvals['delimiter'] = "\t"
|
||||
|
||||
path = utils.path_dwim(self.basedir, paramvals['file'])
|
||||
|
||||
var = self.read_csv(path, key, paramvals['delimiter'], paramvals['default'], paramvals['col'])
|
||||
if var is not None:
|
||||
if type(var) is list:
|
||||
for v in var:
|
||||
ret.append(v)
|
||||
else:
|
||||
ret.append(var)
|
||||
return ret
|
39
v2/ansible/plugins/lookup/dict.py
Normal file
39
v2/ansible/plugins/lookup/dict.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# (c) 2014, Kent R. Spillner <kspillner@acm.org>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
|
||||
def flatten_hash_to_list(terms):
|
||||
ret = []
|
||||
for key in terms:
|
||||
ret.append({'key': key, 'value': terms[key]})
|
||||
return ret
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, dict):
|
||||
raise errors.AnsibleError("with_dict expects a dict")
|
||||
|
||||
return flatten_hash_to_list(terms)
|
68
v2/ansible/plugins/lookup/dnstxt.py
Normal file
68
v2/ansible/plugins/lookup/dnstxt.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
HAVE_DNS=False
|
||||
try:
|
||||
import dns.resolver
|
||||
from dns.exception import DNSException
|
||||
HAVE_DNS=True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# ==============================================================
|
||||
# DNSTXT: DNS TXT records
|
||||
#
|
||||
# key=domainname
|
||||
# TODO: configurable resolver IPs
|
||||
# --------------------------------------------------------------
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
if HAVE_DNS == False:
|
||||
raise errors.AnsibleError("Can't LOOKUP(dnstxt): module dns.resolver is not installed")
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
domain = term.split()[0]
|
||||
string = []
|
||||
try:
|
||||
answers = dns.resolver.query(domain, 'TXT')
|
||||
for rdata in answers:
|
||||
s = rdata.to_text()
|
||||
string.append(s[1:-1]) # Strip outside quotes on TXT rdata
|
||||
|
||||
except dns.resolver.NXDOMAIN:
|
||||
string = 'NXDOMAIN'
|
||||
except dns.resolver.Timeout:
|
||||
string = ''
|
||||
except dns.exception.DNSException, e:
|
||||
raise errors.AnsibleError("dns.resolver unhandled exception", e)
|
||||
|
||||
ret.append(''.join(string))
|
||||
return ret
|
41
v2/ansible/plugins/lookup/env.py
Normal file
41
v2/ansible/plugins/lookup/env.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
from ansible.utils import template
|
||||
import os
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
try:
|
||||
terms = template.template(self.basedir, terms, inject)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
var = term.split()[0]
|
||||
ret.append(os.getenv(var, ''))
|
||||
return ret
|
78
v2/ansible/plugins/lookup/etcd.py
Normal file
78
v2/ansible/plugins/lookup/etcd.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# (c) 2013, Jan-Piet Mens <jpmens(at)gmail.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/>.
|
||||
|
||||
from ansible import utils
|
||||
import os
|
||||
import urllib2
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
# this can be made configurable, not should not use ansible.cfg
|
||||
ANSIBLE_ETCD_URL = 'http://127.0.0.1:4001'
|
||||
if os.getenv('ANSIBLE_ETCD_URL') is not None:
|
||||
ANSIBLE_ETCD_URL = os.environ['ANSIBLE_ETCD_URL']
|
||||
|
||||
class etcd():
|
||||
def __init__(self, url=ANSIBLE_ETCD_URL):
|
||||
self.url = url
|
||||
self.baseurl = '%s/v1/keys' % (self.url)
|
||||
|
||||
def get(self, key):
|
||||
url = "%s/%s" % (self.baseurl, key)
|
||||
|
||||
data = None
|
||||
value = ""
|
||||
try:
|
||||
r = urllib2.urlopen(url)
|
||||
data = r.read()
|
||||
except:
|
||||
return value
|
||||
|
||||
try:
|
||||
# {"action":"get","key":"/name","value":"Jane Jolie","index":5}
|
||||
item = json.loads(data)
|
||||
if 'value' in item:
|
||||
value = item['value']
|
||||
if 'errorCode' in item:
|
||||
value = "ENOENT"
|
||||
except:
|
||||
raise
|
||||
pass
|
||||
|
||||
return value
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
self.etcd = etcd()
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
key = term.split()[0]
|
||||
value = self.etcd.get(key)
|
||||
ret.append(value)
|
||||
return ret
|
59
v2/ansible/plugins/lookup/file.py
Normal file
59
v2/ansible/plugins/lookup/file.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
import codecs
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
ret = []
|
||||
|
||||
# this can happen if the variable contains a string, strictly not desired for lookup
|
||||
# plugins, but users may try it, so make it work.
|
||||
if not isinstance(terms, list):
|
||||
terms = [ terms ]
|
||||
|
||||
for term in terms:
|
||||
basedir_path = utils.path_dwim(self.basedir, term)
|
||||
relative_path = None
|
||||
playbook_path = None
|
||||
|
||||
# Special handling of the file lookup, used primarily when the
|
||||
# lookup is done from a role. If the file isn't found in the
|
||||
# basedir of the current file, use dwim_relative to look in the
|
||||
# role/files/ directory, and finally the playbook directory
|
||||
# itself (which will be relative to the current working dir)
|
||||
if '_original_file' in inject:
|
||||
relative_path = utils.path_dwim_relative(inject['_original_file'], 'files', term, self.basedir, check=False)
|
||||
if 'playbook_dir' in inject:
|
||||
playbook_path = os.path.join(inject['playbook_dir'], term)
|
||||
|
||||
for path in (basedir_path, relative_path, playbook_path):
|
||||
if path and os.path.exists(path):
|
||||
ret.append(codecs.open(path, encoding="utf8").read().rstrip())
|
||||
break
|
||||
else:
|
||||
raise errors.AnsibleError("could not locate file in lookup: %s" % term)
|
||||
|
||||
return ret
|
39
v2/ansible/plugins/lookup/fileglob.py
Normal file
39
v2/ansible/plugins/lookup/fileglob.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
import os
|
||||
import glob
|
||||
from ansible import utils
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
ret = []
|
||||
|
||||
for term in terms:
|
||||
|
||||
dwimmed = utils.path_dwim(self.basedir, term)
|
||||
globbed = glob.glob(dwimmed)
|
||||
ret.extend(g for g in globbed if os.path.isfile(g))
|
||||
|
||||
return ret
|
194
v2/ansible/plugins/lookup/first_found.py
Normal file
194
v2/ansible/plugins/lookup/first_found.py
Normal file
|
@ -0,0 +1,194 @@
|
|||
# (c) 2013, seth vidal <skvidal@fedoraproject.org> red hat, inc
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
# take a list of files and (optionally) a list of paths
|
||||
# return the first existing file found in the paths
|
||||
# [file1, file2, file3], [path1, path2, path3]
|
||||
# search order is:
|
||||
# path1/file1
|
||||
# path1/file2
|
||||
# path1/file3
|
||||
# path2/file1
|
||||
# path2/file2
|
||||
# path2/file3
|
||||
# path3/file1
|
||||
# path3/file2
|
||||
# path3/file3
|
||||
|
||||
# first file found with os.path.exists() is returned
|
||||
# no file matches raises ansibleerror
|
||||
# EXAMPLES
|
||||
# - name: copy first existing file found to /some/file
|
||||
# action: copy src=$item dest=/some/file
|
||||
# with_first_found:
|
||||
# - files: foo ${inventory_hostname} bar
|
||||
# paths: /tmp/production /tmp/staging
|
||||
|
||||
# that will look for files in this order:
|
||||
# /tmp/production/foo
|
||||
# ${inventory_hostname}
|
||||
# bar
|
||||
# /tmp/staging/foo
|
||||
# ${inventory_hostname}
|
||||
# bar
|
||||
|
||||
# - name: copy first existing file found to /some/file
|
||||
# action: copy src=$item dest=/some/file
|
||||
# with_first_found:
|
||||
# - files: /some/place/foo ${inventory_hostname} /some/place/else
|
||||
|
||||
# that will look for files in this order:
|
||||
# /some/place/foo
|
||||
# $relative_path/${inventory_hostname}
|
||||
# /some/place/else
|
||||
|
||||
# example - including tasks:
|
||||
# tasks:
|
||||
# - include: $item
|
||||
# with_first_found:
|
||||
# - files: generic
|
||||
# paths: tasks/staging tasks/production
|
||||
# this will include the tasks in the file generic where it is found first (staging or production)
|
||||
|
||||
# example simple file lists
|
||||
#tasks:
|
||||
#- name: first found file
|
||||
# action: copy src=$item dest=/etc/file.cfg
|
||||
# with_first_found:
|
||||
# - files: foo.${inventory_hostname} foo
|
||||
|
||||
|
||||
# example skipping if no matched files
|
||||
# First_found also offers the ability to control whether or not failing
|
||||
# to find a file returns an error or not
|
||||
#
|
||||
#- name: first found file - or skip
|
||||
# action: copy src=$item dest=/etc/file.cfg
|
||||
# with_first_found:
|
||||
# - files: foo.${inventory_hostname}
|
||||
# skip: true
|
||||
|
||||
# example a role with default configuration and configuration per host
|
||||
# you can set multiple terms with their own files and paths to look through.
|
||||
# consider a role that sets some configuration per host falling back on a default config.
|
||||
#
|
||||
#- name: some configuration template
|
||||
# template: src={{ item }} dest=/etc/file.cfg mode=0444 owner=root group=root
|
||||
# with_first_found:
|
||||
# - files:
|
||||
# - ${inventory_hostname}/etc/file.cfg
|
||||
# paths:
|
||||
# - ../../../templates.overwrites
|
||||
# - ../../../templates
|
||||
# - files:
|
||||
# - etc/file.cfg
|
||||
# paths:
|
||||
# - templates
|
||||
|
||||
# the above will return an empty list if the files cannot be found at all
|
||||
# if skip is unspecificed or if it is set to false then it will return a list
|
||||
# error which can be caught bye ignore_errors: true for that action.
|
||||
|
||||
# finally - if you want you can use it, in place to replace first_available_file:
|
||||
# you simply cannot use the - files, path or skip options. simply replace
|
||||
# first_available_file with with_first_found and leave the file listing in place
|
||||
#
|
||||
#
|
||||
# - name: with_first_found like first_available_file
|
||||
# action: copy src=$item dest=/tmp/faftest
|
||||
# with_first_found:
|
||||
# - ../files/foo
|
||||
# - ../files/bar
|
||||
# - ../files/baz
|
||||
# ignore_errors: true
|
||||
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
result = None
|
||||
anydict = False
|
||||
skip = False
|
||||
|
||||
for term in terms:
|
||||
if isinstance(term, dict):
|
||||
anydict = True
|
||||
|
||||
total_search = []
|
||||
if anydict:
|
||||
for term in terms:
|
||||
if isinstance(term, dict):
|
||||
files = term.get('files', [])
|
||||
paths = term.get('paths', [])
|
||||
skip = utils.boolean(term.get('skip', False))
|
||||
|
||||
filelist = files
|
||||
if isinstance(files, basestring):
|
||||
files = files.replace(',', ' ')
|
||||
files = files.replace(';', ' ')
|
||||
filelist = files.split(' ')
|
||||
|
||||
pathlist = paths
|
||||
if paths:
|
||||
if isinstance(paths, basestring):
|
||||
paths = paths.replace(',', ' ')
|
||||
paths = paths.replace(':', ' ')
|
||||
paths = paths.replace(';', ' ')
|
||||
pathlist = paths.split(' ')
|
||||
|
||||
if not pathlist:
|
||||
total_search = filelist
|
||||
else:
|
||||
for path in pathlist:
|
||||
for fn in filelist:
|
||||
f = os.path.join(path, fn)
|
||||
total_search.append(f)
|
||||
else:
|
||||
total_search.append(term)
|
||||
else:
|
||||
total_search = terms
|
||||
|
||||
for fn in total_search:
|
||||
if inject and '_original_file' in inject:
|
||||
# check the templates and vars directories too,
|
||||
# if they exist
|
||||
for roledir in ('templates', 'vars'):
|
||||
path = utils.path_dwim(os.path.join(self.basedir, '..', roledir), fn)
|
||||
if os.path.exists(path):
|
||||
return [path]
|
||||
# if none of the above were found, just check the
|
||||
# current filename against the basedir (this will already
|
||||
# have ../files from runner, if it's a role task
|
||||
path = utils.path_dwim(self.basedir, fn)
|
||||
if os.path.exists(path):
|
||||
return [path]
|
||||
else:
|
||||
if skip:
|
||||
return []
|
||||
else:
|
||||
return [None]
|
||||
|
78
v2/ansible/plugins/lookup/flattened.py
Normal file
78
v2/ansible/plugins/lookup/flattened.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
|
||||
|
||||
def check_list_of_one_list(term):
|
||||
# make sure term is not a list of one (list of one..) item
|
||||
# return the final non list item if so
|
||||
|
||||
if isinstance(term,list) and len(term) == 1:
|
||||
term = term[0]
|
||||
if isinstance(term,list):
|
||||
term = check_list_of_one_list(term)
|
||||
|
||||
return term
|
||||
|
||||
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
|
||||
def flatten(self, terms, inject):
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
term = check_list_of_one_list(term)
|
||||
|
||||
if term == 'None' or term == 'null':
|
||||
# ignore undefined items
|
||||
break
|
||||
|
||||
if isinstance(term, basestring):
|
||||
# convert a variable to a list
|
||||
term2 = utils.listify_lookup_plugin_terms(term, self.basedir, inject)
|
||||
# but avoid converting a plain string to a list of one string
|
||||
if term2 != [ term ]:
|
||||
term = term2
|
||||
|
||||
if isinstance(term, list):
|
||||
# if it's a list, check recursively for items that are a list
|
||||
term = self.flatten(term, inject)
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
# see if the string represents a list and convert to list if so
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, list):
|
||||
raise errors.AnsibleError("with_flattened expects a list")
|
||||
|
||||
ret = self.flatten(terms, inject)
|
||||
return ret
|
||||
|
44
v2/ansible/plugins/lookup/indexed_items.py
Normal file
44
v2/ansible/plugins/lookup/indexed_items.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
|
||||
def flatten(terms):
|
||||
ret = []
|
||||
for term in terms:
|
||||
if isinstance(term, list):
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
return ret
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, list):
|
||||
raise errors.AnsibleError("with_indexed_items expects a list")
|
||||
|
||||
items = flatten(terms)
|
||||
return zip(range(len(items)), items)
|
||||
|
48
v2/ansible/plugins/lookup/inventory_hostnames.py
Normal file
48
v2/ansible/plugins/lookup/inventory_hostnames.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2013, Steven Dossett <sdossett@panath.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/>.
|
||||
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
import ansible.inventory as inventory
|
||||
|
||||
def flatten(terms):
|
||||
ret = []
|
||||
for term in terms:
|
||||
if isinstance(term, list):
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
return ret
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
if 'runner' in kwargs:
|
||||
self.host_list = kwargs['runner'].inventory.host_list
|
||||
else:
|
||||
raise errors.AnsibleError("inventory_hostnames must be used as a loop. Example: \"with_inventory_hostnames: \'all\'\"")
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, list):
|
||||
raise errors.AnsibleError("with_inventory_hostnames expects a list")
|
||||
return flatten(inventory.Inventory(self.host_list).list_hosts(terms))
|
||||
|
44
v2/ansible/plugins/lookup/items.py
Normal file
44
v2/ansible/plugins/lookup/items.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
|
||||
def flatten(terms):
|
||||
ret = []
|
||||
for term in terms:
|
||||
if isinstance(term, list):
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
return ret
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, list) and not isinstance(terms,set):
|
||||
raise errors.AnsibleError("with_items expects a list or a set")
|
||||
|
||||
return flatten(terms)
|
||||
|
||||
|
38
v2/ansible/plugins/lookup/lines.py
Normal file
38
v2/ansible/plugins/lookup/lines.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.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/>.
|
||||
|
||||
import subprocess
|
||||
from ansible import utils, errors
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
p = subprocess.Popen(term, cwd=self.basedir, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
if p.returncode == 0:
|
||||
ret.extend(stdout.splitlines())
|
||||
else:
|
||||
raise errors.AnsibleError("lookup_plugin.lines(%s) returned %d" % (term, p.returncode))
|
||||
return ret
|
73
v2/ansible/plugins/lookup/nested.py
Normal file
73
v2/ansible/plugins/lookup/nested.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
import ansible.utils as utils
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.errors as errors
|
||||
|
||||
def flatten(terms):
|
||||
ret = []
|
||||
for term in terms:
|
||||
if isinstance(term, list):
|
||||
ret.extend(term)
|
||||
elif isinstance(term, tuple):
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
return ret
|
||||
|
||||
def combine(a,b):
|
||||
results = []
|
||||
for x in a:
|
||||
for y in b:
|
||||
results.append(flatten([x,y]))
|
||||
return results
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def __lookup_injects(self, terms, inject):
|
||||
results = []
|
||||
for x in terms:
|
||||
intermediate = utils.listify_lookup_plugin_terms(x, self.basedir, inject)
|
||||
results.append(intermediate)
|
||||
return results
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
# this code is common with 'items.py' consider moving to utils if we need it again
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
terms = self.__lookup_injects(terms, inject)
|
||||
|
||||
my_list = terms[:]
|
||||
my_list.reverse()
|
||||
result = []
|
||||
if len(my_list) == 0:
|
||||
raise errors.AnsibleError("with_nested requires at least one element in the nested list")
|
||||
result = my_list.pop()
|
||||
while len(my_list) > 0:
|
||||
result2 = combine(result, my_list.pop())
|
||||
result = result2
|
||||
new_result = []
|
||||
for x in result:
|
||||
new_result.append(flatten(x))
|
||||
return new_result
|
||||
|
||||
|
129
v2/ansible/plugins/lookup/password.py
Normal file
129
v2/ansible/plugins/lookup/password.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.com>
|
||||
# (c) 2013, Javier Candeira <javier@candeira.com>
|
||||
# (c) 2013, Maykel Moya <mmoya@speedyrails.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
import errno
|
||||
from string import ascii_letters, digits
|
||||
import string
|
||||
import random
|
||||
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
LENGTH = 20
|
||||
|
||||
def __init__(self, length=None, encrypt=None, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def random_salt(self):
|
||||
salt_chars = ascii_letters + digits + './'
|
||||
return utils.random_password(length=8, chars=salt_chars)
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
ret = []
|
||||
|
||||
for term in terms:
|
||||
# you can't have escaped spaces in yor pathname
|
||||
params = term.split()
|
||||
relpath = params[0]
|
||||
|
||||
paramvals = {
|
||||
'length': LookupModule.LENGTH,
|
||||
'encrypt': None,
|
||||
'chars': ['ascii_letters','digits',".,:-_"],
|
||||
}
|
||||
|
||||
# get non-default parameters if specified
|
||||
try:
|
||||
for param in params[1:]:
|
||||
name, value = param.split('=')
|
||||
assert(name in paramvals)
|
||||
if name == 'length':
|
||||
paramvals[name] = int(value)
|
||||
elif name == 'chars':
|
||||
use_chars=[]
|
||||
if ",," in value:
|
||||
use_chars.append(',')
|
||||
use_chars.extend(value.replace(',,',',').split(','))
|
||||
paramvals['chars'] = use_chars
|
||||
else:
|
||||
paramvals[name] = value
|
||||
except (ValueError, AssertionError), e:
|
||||
raise errors.AnsibleError(e)
|
||||
|
||||
length = paramvals['length']
|
||||
encrypt = paramvals['encrypt']
|
||||
use_chars = paramvals['chars']
|
||||
|
||||
# get password or create it if file doesn't exist
|
||||
path = utils.path_dwim(self.basedir, relpath)
|
||||
if not os.path.exists(path):
|
||||
pathdir = os.path.dirname(path)
|
||||
if not os.path.isdir(pathdir):
|
||||
try:
|
||||
os.makedirs(pathdir, mode=0700)
|
||||
except OSError, e:
|
||||
raise errors.AnsibleError("cannot create the path for the password lookup: %s (error was %s)" % (pathdir, str(e)))
|
||||
|
||||
chars = "".join([getattr(string,c,c) for c in use_chars]).replace('"','').replace("'",'')
|
||||
password = ''.join(random.choice(chars) for _ in range(length))
|
||||
|
||||
if encrypt is not None:
|
||||
salt = self.random_salt()
|
||||
content = '%s salt=%s' % (password, salt)
|
||||
else:
|
||||
content = password
|
||||
with open(path, 'w') as f:
|
||||
os.chmod(path, 0600)
|
||||
f.write(content + '\n')
|
||||
else:
|
||||
content = open(path).read().rstrip()
|
||||
sep = content.find(' ')
|
||||
|
||||
if sep >= 0:
|
||||
password = content[:sep]
|
||||
salt = content[sep+1:].split('=')[1]
|
||||
else:
|
||||
password = content
|
||||
salt = None
|
||||
|
||||
# crypt requested, add salt if missing
|
||||
if (encrypt is not None and not salt):
|
||||
salt = self.random_salt()
|
||||
content = '%s salt=%s' % (password, salt)
|
||||
with open(path, 'w') as f:
|
||||
os.chmod(path, 0600)
|
||||
f.write(content + '\n')
|
||||
# crypt not requested, remove salt if present
|
||||
elif (encrypt is None and salt):
|
||||
with open(path, 'w') as f:
|
||||
os.chmod(path, 0600)
|
||||
f.write(password + '\n')
|
||||
|
||||
if encrypt:
|
||||
password = utils.do_encrypt(password, encrypt, salt=salt)
|
||||
|
||||
ret.append(password)
|
||||
|
||||
return ret
|
||||
|
52
v2/ansible/plugins/lookup/pipe.py
Normal file
52
v2/ansible/plugins/lookup/pipe.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
# (c) 2012, Daniel Hokka Zakrisson <daniel@hozac.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/>.
|
||||
|
||||
import subprocess
|
||||
from ansible import utils, errors
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
'''
|
||||
http://docs.python.org/2/library/subprocess.html#popen-constructor
|
||||
|
||||
The shell argument (which defaults to False) specifies whether to use the
|
||||
shell as the program to execute. If shell is True, it is recommended to pass
|
||||
args as a string rather than as a sequence
|
||||
|
||||
https://github.com/ansible/ansible/issues/6550
|
||||
'''
|
||||
term = str(term)
|
||||
|
||||
p = subprocess.Popen(term, cwd=self.basedir, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
if p.returncode == 0:
|
||||
ret.append(stdout.decode("utf-8").rstrip())
|
||||
else:
|
||||
raise errors.AnsibleError("lookup_plugin.pipe(%s) returned %d" % (term, p.returncode))
|
||||
return ret
|
41
v2/ansible/plugins/lookup/random_choice.py
Normal file
41
v2/ansible/plugins/lookup/random_choice.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# (c) 2013, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
import random
|
||||
from ansible import utils
|
||||
|
||||
# useful for introducing chaos ... or just somewhat reasonably fair selection
|
||||
# amongst available mirrors
|
||||
#
|
||||
# tasks:
|
||||
# - debug: msg=$item
|
||||
# with_random_choice:
|
||||
# - one
|
||||
# - two
|
||||
# - three
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
return [ random.choice(terms) ]
|
||||
|
72
v2/ansible/plugins/lookup/redis_kv.py
Normal file
72
v2/ansible/plugins/lookup/redis_kv.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.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/>.
|
||||
|
||||
from ansible import utils, errors
|
||||
import os
|
||||
HAVE_REDIS=False
|
||||
try:
|
||||
import redis # https://github.com/andymccurdy/redis-py/
|
||||
HAVE_REDIS=True
|
||||
except ImportError:
|
||||
pass
|
||||
import re
|
||||
|
||||
# ==============================================================
|
||||
# REDISGET: Obtain value from a GET on a Redis key. Terms
|
||||
# expected: 0 = URL, 1 = Key
|
||||
# URL may be empty, in which case redis://localhost:6379 assumed
|
||||
# --------------------------------------------------------------
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
if HAVE_REDIS == False:
|
||||
raise errors.AnsibleError("Can't LOOKUP(redis_kv): module redis is not installed")
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
(url,key) = term.split(',')
|
||||
if url == "":
|
||||
url = 'redis://localhost:6379'
|
||||
|
||||
# urlsplit on Python 2.6.1 is broken. Hmm. Probably also the reason
|
||||
# Redis' from_url() doesn't work here.
|
||||
|
||||
p = '(?P<scheme>[^:]+)://?(?P<host>[^:/ ]+).?(?P<port>[0-9]*).*'
|
||||
|
||||
try:
|
||||
m = re.search(p, url)
|
||||
host = m.group('host')
|
||||
port = int(m.group('port'))
|
||||
except AttributeError:
|
||||
raise errors.AnsibleError("Bad URI in redis lookup")
|
||||
|
||||
try:
|
||||
conn = redis.Redis(host=host, port=port)
|
||||
res = conn.get(key)
|
||||
if res is None:
|
||||
res = ""
|
||||
ret.append(res)
|
||||
except:
|
||||
ret.append("") # connection failed or key not found
|
||||
return ret
|
204
v2/ansible/plugins/lookup/sequence.py
Normal file
204
v2/ansible/plugins/lookup/sequence.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
# (c) 2013, Jayson Vantuyl <jayson@aggressive.ly>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
import ansible.utils as utils
|
||||
from re import compile as re_compile, IGNORECASE
|
||||
|
||||
# shortcut format
|
||||
NUM = "(0?x?[0-9a-f]+)"
|
||||
SHORTCUT = re_compile(
|
||||
"^(" + # Group 0
|
||||
NUM + # Group 1: Start
|
||||
"-)?" +
|
||||
NUM + # Group 2: End
|
||||
"(/" + # Group 3
|
||||
NUM + # Group 4: Stride
|
||||
")?" +
|
||||
"(:(.+))?$", # Group 5, Group 6: Format String
|
||||
IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
class LookupModule(object):
|
||||
"""
|
||||
sequence lookup module
|
||||
|
||||
Used to generate some sequence of items. Takes arguments in two forms.
|
||||
|
||||
The simple / shortcut form is:
|
||||
|
||||
[start-]end[/stride][:format]
|
||||
|
||||
As indicated by the brackets: start, stride, and format string are all
|
||||
optional. The format string is in the style of printf. This can be used
|
||||
to pad with zeros, format in hexadecimal, etc. All of the numerical values
|
||||
can be specified in octal (i.e. 0664) or hexadecimal (i.e. 0x3f8).
|
||||
Negative numbers are not supported.
|
||||
|
||||
Some examples:
|
||||
|
||||
5 -> ["1","2","3","4","5"]
|
||||
5-8 -> ["5", "6", "7", "8"]
|
||||
2-10/2 -> ["2", "4", "6", "8", "10"]
|
||||
4:host%02d -> ["host01","host02","host03","host04"]
|
||||
|
||||
The standard Ansible key-value form is accepted as well. For example:
|
||||
|
||||
start=5 end=11 stride=2 format=0x%02x -> ["0x05","0x07","0x09","0x0a"]
|
||||
|
||||
This format takes an alternate form of "end" called "count", which counts
|
||||
some number from the starting value. For example:
|
||||
|
||||
count=5 -> ["1", "2", "3", "4", "5"]
|
||||
start=0x0f00 count=4 format=%04x -> ["0f00", "0f01", "0f02", "0f03"]
|
||||
start=0 count=5 stride=2 -> ["0", "2", "4", "6", "8"]
|
||||
start=1 count=5 stride=2 -> ["1", "3", "5", "7", "9"]
|
||||
|
||||
The count option is mostly useful for avoiding off-by-one errors and errors
|
||||
calculating the number of entries in a sequence when a stride is specified.
|
||||
"""
|
||||
|
||||
def __init__(self, basedir, **kwargs):
|
||||
"""absorb any keyword args"""
|
||||
self.basedir = basedir
|
||||
|
||||
def reset(self):
|
||||
"""set sensible defaults"""
|
||||
self.start = 1
|
||||
self.count = None
|
||||
self.end = None
|
||||
self.stride = 1
|
||||
self.format = "%d"
|
||||
|
||||
def parse_kv_args(self, args):
|
||||
"""parse key-value style arguments"""
|
||||
for arg in ["start", "end", "count", "stride"]:
|
||||
try:
|
||||
arg_raw = args.pop(arg, None)
|
||||
if arg_raw is None:
|
||||
continue
|
||||
arg_cooked = int(arg_raw, 0)
|
||||
setattr(self, arg, arg_cooked)
|
||||
except ValueError:
|
||||
raise AnsibleError(
|
||||
"can't parse arg %s=%r as integer"
|
||||
% (arg, arg_raw)
|
||||
)
|
||||
if 'format' in args:
|
||||
self.format = args.pop("format")
|
||||
if args:
|
||||
raise AnsibleError(
|
||||
"unrecognized arguments to with_sequence: %r"
|
||||
% args.keys()
|
||||
)
|
||||
|
||||
def parse_simple_args(self, term):
|
||||
"""parse the shortcut forms, return True/False"""
|
||||
match = SHORTCUT.match(term)
|
||||
if not match:
|
||||
return False
|
||||
|
||||
_, start, end, _, stride, _, format = match.groups()
|
||||
|
||||
if start is not None:
|
||||
try:
|
||||
start = int(start, 0)
|
||||
except ValueError:
|
||||
raise AnsibleError("can't parse start=%s as integer" % start)
|
||||
if end is not None:
|
||||
try:
|
||||
end = int(end, 0)
|
||||
except ValueError:
|
||||
raise AnsibleError("can't parse end=%s as integer" % end)
|
||||
if stride is not None:
|
||||
try:
|
||||
stride = int(stride, 0)
|
||||
except ValueError:
|
||||
raise AnsibleError("can't parse stride=%s as integer" % stride)
|
||||
|
||||
if start is not None:
|
||||
self.start = start
|
||||
if end is not None:
|
||||
self.end = end
|
||||
if stride is not None:
|
||||
self.stride = stride
|
||||
if format is not None:
|
||||
self.format = format
|
||||
|
||||
def sanity_check(self):
|
||||
if self.count is None and self.end is None:
|
||||
raise AnsibleError(
|
||||
"must specify count or end in with_sequence"
|
||||
)
|
||||
elif self.count is not None and self.end is not None:
|
||||
raise AnsibleError(
|
||||
"can't specify both count and end in with_sequence"
|
||||
)
|
||||
elif self.count is not None:
|
||||
# convert count to end
|
||||
self.end = self.start + self.count * self.stride - 1
|
||||
del self.count
|
||||
if self.end < self.start:
|
||||
raise AnsibleError("can't count backwards")
|
||||
if self.format.count('%') != 1:
|
||||
raise AnsibleError("bad formatting string: %s" % self.format)
|
||||
|
||||
def generate_sequence(self):
|
||||
numbers = xrange(self.start, self.end + 1, self.stride)
|
||||
|
||||
for i in numbers:
|
||||
try:
|
||||
formatted = self.format % i
|
||||
yield formatted
|
||||
except (ValueError, TypeError):
|
||||
raise AnsibleError(
|
||||
"problem formatting %r with %r" % self.format
|
||||
)
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
results = []
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
if isinstance(terms, basestring):
|
||||
terms = [ terms ]
|
||||
|
||||
for term in terms:
|
||||
try:
|
||||
self.reset() # clear out things for this iteration
|
||||
|
||||
try:
|
||||
if not self.parse_simple_args(term):
|
||||
self.parse_kv_args(utils.parse_kv(term))
|
||||
except Exception:
|
||||
raise AnsibleError(
|
||||
"unknown error parsing with_sequence arguments: %r"
|
||||
% term
|
||||
)
|
||||
|
||||
self.sanity_check()
|
||||
|
||||
results.extend(self.generate_sequence())
|
||||
except AnsibleError:
|
||||
raise
|
||||
except Exception:
|
||||
raise AnsibleError(
|
||||
"unknown error generating sequence"
|
||||
)
|
||||
|
||||
return results
|
67
v2/ansible/plugins/lookup/subelements.py
Normal file
67
v2/ansible/plugins/lookup/subelements.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import ansible.utils as utils
|
||||
import ansible.errors as errors
|
||||
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
terms[0] = utils.listify_lookup_plugin_terms(terms[0], self.basedir, inject)
|
||||
|
||||
if not isinstance(terms, list) or not len(terms) == 2:
|
||||
raise errors.AnsibleError(
|
||||
"subelements lookup expects a list of two items, first a dict or a list, and second a string")
|
||||
terms[0] = utils.listify_lookup_plugin_terms(terms[0], self.basedir, inject)
|
||||
if not isinstance(terms[0], (list, dict)) or not isinstance(terms[1], basestring):
|
||||
raise errors.AnsibleError(
|
||||
"subelements lookup expects a list of two items, first a dict or a list, and second a string")
|
||||
|
||||
if isinstance(terms[0], dict): # convert to list:
|
||||
if terms[0].get('skipped',False) != False:
|
||||
# the registered result was completely skipped
|
||||
return []
|
||||
elementlist = []
|
||||
for key in terms[0].iterkeys():
|
||||
elementlist.append(terms[0][key])
|
||||
else:
|
||||
elementlist = terms[0]
|
||||
subelement = terms[1]
|
||||
|
||||
ret = []
|
||||
for item0 in elementlist:
|
||||
if not isinstance(item0, dict):
|
||||
raise errors.AnsibleError("subelements lookup expects a dictionary, got '%s'" %item0)
|
||||
if item0.get('skipped',False) != False:
|
||||
# this particular item is to be skipped
|
||||
continue
|
||||
if not subelement in item0:
|
||||
raise errors.AnsibleError("could not find '%s' key in iterated item '%s'" % (subelement, item0))
|
||||
if not isinstance(item0[subelement], list):
|
||||
raise errors.AnsibleError("the key %s should point to a list, got '%s'" % (subelement, item0[subelement]))
|
||||
sublist = item0.pop(subelement, [])
|
||||
for item1 in sublist:
|
||||
ret.append((item0, item1))
|
||||
|
||||
return ret
|
||||
|
33
v2/ansible/plugins/lookup/template.py
Normal file
33
v2/ansible/plugins/lookup/template.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
from ansible.utils import template
|
||||
import ansible.utils as utils
|
||||
|
||||
class LookupModule(object):
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
|
||||
ret = []
|
||||
for term in terms:
|
||||
ret.append(template.template_from_file(self.basedir, term, inject))
|
||||
return ret
|
64
v2/ansible/plugins/lookup/together.py
Normal file
64
v2/ansible/plugins/lookup/together.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# (c) 2013, Bradley Young <young.bradley@gmail.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/>.
|
||||
|
||||
import ansible.utils as utils
|
||||
from ansible.utils import safe_eval
|
||||
import ansible.errors as errors
|
||||
from itertools import izip_longest
|
||||
|
||||
def flatten(terms):
|
||||
ret = []
|
||||
for term in terms:
|
||||
if isinstance(term, list):
|
||||
ret.extend(term)
|
||||
elif isinstance(term, tuple):
|
||||
ret.extend(term)
|
||||
else:
|
||||
ret.append(term)
|
||||
return ret
|
||||
|
||||
class LookupModule(object):
|
||||
"""
|
||||
Transpose a list of arrays:
|
||||
[1, 2, 3], [4, 5, 6] -> [1, 4], [2, 5], [3, 6]
|
||||
Replace any empty spots in 2nd array with None:
|
||||
[1, 2], [3] -> [1, 3], [2, None]
|
||||
"""
|
||||
|
||||
def __init__(self, basedir=None, **kwargs):
|
||||
self.basedir = basedir
|
||||
|
||||
def __lookup_injects(self, terms, inject):
|
||||
results = []
|
||||
for x in terms:
|
||||
intermediate = utils.listify_lookup_plugin_terms(x, self.basedir, inject)
|
||||
results.append(intermediate)
|
||||
return results
|
||||
|
||||
def run(self, terms, inject=None, **kwargs):
|
||||
|
||||
# this code is common with 'items.py' consider moving to utils if we need it again
|
||||
|
||||
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
|
||||
terms = self.__lookup_injects(terms, inject)
|
||||
|
||||
my_list = terms[:]
|
||||
if len(my_list) == 0:
|
||||
raise errors.AnsibleError("with_together requires at least one element in each list")
|
||||
return [flatten(x) for x in izip_longest(*my_list, fillvalue=None)]
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ class TestErrors(unittest.TestCase):
|
|||
mock_method.return_value = ('this is line 1\n', '')
|
||||
e = AnsibleError(self.message, self.obj)
|
||||
|
||||
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 1, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\nthis is line 1\n^\n")
|
||||
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 1, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nthis is line 1\n^\n")
|
||||
|
||||
def test_get_error_lines_from_file(self):
|
||||
m = mock_open()
|
||||
|
@ -63,12 +63,12 @@ class TestErrors(unittest.TestCase):
|
|||
self.obj._line_number = 1
|
||||
self.obj._column_number = 1
|
||||
e = AnsibleError(self.message, self.obj)
|
||||
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 1, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\nthis is line 1\n^\n")
|
||||
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 1, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nthis is line 1\n^\n")
|
||||
|
||||
# this line will not be found, as it is out of the index range
|
||||
self.obj._data_source = 'foo.yml'
|
||||
self.obj._line_number = 2
|
||||
self.obj._column_number = 1
|
||||
e = AnsibleError(self.message, self.obj)
|
||||
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 2, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")
|
||||
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 2, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ class TestModArgsDwim(unittest.TestCase):
|
|||
# and the task knows the line numbers
|
||||
|
||||
def setUp(self):
|
||||
self.m = ModuleArgsParser()
|
||||
pass
|
||||
|
||||
def _debug(self, mod, args, to):
|
||||
|
@ -43,7 +42,8 @@ class TestModArgsDwim(unittest.TestCase):
|
|||
pass
|
||||
|
||||
def test_basic_shell(self):
|
||||
mod, args, to = self.m.parse(dict(shell='echo hi'))
|
||||
m = ModuleArgsParser(dict(shell='echo hi'))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'command')
|
||||
self.assertEqual(args, dict(
|
||||
|
@ -53,7 +53,8 @@ class TestModArgsDwim(unittest.TestCase):
|
|||
self.assertIsNone(to)
|
||||
|
||||
def test_basic_command(self):
|
||||
mod, args, to = self.m.parse(dict(command='echo hi'))
|
||||
m = ModuleArgsParser(dict(command='echo hi'))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'command')
|
||||
self.assertEqual(args, dict(
|
||||
|
@ -62,7 +63,8 @@ class TestModArgsDwim(unittest.TestCase):
|
|||
self.assertIsNone(to)
|
||||
|
||||
def test_shell_with_modifiers(self):
|
||||
mod, args, to = self.m.parse(dict(shell='/bin/foo creates=/tmp/baz removes=/tmp/bleep'))
|
||||
m = ModuleArgsParser(dict(shell='/bin/foo creates=/tmp/baz removes=/tmp/bleep'))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'command')
|
||||
self.assertEqual(args, dict(
|
||||
|
@ -74,42 +76,55 @@ class TestModArgsDwim(unittest.TestCase):
|
|||
self.assertIsNone(to)
|
||||
|
||||
def test_normal_usage(self):
|
||||
mod, args, to = self.m.parse(dict(copy='src=a dest=b'))
|
||||
m = ModuleArgsParser(dict(copy='src=a dest=b'))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'copy')
|
||||
self.assertEqual(args, dict(src='a', dest='b'))
|
||||
self.assertIsNone(to)
|
||||
|
||||
def test_complex_args(self):
|
||||
mod, args, to = self.m.parse(dict(copy=dict(src='a', dest='b')))
|
||||
m = ModuleArgsParser(dict(copy=dict(src='a', dest='b')))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'copy')
|
||||
self.assertEqual(args, dict(src='a', dest='b'))
|
||||
self.assertIsNone(to)
|
||||
|
||||
def test_action_with_complex(self):
|
||||
mod, args, to = self.m.parse(dict(action=dict(module='copy', src='a', dest='b')))
|
||||
m = ModuleArgsParser(dict(action=dict(module='copy', src='a', dest='b')))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'copy')
|
||||
self.assertEqual(args, dict(src='a', dest='b'))
|
||||
self.assertIsNone(to)
|
||||
|
||||
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'))))
|
||||
m = ModuleArgsParser(dict(action=dict(module='copy', args=dict(src='a', dest='b'))))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'copy')
|
||||
self.assertEqual(args, dict(src='a', dest='b'))
|
||||
self.assertIsNone(to)
|
||||
|
||||
def test_local_action_string(self):
|
||||
mod, args, to = self.m.parse(dict(local_action='copy src=a dest=b'))
|
||||
m = ModuleArgsParser(dict(local_action='copy src=a dest=b'))
|
||||
mod, args, to = m.parse()
|
||||
self._debug(mod, args, to)
|
||||
self.assertEqual(mod, 'copy')
|
||||
self.assertEqual(args, dict(src='a', dest='b'))
|
||||
self.assertIs(to, 'localhost')
|
||||
|
||||
def test_multiple_actions(self):
|
||||
self.assertRaises(AnsibleParserError, self.m.parse, dict(action='shell echo hi', local_action='shell echo hi'))
|
||||
self.assertRaises(AnsibleParserError, self.m.parse, dict(action='shell echo hi', shell='echo hi'))
|
||||
self.assertRaises(AnsibleParserError, self.m.parse, dict(local_action='shell echo hi', shell='echo hi'))
|
||||
self.assertRaises(AnsibleParserError, self.m.parse, dict(ping='data=hi', shell='echo hi'))
|
||||
m = ModuleArgsParser(dict(action='shell echo hi', local_action='shell echo hi'))
|
||||
self.assertRaises(AnsibleParserError, m.parse)
|
||||
|
||||
m = ModuleArgsParser(dict(action='shell echo hi', shell='echo hi'))
|
||||
self.assertRaises(AnsibleParserError, m.parse)
|
||||
|
||||
m = ModuleArgsParser(dict(local_action='shell echo hi', shell='echo hi'))
|
||||
self.assertRaises(AnsibleParserError, m.parse)
|
||||
|
||||
m = ModuleArgsParser(dict(ping='data=hi', shell='echo hi'))
|
||||
self.assertRaises(AnsibleParserError, m.parse)
|
||||
|
||||
|
|
65
v2/test/playbook/test_playbook.py
Normal file
65
v2/test/playbook/test_playbook.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.compat.tests.mock import patch, MagicMock
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.playbook import Playbook
|
||||
|
||||
from test.mock.loader import DictDataLoader
|
||||
|
||||
class TestPlaybook(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_empty_playbook(self):
|
||||
p = Playbook()
|
||||
|
||||
def test_basic_playbook(self):
|
||||
fake_loader = DictDataLoader({
|
||||
"test_file.yml":"""
|
||||
- hosts: all
|
||||
""",
|
||||
})
|
||||
p = Playbook.load("test_file.yml", loader=fake_loader)
|
||||
|
||||
def test_bad_playbook_files(self):
|
||||
fake_loader = DictDataLoader({
|
||||
# represents a playbook which is not a list of plays
|
||||
"bad_list.yml": """
|
||||
foo: bar
|
||||
|
||||
""",
|
||||
# represents a playbook where a play entry is mis-formatted
|
||||
"bad_entry.yml": """
|
||||
-
|
||||
- "This should be a mapping..."
|
||||
|
||||
""",
|
||||
})
|
||||
self.assertRaises(AnsibleParserError, Playbook.load, "bad_list.yml", fake_loader)
|
||||
self.assertRaises(AnsibleParserError, Playbook.load, "bad_entry.yml", fake_loader)
|
||||
|
63
v2/test/playbook/test_task_include.py
Normal file
63
v2/test/playbook/test_task_include.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.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/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.parsing.yaml.objects import AnsibleMapping
|
||||
from ansible.playbook.task_include import TaskInclude
|
||||
|
||||
from test.mock.loader import DictDataLoader
|
||||
|
||||
class TestTaskInclude(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._fake_loader = DictDataLoader({
|
||||
"foo.yml": """
|
||||
- shell: echo "hello world"
|
||||
"""
|
||||
})
|
||||
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_empty_task_include(self):
|
||||
ti = TaskInclude()
|
||||
|
||||
def test_basic_task_include(self):
|
||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml'), loader=self._fake_loader)
|
||||
|
||||
def test_task_include_with_loop(self):
|
||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', with_items=['a', 'b', 'c']), loader=self._fake_loader)
|
||||
|
||||
def test_task_include_with_conditional(self):
|
||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', when="1 == 1"), loader=self._fake_loader)
|
||||
|
||||
def test_task_include_with_tags(self):
|
||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', tags="foo"), loader=self._fake_loader)
|
||||
ti = TaskInclude.load(AnsibleMapping(include='foo.yml', tags=["foo", "bar"]), loader=self._fake_loader)
|
||||
|
||||
def test_task_include_errors(self):
|
||||
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include=''), loader=self._fake_loader)
|
||||
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include='foo.yml', vars="1"), loader=self._fake_loader)
|
||||
self.assertRaises(AnsibleParserError, TaskInclude.load, AnsibleMapping(include='foo.yml a=1', vars=dict(b=2)), loader=self._fake_loader)
|
||||
|
|
@ -36,10 +36,6 @@ class TestErrors(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_push_basedir(self):
|
||||
push_basedir('/root/foo/bar')
|
||||
self.assertEqual(_basedirs, ['/root/foo/bar'])
|
||||
|
||||
@patch.object(PluginLoader, '_get_paths')
|
||||
def test_print_paths(self, mock_method):
|
||||
mock_method.return_value = ['/path/one', '/path/two', '/path/three']
|
||||
|
|
Loading…
Reference in a new issue