Adding new playbook objects for v2

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

View file

@ -48,10 +48,13 @@ class AnsibleError(Exception):
if isinstance(self._obj, AnsibleBaseYAMLObject):
extended_error = self._get_extended_error()
if extended_error:
self.message = '%s\n%s' % (message, extended_error)
self.message = '%s\n\n%s' % (message, extended_error)
else:
self.message = message
def __str__(self):
return self.message
def __repr__(self):
return self.message
@ -129,7 +132,7 @@ class AnsibleError(Exception):
if unbalanced:
error_message += YAML_COMMON_UNBALANCED_QUOTES_ERROR
except IOError:
except (IOError, TypeError):
error_message += '\n(could not open file to display line)'
except IndexError:
error_message += '\n(specified line no longer in file, maybe it changed?)'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,14 +19,60 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.parsing.yaml import DataLoader
from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.play import Play
from ansible.plugins import push_basedir
__all__ = ['Playbook']
class Playbook:
def __init__(self, filename):
self.ds = v2.utils.load_yaml_from_file(filename)
self.plays = []
def load(self):
# loads a list of plays from the parsed ds
self.plays = []
def __init__(self, loader=None):
# Entries in the datastructure of a playbook may
# be either a play or an include statement
self._entries = []
self._basedir = '.'
if loader:
self._loader = loader
else:
self._loader = DataLoader()
@staticmethod
def load(file_name, loader=None):
pb = Playbook(loader=loader)
pb._load_playbook_data(file_name)
return pb
def _load_playbook_data(self, file_name):
# add the base directory of the file to the data loader,
# so that it knows where to find relatively pathed files
basedir = os.path.dirname(file_name)
self._loader.set_basedir(basedir)
ds = self._loader.load_from_file(file_name)
if not isinstance(ds, list):
raise AnsibleParserError("playbooks must be a list of plays", obj=ds)
# Parse the playbook entries. For plays, we simply parse them
# using the Play() object, and includes are parsed using the
# PlaybookInclude() object
for entry in ds:
if not isinstance(entry, dict):
raise AnsibleParserError("playbook entries must be either a valid play or an include statement", obj=entry)
if 'include' in entry:
entry_obj = PlaybookInclude.load(entry, loader=self._loader)
else:
entry_obj = Play.load(entry, loader=self._loader)
self._entries.append(entry_obj)
def get_plays(self):
return self.plays

View file

@ -110,7 +110,7 @@ class Base:
valid_attrs = [name for (name, attribute) in iteritems(self._get_base_attributes())]
for key in ds:
if key not in valid_attrs:
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (key, self.__class__), obj=ds)
raise AnsibleParserError("'%s' is not a valid attribute for a %s" % (key, self.__class__.__name__), obj=ds)
def validate(self):
''' validation that is done at parse time, not load time '''

View file

@ -67,15 +67,15 @@ class Block(Base):
return ds
def _load_block(self, attr, ds):
return load_list_of_tasks(ds)
return load_list_of_tasks(ds, block=self, loader=self._loader)
def _load_rescue(self, attr, ds):
return load_list_of_tasks(ds)
return load_list_of_tasks(ds, block=self, loader=self._loader)
def _load_always(self, attr, ds):
return load_list_of_tasks(ds)
return load_list_of_tasks(ds, block=self, loader=self._loader)
# not currently used
#def _load_otherwise(self, attr, ds):
# return self._load_list_of_tasks(ds)
# return self._load_list_of_tasks(ds, block=self, loader=self._loader)

View file

@ -17,6 +17,7 @@
from types import NoneType
from ansible.errors import AnsibleParserError
def load_list_of_blocks(ds, role=None, loader=None):
'''
@ -38,24 +39,34 @@ def load_list_of_blocks(ds, role=None, loader=None):
return block_list
def load_list_of_tasks(ds, block=None, role=None, loader=None):
def load_list_of_tasks(ds, block=None, role=None, task_include=None, loader=None):
'''
Given a list of task datastructures (parsed from YAML),
return a list of Task() objects.
return a list of Task() or TaskInclude() objects.
'''
# we import here to prevent a circular dependency with imports
from ansible.playbook.task import Task
from ansible.playbook.task_include import TaskInclude
assert type(ds) == list
task_list = []
for task in ds:
t = Task.load(task, block=block, role=role, loader=loader)
if not isinstance(task, dict):
raise AnsibleParserError("task/handler entries must be dictionaries (got a %s)" % type(task), obj=ds)
if 'include' in task:
t = TaskInclude.load(task, block=block, role=role, task_include=task_include, loader=loader)
else:
t = Task.load(task, block=block, role=role, task_include=task_include, loader=loader)
task_list.append(t)
return task_list
def load_list_of_roles(ds, loader=None):
'''
Loads and returns a list of RoleInclude objects from the datastructure

View file

@ -124,28 +124,28 @@ class Play(Base):
Loads a list of blocks from a list which may be mixed tasks/blocks.
Bare tasks outside of a block are given an implicit block.
'''
return load_list_of_blocks(ds)
return load_list_of_blocks(ds, loader=self._loader)
def _load_pre_tasks(self, attr, ds):
'''
Loads a list of blocks from a list which may be mixed tasks/blocks.
Bare tasks outside of a block are given an implicit block.
'''
return load_list_of_blocks(ds)
return load_list_of_blocks(ds, loader=self._loader)
def _load_post_tasks(self, attr, ds):
'''
Loads a list of blocks from a list which may be mixed tasks/blocks.
Bare tasks outside of a block are given an implicit block.
'''
return load_list_of_blocks(ds)
return load_list_of_blocks(ds, loader=self._loader)
def _load_handlers(self, attr, ds):
'''
Loads a list of blocks from a list which may be mixed handlers/blocks.
Bare handlers outside of a block are given an implicit block.
'''
return load_list_of_blocks(ds)
return load_list_of_blocks(ds, loader=self._loader)
def _load_roles(self, attr, ds):
'''

View file

@ -95,11 +95,11 @@ class Role:
task_data = self._load_role_yaml('tasks')
if task_data:
self._task_blocks = load_list_of_blocks(task_data)
self._task_blocks = load_list_of_blocks(task_data, role=self, loader=self._loader)
handler_data = self._load_role_yaml('handlers')
if handler_data:
self._handler_blocks = load_list_of_blocks(handler_data)
self._handler_blocks = load_list_of_blocks(handler_data, role=self, loader=self._loader)
# vars and default vars are regular dictionaries
self._role_vars = self._load_role_yaml('vars')

View file

@ -27,6 +27,7 @@ from ansible.errors import AnsibleError
from ansible.parsing.splitter import parse_kv
from ansible.parsing.mod_args import ModuleArgsParser
from ansible.parsing.yaml import DataLoader
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
from ansible.plugins import module_finder, lookup_finder
class Task(Base):
@ -54,6 +55,7 @@ class Task(Base):
_always_run = FieldAttribute(isa='bool')
_any_errors_fatal = FieldAttribute(isa='bool')
_async = FieldAttribute(isa='int')
_changed_when = FieldAttribute(isa='string')
_connection = FieldAttribute(isa='string')
_delay = FieldAttribute(isa='int')
_delegate_to = FieldAttribute(isa='string')
@ -88,10 +90,13 @@ class Task(Base):
_until = FieldAttribute(isa='list') # ?
_when = FieldAttribute(isa='list', default=[])
def __init__(self, block=None, role=None):
def __init__(self, block=None, role=None, task_include=None):
''' constructors a task, without the Task.load classmethod, it will be pretty blank '''
self._block = block
self._role = role
self._block = block
self._role = role
self._task_include = task_include
super(Task, self).__init__()
def get_name(self):
@ -120,8 +125,8 @@ class Task(Base):
return buf
@staticmethod
def load(data, block=None, role=None, loader=None):
t = Task(block=block, role=role)
def load(data, block=None, role=None, task_include=None, loader=None):
t = Task(block=block, role=role, task_include=task_include)
return t.load_data(data, loader=loader)
def __repr__(self):
@ -131,9 +136,10 @@ class Task(Base):
def _munge_loop(self, ds, new_ds, k, v):
''' take a lookup plugin name and store it correctly '''
if self._loop.value is not None:
raise AnsibleError("duplicate loop in task: %s" % k)
new_ds['loop'] = k
loop_name = k.replace("with_", "")
if new_ds.get('loop') is not None:
raise AnsibleError("duplicate loop in task: %s" % loop_name)
new_ds['loop'] = loop_name
new_ds['loop_args'] = v
def munge(self, ds):
@ -147,13 +153,15 @@ class Task(Base):
# the new, cleaned datastructure, which will have legacy
# items reduced to a standard structure suitable for the
# attributes of the task class
new_ds = dict()
new_ds = AnsibleMapping()
if isinstance(ds, AnsibleBaseYAMLObject):
new_ds.copy_position_info(ds)
# use the args parsing class to determine the action, args,
# and the delegate_to value from the various possible forms
# supported as legacy
args_parser = ModuleArgsParser()
(action, args, delegate_to) = args_parser.parse(ds)
args_parser = ModuleArgsParser(task_ds=ds)
(action, args, delegate_to) = args_parser.parse()
new_ds['action'] = action
new_ds['args'] = args
@ -164,7 +172,7 @@ class Task(Base):
# we don't want to re-assign these values, which were
# determined by the ModuleArgsParser() above
continue
elif "with_%s" % k in lookup_finder:
elif k.replace("with_", "") in lookup_finder:
self._munge_loop(ds, new_ds, k, v)
else:
new_ds[k] = v

View file

@ -19,3 +19,128 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleParserError
from ansible.parsing.splitter import split_args, parse_kv
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleMapping
from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.base import Base
from ansible.playbook.helpers import load_list_of_tasks
from ansible.plugins import lookup_finder
__all__ = ['TaskInclude']
class TaskInclude(Base):
'''
A class used to wrap the use of `include: /some/other/file.yml`
within a task list, which may return a list of Task objects and/or
more TaskInclude objects.
'''
# the description field is used mainly internally to
# show a nice reprsentation of this class, rather than
# simply using __class__.__name__
__desc__ = "task include statement"
#-----------------------------------------------------------------
# Attributes
_include = FieldAttribute(isa='string')
_loop = FieldAttribute(isa='string', private=True)
_loop_args = FieldAttribute(isa='list', private=True)
_tags = FieldAttribute(isa='list', default=[])
_vars = FieldAttribute(isa='dict', default=dict())
_when = FieldAttribute(isa='list', default=[])
def __init__(self, block=None, role=None, task_include=None):
self._tasks = []
self._block = block
self._role = role
self._task_include = task_include
super(TaskInclude, self).__init__()
@staticmethod
def load(data, block=None, role=None, task_include=None, loader=None):
ti = TaskInclude(block=block, role=role, task_include=None)
return ti.load_data(data, loader=loader)
def munge(self, ds):
'''
Regorganizes the data for a TaskInclude datastructure to line
up with what we expect the proper attributes to be
'''
assert isinstance(ds, dict)
# the new, cleaned datastructure, which will have legacy
# items reduced to a standard structure
new_ds = AnsibleMapping()
if isinstance(ds, AnsibleBaseYAMLObject):
new_ds.copy_position_info(ds)
for (k,v) in ds.iteritems():
if k == 'include':
self._munge_include(ds, new_ds, k, v)
elif k.replace("with_", "") in lookup_finder:
self._munge_loop(ds, new_ds, k, v)
else:
# some basic error checking, to make sure vars are properly
# formatted and do not conflict with k=v parameters
# FIXME: we could merge these instead, but controlling the order
# in which they're encountered could be difficult
if k == 'vars':
if 'vars' in new_ds:
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
elif not isinstance(v, dict):
raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds)
new_ds[k] = v
return new_ds
def _munge_include(self, ds, new_ds, k, v):
'''
Splits the include line up into filename and parameters
'''
# The include line must include at least one item, which is the filename
# to include. Anything after that should be regarded as a parameter to the include
items = split_args(v)
if len(items) == 0:
raise AnsibleParserError("include statements must specify the file name to include", obj=ds)
else:
# FIXME/TODO: validate that items[0] is a file, which also
# exists and is readable
new_ds['include'] = items[0]
if len(items) > 1:
# rejoin the parameter portion of the arguments and
# then use parse_kv() to get a dict of params back
params = parse_kv(" ".join(items[1:]))
if 'vars' in new_ds:
# FIXME: see fixme above regarding merging vars
raise AnsibleParserError("include parameters cannot be mixed with 'vars' entries for include statements", obj=ds)
new_ds['vars'] = params
def _munge_loop(self, ds, new_ds, k, v):
''' take a lookup plugin name and store it correctly '''
loop_name = k.replace("with_", "")
if new_ds.get('loop') is not None:
raise AnsibleError("duplicate loop in task: %s" % loop_name)
new_ds['loop'] = loop_name
new_ds['loop_args'] = v
def _load_include(self, attr, ds):
''' loads the file name specified in the ds and returns a list of tasks '''
data = self._loader.load_from_file(ds)
if not isinstance(data, list):
raise AnsibleParsingError("included task files must contain a list of tasks", obj=ds)
self._tasks = load_list_of_tasks(data, task_include=self, loader=self._loader)
return ds

View 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

View 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)

View 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

View 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

View 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

View 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

View 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

View 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]

View 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

View 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)

View 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))

View 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)

View 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

View 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

View 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

View 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

View 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) ]

View 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

View 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

View 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

View 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

View 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)]

View file

@ -51,7 +51,7 @@ class TestErrors(unittest.TestCase):
mock_method.return_value = ('this is line 1\n', '')
e = AnsibleError(self.message, self.obj)
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 1, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\nthis is line 1\n^\n")
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 1, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nthis is line 1\n^\n")
def test_get_error_lines_from_file(self):
m = mock_open()
@ -63,12 +63,12 @@ class TestErrors(unittest.TestCase):
self.obj._line_number = 1
self.obj._column_number = 1
e = AnsibleError(self.message, self.obj)
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 1, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\nthis is line 1\n^\n")
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 1, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nthis is line 1\n^\n")
# this line will not be found, as it is out of the index range
self.obj._data_source = 'foo.yml'
self.obj._line_number = 2
self.obj._column_number = 1
e = AnsibleError(self.message, self.obj)
self.assertEqual(e.message, "This is the error message\nThe error appears to have been in 'foo.yml': line 2, column 1,\nbut may actually be before there depending on the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")
self.assertEqual(e.message, "This is the error message\n\nThe error appears to have been in 'foo.yml': line 2, column 1, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")

View file

@ -31,7 +31,6 @@ class TestModArgsDwim(unittest.TestCase):
# and the task knows the line numbers
def setUp(self):
self.m = ModuleArgsParser()
pass
def _debug(self, mod, args, to):
@ -43,7 +42,8 @@ class TestModArgsDwim(unittest.TestCase):
pass
def test_basic_shell(self):
mod, args, to = self.m.parse(dict(shell='echo hi'))
m = ModuleArgsParser(dict(shell='echo hi'))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'command')
self.assertEqual(args, dict(
@ -53,7 +53,8 @@ class TestModArgsDwim(unittest.TestCase):
self.assertIsNone(to)
def test_basic_command(self):
mod, args, to = self.m.parse(dict(command='echo hi'))
m = ModuleArgsParser(dict(command='echo hi'))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'command')
self.assertEqual(args, dict(
@ -62,7 +63,8 @@ class TestModArgsDwim(unittest.TestCase):
self.assertIsNone(to)
def test_shell_with_modifiers(self):
mod, args, to = self.m.parse(dict(shell='/bin/foo creates=/tmp/baz removes=/tmp/bleep'))
m = ModuleArgsParser(dict(shell='/bin/foo creates=/tmp/baz removes=/tmp/bleep'))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'command')
self.assertEqual(args, dict(
@ -74,42 +76,55 @@ class TestModArgsDwim(unittest.TestCase):
self.assertIsNone(to)
def test_normal_usage(self):
mod, args, to = self.m.parse(dict(copy='src=a dest=b'))
m = ModuleArgsParser(dict(copy='src=a dest=b'))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'copy')
self.assertEqual(args, dict(src='a', dest='b'))
self.assertIsNone(to)
def test_complex_args(self):
mod, args, to = self.m.parse(dict(copy=dict(src='a', dest='b')))
m = ModuleArgsParser(dict(copy=dict(src='a', dest='b')))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'copy')
self.assertEqual(args, dict(src='a', dest='b'))
self.assertIsNone(to)
def test_action_with_complex(self):
mod, args, to = self.m.parse(dict(action=dict(module='copy', src='a', dest='b')))
m = ModuleArgsParser(dict(action=dict(module='copy', src='a', dest='b')))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'copy')
self.assertEqual(args, dict(src='a', dest='b'))
self.assertIsNone(to)
def test_action_with_complex_and_complex_args(self):
mod, args, to = self.m.parse(dict(action=dict(module='copy', args=dict(src='a', dest='b'))))
m = ModuleArgsParser(dict(action=dict(module='copy', args=dict(src='a', dest='b'))))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'copy')
self.assertEqual(args, dict(src='a', dest='b'))
self.assertIsNone(to)
def test_local_action_string(self):
mod, args, to = self.m.parse(dict(local_action='copy src=a dest=b'))
m = ModuleArgsParser(dict(local_action='copy src=a dest=b'))
mod, args, to = m.parse()
self._debug(mod, args, to)
self.assertEqual(mod, 'copy')
self.assertEqual(args, dict(src='a', dest='b'))
self.assertIs(to, 'localhost')
def test_multiple_actions(self):
self.assertRaises(AnsibleParserError, self.m.parse, dict(action='shell echo hi', local_action='shell echo hi'))
self.assertRaises(AnsibleParserError, self.m.parse, dict(action='shell echo hi', shell='echo hi'))
self.assertRaises(AnsibleParserError, self.m.parse, dict(local_action='shell echo hi', shell='echo hi'))
self.assertRaises(AnsibleParserError, self.m.parse, dict(ping='data=hi', shell='echo hi'))
m = ModuleArgsParser(dict(action='shell echo hi', local_action='shell echo hi'))
self.assertRaises(AnsibleParserError, m.parse)
m = ModuleArgsParser(dict(action='shell echo hi', shell='echo hi'))
self.assertRaises(AnsibleParserError, m.parse)
m = ModuleArgsParser(dict(local_action='shell echo hi', shell='echo hi'))
self.assertRaises(AnsibleParserError, m.parse)
m = ModuleArgsParser(dict(ping='data=hi', shell='echo hi'))
self.assertRaises(AnsibleParserError, m.parse)

View 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)

View 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)

View file

@ -36,10 +36,6 @@ class TestErrors(unittest.TestCase):
def tearDown(self):
pass
def test_push_basedir(self):
push_basedir('/root/foo/bar')
self.assertEqual(_basedirs, ['/root/foo/bar'])
@patch.object(PluginLoader, '_get_paths')
def test_print_paths(self, mock_method):
mock_method.return_value = ['/path/one', '/path/two', '/path/three']