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):
|
if isinstance(self._obj, AnsibleBaseYAMLObject):
|
||||||
extended_error = self._get_extended_error()
|
extended_error = self._get_extended_error()
|
||||||
if extended_error:
|
if extended_error:
|
||||||
self.message = '%s\n%s' % (message, extended_error)
|
self.message = '%s\n\n%s' % (message, extended_error)
|
||||||
else:
|
else:
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
@ -129,7 +132,7 @@ class AnsibleError(Exception):
|
||||||
if unbalanced:
|
if unbalanced:
|
||||||
error_message += YAML_COMMON_UNBALANCED_QUOTES_ERROR
|
error_message += YAML_COMMON_UNBALANCED_QUOTES_ERROR
|
||||||
|
|
||||||
except IOError:
|
except (IOError, TypeError):
|
||||||
error_message += '\n(could not open file to display line)'
|
error_message += '\n(could not open file to display line)'
|
||||||
except IndexError:
|
except IndexError:
|
||||||
error_message += '\n(specified line no longer in file, maybe it changed?)'
|
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.
|
Args may also be munged for certain shell command parameters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, task=None):
|
def __init__(self, task_ds=dict()):
|
||||||
self._task = task
|
assert isinstance(task_ds, dict)
|
||||||
|
self._task_ds = task_ds
|
||||||
|
|
||||||
|
|
||||||
def _split_module_string(self, str):
|
def _split_module_string(self, str):
|
||||||
|
@ -144,7 +145,7 @@ class ModuleArgsParser:
|
||||||
# form is like: local_action: copy src=a dest=b ... pretty common
|
# form is like: local_action: copy src=a dest=b ... pretty common
|
||||||
args = parse_kv(thing)
|
args = parse_kv(thing)
|
||||||
else:
|
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
|
return args
|
||||||
|
|
||||||
def _normalize_new_style_args(self, thing):
|
def _normalize_new_style_args(self, thing):
|
||||||
|
@ -179,19 +180,17 @@ class ModuleArgsParser:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# need a dict or a string, so giving up
|
# 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)
|
return (action, args)
|
||||||
|
|
||||||
def parse(self, ds):
|
def parse(self):
|
||||||
'''
|
'''
|
||||||
Given a task in one of the supported forms, parses and returns
|
Given a task in one of the supported forms, parses and returns
|
||||||
returns the action, arguments, and delegate_to values for the
|
returns the action, arguments, and delegate_to values for the
|
||||||
task, dealing with all sorts of levels of fuzziness.
|
task, dealing with all sorts of levels of fuzziness.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
assert isinstance(ds, dict)
|
|
||||||
|
|
||||||
thing = None
|
thing = None
|
||||||
|
|
||||||
action = None
|
action = None
|
||||||
|
@ -204,38 +203,38 @@ class ModuleArgsParser:
|
||||||
#
|
#
|
||||||
|
|
||||||
# action
|
# action
|
||||||
if 'action' in ds:
|
if 'action' in self._task_ds:
|
||||||
|
|
||||||
# an old school 'action' statement
|
# an old school 'action' statement
|
||||||
thing = ds['action']
|
thing = self._task_ds['action']
|
||||||
delegate_to = None
|
delegate_to = None
|
||||||
action, args = self._normalize_parameters(thing)
|
action, args = self._normalize_parameters(thing)
|
||||||
|
|
||||||
# local_action
|
# local_action
|
||||||
if 'local_action' in ds:
|
if 'local_action' in self._task_ds:
|
||||||
|
|
||||||
# local_action is similar but also implies a delegate_to
|
# local_action is similar but also implies a delegate_to
|
||||||
if action is not None:
|
if action is not None:
|
||||||
raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task)
|
raise AnsibleParserError("action and local_action are mutually exclusive", obj=self._task_ds)
|
||||||
thing = ds.get('local_action', '')
|
thing = self._task_ds.get('local_action', '')
|
||||||
delegate_to = 'localhost'
|
delegate_to = 'localhost'
|
||||||
action, args = self._normalize_parameters(thing)
|
action, args = self._normalize_parameters(thing)
|
||||||
|
|
||||||
# module: <stuff> is the more new-style invocation
|
# module: <stuff> is the more new-style invocation
|
||||||
|
|
||||||
# walk the input dictionary to see we recognize a module name
|
# 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:
|
if item in module_finder:
|
||||||
# finding more than one module name is a problem
|
# finding more than one module name is a problem
|
||||||
if action is not None:
|
if action is not None:
|
||||||
raise AnsibleParserError("conflicting action statements", obj=self._task)
|
raise AnsibleParserError("conflicting action statements", obj=self._task_ds)
|
||||||
action = item
|
action = item
|
||||||
thing = value
|
thing = value
|
||||||
action, args = self._normalize_parameters(value, action=action)
|
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 we didn't see any module in the task at all, it's not a task really
|
||||||
if action is None:
|
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
|
# shell modules require special handling
|
||||||
(action, args) = self._handle_shell_weirdness(action, args)
|
(action, args) = self._handle_shell_weirdness(action, args)
|
||||||
|
|
|
@ -27,6 +27,7 @@ from yaml import load, YAMLError
|
||||||
from ansible.errors import AnsibleParserError
|
from ansible.errors import AnsibleParserError
|
||||||
|
|
||||||
from ansible.parsing.vault import VaultLib
|
from ansible.parsing.vault import VaultLib
|
||||||
|
from ansible.parsing.splitter import unquote
|
||||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
|
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
|
||||||
from ansible.parsing.yaml.strings import YAML_SYNTAX_ERROR
|
from ansible.parsing.yaml.strings import YAML_SYNTAX_ERROR
|
||||||
|
@ -55,6 +56,7 @@ class DataLoader():
|
||||||
_FILE_CACHE = dict()
|
_FILE_CACHE = dict()
|
||||||
|
|
||||||
def __init__(self, vault_password=None):
|
def __init__(self, vault_password=None):
|
||||||
|
self._basedir = '.'
|
||||||
self._vault = VaultLib(password=vault_password)
|
self._vault = VaultLib(password=vault_password)
|
||||||
|
|
||||||
def load(self, data, file_name='<string>', show_content=True):
|
def load(self, data, file_name='<string>', show_content=True):
|
||||||
|
@ -70,13 +72,15 @@ class DataLoader():
|
||||||
try:
|
try:
|
||||||
# if loading JSON failed for any reason, we go ahead
|
# if loading JSON failed for any reason, we go ahead
|
||||||
# and try to parse it as YAML instead
|
# 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:
|
except YAMLError as yaml_exc:
|
||||||
self._handle_error(yaml_exc, file_name, show_content)
|
self._handle_error(yaml_exc, file_name, show_content)
|
||||||
|
|
||||||
def load_from_file(self, file_name):
|
def load_from_file(self, file_name):
|
||||||
''' Loads data from a file, which can contain either JSON or YAML. '''
|
''' 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
|
# if the file has already been read in and cached, we'll
|
||||||
# return those results to avoid more file/vault operations
|
# return those results to avoid more file/vault operations
|
||||||
if file_name in self._FILE_CACHE:
|
if file_name in self._FILE_CACHE:
|
||||||
|
@ -100,9 +104,14 @@ class DataLoader():
|
||||||
def is_file(self, path):
|
def is_file(self, path):
|
||||||
return os.path.isfile(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. '''
|
''' 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):
|
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)
|
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
|
from ansible.parsing.yaml.objects import AnsibleMapping
|
||||||
|
|
||||||
class AnsibleConstructor(Constructor):
|
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):
|
def construct_yaml_map(self, node):
|
||||||
data = AnsibleMapping()
|
data = AnsibleMapping()
|
||||||
yield data
|
yield data
|
||||||
|
@ -36,7 +40,16 @@ class AnsibleConstructor(Constructor):
|
||||||
ret = AnsibleMapping(super(Constructor, self).construct_mapping(node, deep))
|
ret = AnsibleMapping(super(Constructor, self).construct_mapping(node, deep))
|
||||||
ret._line_number = node.__line__
|
ret._line_number = node.__line__
|
||||||
ret._column_number = node.__column__
|
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
|
return ret
|
||||||
|
|
||||||
AnsibleConstructor.add_constructor(
|
AnsibleConstructor.add_constructor(
|
||||||
|
|
|
@ -28,11 +28,11 @@ from ansible.parsing.yaml.composer import AnsibleComposer
|
||||||
from ansible.parsing.yaml.constructor import AnsibleConstructor
|
from ansible.parsing.yaml.constructor import AnsibleConstructor
|
||||||
|
|
||||||
class AnsibleLoader(Reader, Scanner, Parser, AnsibleComposer, AnsibleConstructor, Resolver):
|
class AnsibleLoader(Reader, Scanner, Parser, AnsibleComposer, AnsibleConstructor, Resolver):
|
||||||
def __init__(self, stream):
|
def __init__(self, stream, file_name=None):
|
||||||
Reader.__init__(self, stream)
|
Reader.__init__(self, stream)
|
||||||
Scanner.__init__(self)
|
Scanner.__init__(self)
|
||||||
Parser.__init__(self)
|
Parser.__init__(self)
|
||||||
AnsibleComposer.__init__(self)
|
AnsibleComposer.__init__(self)
|
||||||
AnsibleConstructor.__init__(self)
|
AnsibleConstructor.__init__(self, file_name=file_name)
|
||||||
Resolver.__init__(self)
|
Resolver.__init__(self)
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ class AnsibleBaseYAMLObject:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
_data_source = None
|
_data_source = None
|
||||||
_line_number = None
|
_line_number = 0
|
||||||
_column_number = None
|
_column_number = 0
|
||||||
|
|
||||||
def get_position_info(self):
|
def get_position_info(self):
|
||||||
return (self._data_source, self._line_number, self._column_number)
|
return (self._data_source, self._line_number, self._column_number)
|
||||||
|
|
|
@ -34,8 +34,8 @@ Syntax Error while loading YAML.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
YAML_POSITION_DETAILS = """\
|
YAML_POSITION_DETAILS = """\
|
||||||
The error appears to have been in '%s': line %s, column %s,
|
The error appears to have been in '%s': line %s, column %s, but may
|
||||||
but may actually be before there depending on the exact syntax problem.
|
be elsewhere in the file depending on the exact syntax problem.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
YAML_COMMON_DICT_ERROR = """\
|
YAML_COMMON_DICT_ERROR = """\
|
||||||
|
|
|
@ -19,14 +19,60 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__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:
|
class Playbook:
|
||||||
def __init__(self, filename):
|
|
||||||
self.ds = v2.utils.load_yaml_from_file(filename)
|
|
||||||
self.plays = []
|
|
||||||
|
|
||||||
def load(self):
|
def __init__(self, loader=None):
|
||||||
# loads a list of plays from the parsed ds
|
# Entries in the datastructure of a playbook may
|
||||||
self.plays = []
|
# 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())]
|
valid_attrs = [name for (name, attribute) in iteritems(self._get_base_attributes())]
|
||||||
for key in ds:
|
for key in ds:
|
||||||
if key not in valid_attrs:
|
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):
|
def validate(self):
|
||||||
''' validation that is done at parse time, not load time '''
|
''' validation that is done at parse time, not load time '''
|
||||||
|
|
|
@ -67,15 +67,15 @@ class Block(Base):
|
||||||
return ds
|
return ds
|
||||||
|
|
||||||
def _load_block(self, attr, 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):
|
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):
|
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
|
# not currently used
|
||||||
#def _load_otherwise(self, attr, ds):
|
#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 types import NoneType
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleParserError
|
||||||
|
|
||||||
def load_list_of_blocks(ds, role=None, loader=None):
|
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
|
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),
|
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
|
# we import here to prevent a circular dependency with imports
|
||||||
from ansible.playbook.task import Task
|
from ansible.playbook.task import Task
|
||||||
|
from ansible.playbook.task_include import TaskInclude
|
||||||
|
|
||||||
assert type(ds) == list
|
assert type(ds) == list
|
||||||
|
|
||||||
task_list = []
|
task_list = []
|
||||||
for task in ds:
|
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)
|
task_list.append(t)
|
||||||
|
|
||||||
return task_list
|
return task_list
|
||||||
|
|
||||||
|
|
||||||
def load_list_of_roles(ds, loader=None):
|
def load_list_of_roles(ds, loader=None):
|
||||||
'''
|
'''
|
||||||
Loads and returns a list of RoleInclude objects from the datastructure
|
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.
|
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.
|
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):
|
def _load_pre_tasks(self, attr, ds):
|
||||||
'''
|
'''
|
||||||
Loads a list of blocks from a list which may be mixed tasks/blocks.
|
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.
|
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):
|
def _load_post_tasks(self, attr, ds):
|
||||||
'''
|
'''
|
||||||
Loads a list of blocks from a list which may be mixed tasks/blocks.
|
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.
|
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):
|
def _load_handlers(self, attr, ds):
|
||||||
'''
|
'''
|
||||||
Loads a list of blocks from a list which may be mixed handlers/blocks.
|
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.
|
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):
|
def _load_roles(self, attr, ds):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -95,11 +95,11 @@ class Role:
|
||||||
|
|
||||||
task_data = self._load_role_yaml('tasks')
|
task_data = self._load_role_yaml('tasks')
|
||||||
if task_data:
|
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')
|
handler_data = self._load_role_yaml('handlers')
|
||||||
if handler_data:
|
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
|
# vars and default vars are regular dictionaries
|
||||||
self._role_vars = self._load_role_yaml('vars')
|
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.splitter import parse_kv
|
||||||
from ansible.parsing.mod_args import ModuleArgsParser
|
from ansible.parsing.mod_args import ModuleArgsParser
|
||||||
from ansible.parsing.yaml import DataLoader
|
from ansible.parsing.yaml import DataLoader
|
||||||
|
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
|
||||||
from ansible.plugins import module_finder, lookup_finder
|
from ansible.plugins import module_finder, lookup_finder
|
||||||
|
|
||||||
class Task(Base):
|
class Task(Base):
|
||||||
|
@ -54,6 +55,7 @@ class Task(Base):
|
||||||
_always_run = FieldAttribute(isa='bool')
|
_always_run = FieldAttribute(isa='bool')
|
||||||
_any_errors_fatal = FieldAttribute(isa='bool')
|
_any_errors_fatal = FieldAttribute(isa='bool')
|
||||||
_async = FieldAttribute(isa='int')
|
_async = FieldAttribute(isa='int')
|
||||||
|
_changed_when = FieldAttribute(isa='string')
|
||||||
_connection = FieldAttribute(isa='string')
|
_connection = FieldAttribute(isa='string')
|
||||||
_delay = FieldAttribute(isa='int')
|
_delay = FieldAttribute(isa='int')
|
||||||
_delegate_to = FieldAttribute(isa='string')
|
_delegate_to = FieldAttribute(isa='string')
|
||||||
|
@ -88,10 +90,13 @@ class Task(Base):
|
||||||
_until = FieldAttribute(isa='list') # ?
|
_until = FieldAttribute(isa='list') # ?
|
||||||
_when = FieldAttribute(isa='list', default=[])
|
_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 '''
|
''' 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__()
|
super(Task, self).__init__()
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
|
@ -120,8 +125,8 @@ class Task(Base):
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(data, block=None, role=None, loader=None):
|
def load(data, block=None, role=None, task_include=None, loader=None):
|
||||||
t = Task(block=block, role=role)
|
t = Task(block=block, role=role, task_include=task_include)
|
||||||
return t.load_data(data, loader=loader)
|
return t.load_data(data, loader=loader)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -131,9 +136,10 @@ class Task(Base):
|
||||||
def _munge_loop(self, ds, new_ds, k, v):
|
def _munge_loop(self, ds, new_ds, k, v):
|
||||||
''' take a lookup plugin name and store it correctly '''
|
''' take a lookup plugin name and store it correctly '''
|
||||||
|
|
||||||
if self._loop.value is not None:
|
loop_name = k.replace("with_", "")
|
||||||
raise AnsibleError("duplicate loop in task: %s" % k)
|
if new_ds.get('loop') is not None:
|
||||||
new_ds['loop'] = k
|
raise AnsibleError("duplicate loop in task: %s" % loop_name)
|
||||||
|
new_ds['loop'] = loop_name
|
||||||
new_ds['loop_args'] = v
|
new_ds['loop_args'] = v
|
||||||
|
|
||||||
def munge(self, ds):
|
def munge(self, ds):
|
||||||
|
@ -147,13 +153,15 @@ class Task(Base):
|
||||||
# the new, cleaned datastructure, which will have legacy
|
# the new, cleaned datastructure, which will have legacy
|
||||||
# items reduced to a standard structure suitable for the
|
# items reduced to a standard structure suitable for the
|
||||||
# attributes of the task class
|
# 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,
|
# use the args parsing class to determine the action, args,
|
||||||
# and the delegate_to value from the various possible forms
|
# and the delegate_to value from the various possible forms
|
||||||
# supported as legacy
|
# supported as legacy
|
||||||
args_parser = ModuleArgsParser()
|
args_parser = ModuleArgsParser(task_ds=ds)
|
||||||
(action, args, delegate_to) = args_parser.parse(ds)
|
(action, args, delegate_to) = args_parser.parse()
|
||||||
|
|
||||||
new_ds['action'] = action
|
new_ds['action'] = action
|
||||||
new_ds['args'] = args
|
new_ds['args'] = args
|
||||||
|
@ -164,7 +172,7 @@ class Task(Base):
|
||||||
# we don't want to re-assign these values, which were
|
# we don't want to re-assign these values, which were
|
||||||
# determined by the ModuleArgsParser() above
|
# determined by the ModuleArgsParser() above
|
||||||
continue
|
continue
|
||||||
elif "with_%s" % k in lookup_finder:
|
elif k.replace("with_", "") in lookup_finder:
|
||||||
self._munge_loop(ds, new_ds, k, v)
|
self._munge_loop(ds, new_ds, k, v)
|
||||||
else:
|
else:
|
||||||
new_ds[k] = v
|
new_ds[k] = v
|
||||||
|
|
|
@ -19,3 +19,128 @@
|
||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__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', '')
|
mock_method.return_value = ('this is line 1\n', '')
|
||||||
e = AnsibleError(self.message, self.obj)
|
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):
|
def test_get_error_lines_from_file(self):
|
||||||
m = mock_open()
|
m = mock_open()
|
||||||
|
@ -63,12 +63,12 @@ class TestErrors(unittest.TestCase):
|
||||||
self.obj._line_number = 1
|
self.obj._line_number = 1
|
||||||
self.obj._column_number = 1
|
self.obj._column_number = 1
|
||||||
e = AnsibleError(self.message, self.obj)
|
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
|
# this line will not be found, as it is out of the index range
|
||||||
self.obj._data_source = 'foo.yml'
|
self.obj._data_source = 'foo.yml'
|
||||||
self.obj._line_number = 2
|
self.obj._line_number = 2
|
||||||
self.obj._column_number = 1
|
self.obj._column_number = 1
|
||||||
e = AnsibleError(self.message, self.obj)
|
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
|
# and the task knows the line numbers
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.m = ModuleArgsParser()
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _debug(self, mod, args, to):
|
def _debug(self, mod, args, to):
|
||||||
|
@ -43,7 +42,8 @@ class TestModArgsDwim(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_basic_shell(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'command')
|
self.assertEqual(mod, 'command')
|
||||||
self.assertEqual(args, dict(
|
self.assertEqual(args, dict(
|
||||||
|
@ -53,7 +53,8 @@ class TestModArgsDwim(unittest.TestCase):
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_basic_command(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'command')
|
self.assertEqual(mod, 'command')
|
||||||
self.assertEqual(args, dict(
|
self.assertEqual(args, dict(
|
||||||
|
@ -62,7 +63,8 @@ class TestModArgsDwim(unittest.TestCase):
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_shell_with_modifiers(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'command')
|
self.assertEqual(mod, 'command')
|
||||||
self.assertEqual(args, dict(
|
self.assertEqual(args, dict(
|
||||||
|
@ -74,42 +76,55 @@ class TestModArgsDwim(unittest.TestCase):
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_normal_usage(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'copy')
|
self.assertEqual(mod, 'copy')
|
||||||
self.assertEqual(args, dict(src='a', dest='b'))
|
self.assertEqual(args, dict(src='a', dest='b'))
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_complex_args(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'copy')
|
self.assertEqual(mod, 'copy')
|
||||||
self.assertEqual(args, dict(src='a', dest='b'))
|
self.assertEqual(args, dict(src='a', dest='b'))
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_action_with_complex(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'copy')
|
self.assertEqual(mod, 'copy')
|
||||||
self.assertEqual(args, dict(src='a', dest='b'))
|
self.assertEqual(args, dict(src='a', dest='b'))
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_action_with_complex_and_complex_args(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'copy')
|
self.assertEqual(mod, 'copy')
|
||||||
self.assertEqual(args, dict(src='a', dest='b'))
|
self.assertEqual(args, dict(src='a', dest='b'))
|
||||||
self.assertIsNone(to)
|
self.assertIsNone(to)
|
||||||
|
|
||||||
def test_local_action_string(self):
|
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._debug(mod, args, to)
|
||||||
self.assertEqual(mod, 'copy')
|
self.assertEqual(mod, 'copy')
|
||||||
self.assertEqual(args, dict(src='a', dest='b'))
|
self.assertEqual(args, dict(src='a', dest='b'))
|
||||||
self.assertIs(to, 'localhost')
|
self.assertIs(to, 'localhost')
|
||||||
|
|
||||||
def test_multiple_actions(self):
|
def test_multiple_actions(self):
|
||||||
self.assertRaises(AnsibleParserError, self.m.parse, dict(action='shell echo hi', local_action='shell echo hi'))
|
m = ModuleArgsParser(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, m.parse)
|
||||||
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', 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):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_push_basedir(self):
|
|
||||||
push_basedir('/root/foo/bar')
|
|
||||||
self.assertEqual(_basedirs, ['/root/foo/bar'])
|
|
||||||
|
|
||||||
@patch.object(PluginLoader, '_get_paths')
|
@patch.object(PluginLoader, '_get_paths')
|
||||||
def test_print_paths(self, mock_method):
|
def test_print_paths(self, mock_method):
|
||||||
mock_method.return_value = ['/path/one', '/path/two', '/path/three']
|
mock_method.return_value = ['/path/one', '/path/two', '/path/three']
|
||||||
|
|
Loading…
Reference in a new issue