diff --git a/.gitmodules b/.gitmodules index 793522a29c..a0e903430a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,3 @@ [submodule "lib/ansible/modules/extras"] path = lib/ansible/modules/extras url = https://github.com/ansible/ansible-modules-extras -[submodule "v1/ansible/modules/core"] - path = v1/ansible/modules/core - url = https://github.com/ansible/ansible-modules-core -[submodule "v1/ansible/modules/extras"] - path = v1/ansible/modules/extras - url = https://github.com/ansible/ansible-modules-extras diff --git a/v1/README.md b/v1/README.md deleted file mode 100644 index 011851da06..0000000000 --- a/v1/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This is dead code, it is here for convenience for those testing current devel so as to ascertain if a bug was introduced in the v2 rewrite or was preexisting in the 1.x codebase. -Using this code should be equivalent of checking out the v1_last tag, which was devel at a point between 1.9.1 and 1.9.2 releases. -The stable-1.9 is the maintenance branch for the 1.9.x code, which might continue to diverge from the v1/ tree as bugs get fixed. - -DO NOT: - - * use this code as reference - * make PRs against this code - * expect this code to be shipped with the 2.0 version of ansible - - diff --git a/v1/ansible/__init__.py b/v1/ansible/__init__.py deleted file mode 100644 index ba5ca83b72..0000000000 --- a/v1/ansible/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . -__version__ = '2.0.0' -__author__ = 'Michael DeHaan' diff --git a/v1/ansible/cache/__init__.py b/v1/ansible/cache/__init__.py deleted file mode 100644 index 4100861c14..0000000000 --- a/v1/ansible/cache/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -# (c) 2014, Michael DeHaan -# -# 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 . - -from collections import MutableMapping - -from ansible import utils -from ansible import constants as C -from ansible import errors - - -class FactCache(MutableMapping): - - def __init__(self, *args, **kwargs): - self._plugin = utils.plugins.cache_loader.get(C.CACHE_PLUGIN) - if self._plugin is None: - return - - def __getitem__(self, key): - if key not in self: - raise KeyError - return self._plugin.get(key) - - def __setitem__(self, key, value): - self._plugin.set(key, value) - - def __delitem__(self, key): - self._plugin.delete(key) - - def __contains__(self, key): - return self._plugin.contains(key) - - def __iter__(self): - return iter(self._plugin.keys()) - - def __len__(self): - return len(self._plugin.keys()) - - def copy(self): - """ Return a primitive copy of the keys and values from the cache. """ - return dict([(k, v) for (k, v) in self.iteritems()]) - - def keys(self): - return self._plugin.keys() - - def flush(self): - """ Flush the fact cache of all keys. """ - self._plugin.flush() diff --git a/v1/ansible/cache/base.py b/v1/ansible/cache/base.py deleted file mode 100644 index b6254cdfd4..0000000000 --- a/v1/ansible/cache/base.py +++ /dev/null @@ -1,41 +0,0 @@ -# (c) 2014, Brian Coca, Josh Drake, et al -# -# 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 . - -import exceptions - -class BaseCacheModule(object): - - def get(self, key): - raise exceptions.NotImplementedError - - def set(self, key, value): - raise exceptions.NotImplementedError - - def keys(self): - raise exceptions.NotImplementedError - - def contains(self, key): - raise exceptions.NotImplementedError - - def delete(self, key): - raise exceptions.NotImplementedError - - def flush(self): - raise exceptions.NotImplementedError - - def copy(self): - raise exceptions.NotImplementedError diff --git a/v1/ansible/cache/jsonfile.py b/v1/ansible/cache/jsonfile.py deleted file mode 100644 index 0bade893a8..0000000000 --- a/v1/ansible/cache/jsonfile.py +++ /dev/null @@ -1,143 +0,0 @@ -# (c) 2014, Brian Coca, Josh Drake, et al -# -# 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 . - -import os -import time -import errno -import codecs - -try: - import simplejson as json -except ImportError: - import json - -from ansible import constants as C -from ansible import utils -from ansible.cache.base import BaseCacheModule - -class CacheModule(BaseCacheModule): - """ - A caching module backed by json files. - """ - def __init__(self, *args, **kwargs): - - self._timeout = float(C.CACHE_PLUGIN_TIMEOUT) - self._cache = {} - self._cache_dir = C.CACHE_PLUGIN_CONNECTION # expects a dir path - if not self._cache_dir: - utils.exit("error, fact_caching_connection is not set, cannot use fact cache") - - if not os.path.exists(self._cache_dir): - try: - os.makedirs(self._cache_dir) - except (OSError,IOError), e: - utils.warning("error while trying to create cache dir %s : %s" % (self._cache_dir, str(e))) - return None - - def get(self, key): - - if key in self._cache: - return self._cache.get(key) - - if self.has_expired(key): - raise KeyError - - cachefile = "%s/%s" % (self._cache_dir, key) - try: - f = codecs.open(cachefile, 'r', encoding='utf-8') - except (OSError,IOError), e: - utils.warning("error while trying to read %s : %s" % (cachefile, str(e))) - else: - value = json.load(f) - self._cache[key] = value - return value - finally: - f.close() - - def set(self, key, value): - - self._cache[key] = value - - cachefile = "%s/%s" % (self._cache_dir, key) - try: - f = codecs.open(cachefile, 'w', encoding='utf-8') - except (OSError,IOError), e: - utils.warning("error while trying to write to %s : %s" % (cachefile, str(e))) - else: - f.write(utils.jsonify(value)) - finally: - f.close() - - def has_expired(self, key): - - cachefile = "%s/%s" % (self._cache_dir, key) - try: - st = os.stat(cachefile) - except (OSError,IOError), e: - if e.errno == errno.ENOENT: - return False - else: - utils.warning("error while trying to stat %s : %s" % (cachefile, str(e))) - - if time.time() - st.st_mtime <= self._timeout: - return False - - if key in self._cache: - del self._cache[key] - return True - - def keys(self): - keys = [] - for k in os.listdir(self._cache_dir): - if not (k.startswith('.') or self.has_expired(k)): - keys.append(k) - return keys - - def contains(self, key): - cachefile = "%s/%s" % (self._cache_dir, key) - - if key in self._cache: - return True - - if self.has_expired(key): - return False - try: - st = os.stat(cachefile) - return True - except (OSError,IOError), e: - if e.errno == errno.ENOENT: - return False - else: - utils.warning("error while trying to stat %s : %s" % (cachefile, str(e))) - - def delete(self, key): - del self._cache[key] - try: - os.remove("%s/%s" % (self._cache_dir, key)) - except (OSError,IOError), e: - pass #TODO: only pass on non existing? - - def flush(self): - self._cache = {} - for key in self.keys(): - self.delete(key) - - def copy(self): - ret = dict() - for key in self.keys(): - ret[key] = self.get(key) - return ret diff --git a/v1/ansible/cache/memcached.py b/v1/ansible/cache/memcached.py deleted file mode 100644 index ea922434b5..0000000000 --- a/v1/ansible/cache/memcached.py +++ /dev/null @@ -1,191 +0,0 @@ -# (c) 2014, Brian Coca, Josh Drake, et al -# -# 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 . - -import collections -import os -import sys -import time -import threading -from itertools import chain - -from ansible import constants as C -from ansible.cache.base import BaseCacheModule - -try: - import memcache -except ImportError: - print 'python-memcached is required for the memcached fact cache' - sys.exit(1) - - -class ProxyClientPool(object): - """ - Memcached connection pooling for thread/fork safety. Inspired by py-redis - connection pool. - - Available connections are maintained in a deque and released in a FIFO manner. - """ - - def __init__(self, *args, **kwargs): - self.max_connections = kwargs.pop('max_connections', 1024) - self.connection_args = args - self.connection_kwargs = kwargs - self.reset() - - def reset(self): - self.pid = os.getpid() - self._num_connections = 0 - self._available_connections = collections.deque(maxlen=self.max_connections) - self._locked_connections = set() - self._lock = threading.Lock() - - def _check_safe(self): - if self.pid != os.getpid(): - with self._lock: - if self.pid == os.getpid(): - # bail out - another thread already acquired the lock - return - self.disconnect_all() - self.reset() - - def get_connection(self): - self._check_safe() - try: - connection = self._available_connections.popleft() - except IndexError: - connection = self.create_connection() - self._locked_connections.add(connection) - return connection - - def create_connection(self): - if self._num_connections >= self.max_connections: - raise RuntimeError("Too many memcached connections") - self._num_connections += 1 - return memcache.Client(*self.connection_args, **self.connection_kwargs) - - def release_connection(self, connection): - self._check_safe() - self._locked_connections.remove(connection) - self._available_connections.append(connection) - - def disconnect_all(self): - for conn in chain(self._available_connections, self._locked_connections): - conn.disconnect_all() - - def __getattr__(self, name): - def wrapped(*args, **kwargs): - return self._proxy_client(name, *args, **kwargs) - return wrapped - - def _proxy_client(self, name, *args, **kwargs): - conn = self.get_connection() - - try: - return getattr(conn, name)(*args, **kwargs) - finally: - self.release_connection(conn) - - -class CacheModuleKeys(collections.MutableSet): - """ - A set subclass that keeps track of insertion time and persists - the set in memcached. - """ - PREFIX = 'ansible_cache_keys' - - def __init__(self, cache, *args, **kwargs): - self._cache = cache - self._keyset = dict(*args, **kwargs) - - def __contains__(self, key): - return key in self._keyset - - def __iter__(self): - return iter(self._keyset) - - def __len__(self): - return len(self._keyset) - - def add(self, key): - self._keyset[key] = time.time() - self._cache.set(self.PREFIX, self._keyset) - - def discard(self, key): - del self._keyset[key] - self._cache.set(self.PREFIX, self._keyset) - - def remove_by_timerange(self, s_min, s_max): - for k in self._keyset.keys(): - t = self._keyset[k] - if s_min < t < s_max: - del self._keyset[k] - self._cache.set(self.PREFIX, self._keyset) - - -class CacheModule(BaseCacheModule): - - def __init__(self, *args, **kwargs): - if C.CACHE_PLUGIN_CONNECTION: - connection = C.CACHE_PLUGIN_CONNECTION.split(',') - else: - connection = ['127.0.0.1:11211'] - - self._timeout = C.CACHE_PLUGIN_TIMEOUT - self._prefix = C.CACHE_PLUGIN_PREFIX - self._cache = ProxyClientPool(connection, debug=0) - self._keys = CacheModuleKeys(self._cache, self._cache.get(CacheModuleKeys.PREFIX) or []) - - def _make_key(self, key): - return "{0}{1}".format(self._prefix, key) - - def _expire_keys(self): - if self._timeout > 0: - expiry_age = time.time() - self._timeout - self._keys.remove_by_timerange(0, expiry_age) - - def get(self, key): - value = self._cache.get(self._make_key(key)) - # guard against the key not being removed from the keyset; - # this could happen in cases where the timeout value is changed - # between invocations - if value is None: - self.delete(key) - raise KeyError - return value - - def set(self, key, value): - self._cache.set(self._make_key(key), value, time=self._timeout, min_compress_len=1) - self._keys.add(key) - - def keys(self): - self._expire_keys() - return list(iter(self._keys)) - - def contains(self, key): - self._expire_keys() - return key in self._keys - - def delete(self, key): - self._cache.delete(self._make_key(key)) - self._keys.discard(key) - - def flush(self): - for key in self.keys(): - self.delete(key) - - def copy(self): - return self._keys.copy() diff --git a/v1/ansible/cache/memory.py b/v1/ansible/cache/memory.py deleted file mode 100644 index 735ed32893..0000000000 --- a/v1/ansible/cache/memory.py +++ /dev/null @@ -1,44 +0,0 @@ -# (c) 2014, Brian Coca, Josh Drake, et al -# -# 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 . - -from ansible.cache.base import BaseCacheModule - -class CacheModule(BaseCacheModule): - - def __init__(self, *args, **kwargs): - self._cache = {} - - def get(self, key): - return self._cache.get(key) - - def set(self, key, value): - self._cache[key] = value - - def keys(self): - return self._cache.keys() - - def contains(self, key): - return key in self._cache - - def delete(self, key): - del self._cache[key] - - def flush(self): - self._cache = {} - - def copy(self): - return self._cache.copy() diff --git a/v1/ansible/cache/redis.py b/v1/ansible/cache/redis.py deleted file mode 100644 index 7ae5ef74c1..0000000000 --- a/v1/ansible/cache/redis.py +++ /dev/null @@ -1,107 +0,0 @@ -# (c) 2014, Brian Coca, Josh Drake, et al -# -# 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 . - -from __future__ import absolute_import -import collections -# FIXME: can we store these as something else before we ship it? -import sys -import time - -try: - import simplejson as json -except ImportError: - import json - -from ansible import constants as C -from ansible.utils import jsonify -from ansible.cache.base import BaseCacheModule - -try: - from redis import StrictRedis -except ImportError: - print "The 'redis' python module is required, 'pip install redis'" - sys.exit(1) - -class CacheModule(BaseCacheModule): - """ - A caching module backed by redis. - - Keys are maintained in a zset with their score being the timestamp - when they are inserted. This allows for the usage of 'zremrangebyscore' - to expire keys. This mechanism is used or a pattern matched 'scan' for - performance. - """ - def __init__(self, *args, **kwargs): - if C.CACHE_PLUGIN_CONNECTION: - connection = C.CACHE_PLUGIN_CONNECTION.split(':') - else: - connection = [] - - self._timeout = float(C.CACHE_PLUGIN_TIMEOUT) - self._prefix = C.CACHE_PLUGIN_PREFIX - self._cache = StrictRedis(*connection) - self._keys_set = 'ansible_cache_keys' - - def _make_key(self, key): - return self._prefix + key - - def get(self, key): - value = self._cache.get(self._make_key(key)) - # guard against the key not being removed from the zset; - # this could happen in cases where the timeout value is changed - # between invocations - if value is None: - self.delete(key) - raise KeyError - return json.loads(value) - - def set(self, key, value): - value2 = jsonify(value) - if self._timeout > 0: # a timeout of 0 is handled as meaning 'never expire' - self._cache.setex(self._make_key(key), int(self._timeout), value2) - else: - self._cache.set(self._make_key(key), value2) - - self._cache.zadd(self._keys_set, time.time(), key) - - def _expire_keys(self): - if self._timeout > 0: - expiry_age = time.time() - self._timeout - self._cache.zremrangebyscore(self._keys_set, 0, expiry_age) - - def keys(self): - self._expire_keys() - return self._cache.zrange(self._keys_set, 0, -1) - - def contains(self, key): - self._expire_keys() - return (self._cache.zrank(self._keys_set, key) >= 0) - - def delete(self, key): - self._cache.delete(self._make_key(key)) - self._cache.zrem(self._keys_set, key) - - def flush(self): - for key in self.keys(): - self.delete(key) - - def copy(self): - # FIXME: there is probably a better way to do this in redis - ret = dict() - for key in self.keys(): - ret[key] = self.get(key) - return ret diff --git a/v1/ansible/callback_plugins/__init__.py b/v1/ansible/callback_plugins/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/v1/ansible/callback_plugins/noop.py b/v1/ansible/callback_plugins/noop.py deleted file mode 100644 index b5d5886874..0000000000 --- a/v1/ansible/callback_plugins/noop.py +++ /dev/null @@ -1,94 +0,0 @@ -# (C) 2012-2014, Michael DeHaan, - -# 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 . - - -class CallbackModule(object): - - """ - this is an example ansible callback file that does nothing. You can drop - other classes in the same directory to define your own handlers. Methods - you do not use can be omitted. If self.disabled is set to True, the plugin - methods will not be called. - - example uses include: logging, emailing, storing info, etc - """ - - def __init__(self): - #if foo: - # self.disabled = True - pass - - def on_any(self, *args, **kwargs): - pass - - def runner_on_failed(self, host, res, ignore_errors=False): - pass - - def runner_on_ok(self, host, res): - pass - - def runner_on_skipped(self, host, item=None): - pass - - def runner_on_unreachable(self, host, res): - pass - - def runner_on_no_hosts(self): - pass - - def runner_on_async_poll(self, host, res, jid, clock): - pass - - def runner_on_async_ok(self, host, res, jid): - pass - - def runner_on_async_failed(self, host, res, jid): - pass - - def playbook_on_start(self): - pass - - def playbook_on_notify(self, host, handler): - pass - - def playbook_on_no_hosts_matched(self): - pass - - def playbook_on_no_hosts_remaining(self): - pass - - def playbook_on_task_start(self, name, is_conditional): - pass - - def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): - pass - - def playbook_on_setup(self): - pass - - def playbook_on_import_for_host(self, host, imported_file): - pass - - def playbook_on_not_import_for_host(self, host, missing_file): - pass - - def playbook_on_play_start(self, name): - pass - - def playbook_on_stats(self, stats): - pass - diff --git a/v1/ansible/callbacks.py b/v1/ansible/callbacks.py deleted file mode 100644 index a7d2283cf0..0000000000 --- a/v1/ansible/callbacks.py +++ /dev/null @@ -1,729 +0,0 @@ -# (C) 2012-2014, Michael DeHaan, - -# 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 . - -import utils -import sys -import getpass -import os -import subprocess -import random -import fnmatch -import tempfile -import fcntl -import constants -import locale -from ansible.color import stringc -from ansible.module_utils import basic -from ansible.utils.unicode import to_unicode, to_bytes - -import logging -if constants.DEFAULT_LOG_PATH != '': - path = constants.DEFAULT_LOG_PATH - - if (os.path.exists(path) and not os.access(path, os.W_OK)) and not os.access(os.path.dirname(path), os.W_OK): - sys.stderr.write("log file at %s is not writeable, aborting\n" % path) - sys.exit(1) - - - logging.basicConfig(filename=path, level=logging.DEBUG, format='%(asctime)s %(name)s %(message)s') - mypid = str(os.getpid()) - user = getpass.getuser() - logger = logging.getLogger("p=%s u=%s | " % (mypid, user)) - -callback_plugins = [] - -def load_callback_plugins(): - global callback_plugins - callback_plugins = [x for x in utils.plugins.callback_loader.all()] - -def get_cowsay_info(): - if constants.ANSIBLE_NOCOWS: - return (None, None) - cowsay = None - if os.path.exists("/usr/bin/cowsay"): - cowsay = "/usr/bin/cowsay" - elif os.path.exists("/usr/games/cowsay"): - cowsay = "/usr/games/cowsay" - elif os.path.exists("/usr/local/bin/cowsay"): - # BSD path for cowsay - cowsay = "/usr/local/bin/cowsay" - elif os.path.exists("/opt/local/bin/cowsay"): - # MacPorts path for cowsay - cowsay = "/opt/local/bin/cowsay" - - noncow = os.getenv("ANSIBLE_COW_SELECTION",None) - if cowsay and noncow == 'random': - cmd = subprocess.Popen([cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - cows = out.split() - cows.append(False) - noncow = random.choice(cows) - return (cowsay, noncow) - -cowsay, noncow = get_cowsay_info() - -def log_lockfile(): - # create the path for the lockfile and open it - tempdir = tempfile.gettempdir() - uid = os.getuid() - path = os.path.join(tempdir, ".ansible-lock.%s" % uid) - lockfile = open(path, 'w') - # use fcntl to set FD_CLOEXEC on the file descriptor, - # so that we don't leak the file descriptor later - lockfile_fd = lockfile.fileno() - old_flags = fcntl.fcntl(lockfile_fd, fcntl.F_GETFD) - fcntl.fcntl(lockfile_fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) - return lockfile - -LOG_LOCK = log_lockfile() - -def log_flock(runner): - if runner is not None: - try: - fcntl.lockf(runner.output_lockfile, fcntl.LOCK_EX) - except OSError: - # already got closed? - pass - else: - try: - fcntl.lockf(LOG_LOCK, fcntl.LOCK_EX) - except OSError: - pass - - -def log_unflock(runner): - if runner is not None: - try: - fcntl.lockf(runner.output_lockfile, fcntl.LOCK_UN) - except OSError: - # already got closed? - pass - else: - try: - fcntl.lockf(LOG_LOCK, fcntl.LOCK_UN) - except OSError: - pass - -def set_playbook(callback, playbook): - ''' used to notify callback plugins of playbook context ''' - callback.playbook = playbook - for callback_plugin in callback_plugins: - callback_plugin.playbook = playbook - -def set_play(callback, play): - ''' used to notify callback plugins of context ''' - callback.play = play - for callback_plugin in callback_plugins: - callback_plugin.play = play - -def set_task(callback, task): - ''' used to notify callback plugins of context ''' - callback.task = task - for callback_plugin in callback_plugins: - callback_plugin.task = task - -def display(msg, color=None, stderr=False, screen_only=False, log_only=False, runner=None): - # prevent a very rare case of interlaced multiprocess I/O - log_flock(runner) - msg2 = msg - if color: - msg2 = stringc(msg, color) - if not log_only: - if not stderr: - try: - print msg2 - except UnicodeEncodeError: - print msg2.encode('utf-8') - else: - try: - print >>sys.stderr, msg2 - except UnicodeEncodeError: - print >>sys.stderr, msg2.encode('utf-8') - if constants.DEFAULT_LOG_PATH != '': - while msg.startswith("\n"): - msg = msg.replace("\n","") - if not screen_only: - if color == 'red': - logger.error(msg) - else: - logger.info(msg) - log_unflock(runner) - -def call_callback_module(method_name, *args, **kwargs): - - for callback_plugin in callback_plugins: - # a plugin that set self.disabled to True will not be called - # see osx_say.py example for such a plugin - if getattr(callback_plugin, 'disabled', False): - continue - methods = [ - getattr(callback_plugin, method_name, None), - getattr(callback_plugin, 'on_any', None) - ] - for method in methods: - if method is not None: - method(*args, **kwargs) - -def vv(msg, host=None): - return verbose(msg, host=host, caplevel=1) - -def vvv(msg, host=None): - return verbose(msg, host=host, caplevel=2) - -def vvvv(msg, host=None): - return verbose(msg, host=host, caplevel=3) - -def verbose(msg, host=None, caplevel=2): - msg = utils.sanitize_output(msg) - if utils.VERBOSITY > caplevel: - if host is None: - display(msg, color='blue') - else: - display("<%s> %s" % (host, msg), color='blue') - -class AggregateStats(object): - ''' holds stats about per-host activity during playbook runs ''' - - def __init__(self): - - self.processed = {} - self.failures = {} - self.ok = {} - self.dark = {} - self.changed = {} - self.skipped = {} - - def _increment(self, what, host): - ''' helper function to bump a statistic ''' - - self.processed[host] = 1 - prev = (getattr(self, what)).get(host, 0) - getattr(self, what)[host] = prev+1 - - def compute(self, runner_results, setup=False, poll=False, ignore_errors=False): - ''' walk through all results and increment stats ''' - - for (host, value) in runner_results.get('contacted', {}).iteritems(): - if not ignore_errors and (('failed' in value and bool(value['failed'])) or - ('failed_when_result' in value and [value['failed_when_result']] or ['rc' in value and value['rc'] != 0])[0]): - self._increment('failures', host) - elif 'skipped' in value and bool(value['skipped']): - self._increment('skipped', host) - elif 'changed' in value and bool(value['changed']): - if not setup and not poll: - self._increment('changed', host) - self._increment('ok', host) - else: - if not poll or ('finished' in value and bool(value['finished'])): - self._increment('ok', host) - - for (host, value) in runner_results.get('dark', {}).iteritems(): - self._increment('dark', host) - - - def summarize(self, host): - ''' return information about a particular host ''' - - return dict( - ok = self.ok.get(host, 0), - failures = self.failures.get(host, 0), - unreachable = self.dark.get(host,0), - changed = self.changed.get(host, 0), - skipped = self.skipped.get(host, 0) - ) - -######################################################################## - -def regular_generic_msg(hostname, result, oneline, caption): - ''' output on the result of a module run that is not command ''' - - if not oneline: - return "%s | %s >> %s\n" % (hostname, caption, utils.jsonify(result,format=True)) - else: - return "%s | %s >> %s\n" % (hostname, caption, utils.jsonify(result)) - - -def banner_cowsay(msg): - - if ": [" in msg: - msg = msg.replace("[","") - if msg.endswith("]"): - msg = msg[:-1] - runcmd = [cowsay,"-W", "60"] - if noncow: - runcmd.append('-f') - runcmd.append(noncow) - runcmd.append(msg) - cmd = subprocess.Popen(runcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - return "%s\n" % out - -def banner_normal(msg): - - width = 78 - len(msg) - if width < 3: - width = 3 - filler = "*" * width - return "\n%s %s " % (msg, filler) - -def banner(msg): - if cowsay: - try: - return banner_cowsay(msg) - except OSError: - # somebody cleverly deleted cowsay or something during the PB run. heh. - return banner_normal(msg) - return banner_normal(msg) - -def command_generic_msg(hostname, result, oneline, caption): - ''' output the result of a command run ''' - - rc = result.get('rc', '0') - stdout = result.get('stdout','') - stderr = result.get('stderr', '') - msg = result.get('msg', '') - - hostname = hostname.encode('utf-8') - caption = caption.encode('utf-8') - - if not oneline: - buf = "%s | %s | rc=%s >>\n" % (hostname, caption, result.get('rc',0)) - if stdout: - buf += stdout - if stderr: - buf += stderr - if msg: - buf += msg - return buf + "\n" - else: - if stderr: - return "%s | %s | rc=%s | (stdout) %s (stderr) %s" % (hostname, caption, rc, stdout, stderr) - else: - return "%s | %s | rc=%s | (stdout) %s" % (hostname, caption, rc, stdout) - -def host_report_msg(hostname, module_name, result, oneline): - ''' summarize the JSON results for a particular host ''' - - failed = utils.is_failed(result) - msg = ('', None) - if module_name in [ 'command', 'shell', 'raw' ] and 'ansible_job_id' not in result and result.get('parsed',True) != False: - if not failed: - msg = (command_generic_msg(hostname, result, oneline, 'success'), 'green') - else: - msg = (command_generic_msg(hostname, result, oneline, 'FAILED'), 'red') - else: - if not failed: - msg = (regular_generic_msg(hostname, result, oneline, 'success'), 'green') - else: - msg = (regular_generic_msg(hostname, result, oneline, 'FAILED'), 'red') - return msg - -############################################### - -class DefaultRunnerCallbacks(object): - ''' no-op callbacks for API usage of Runner() if no callbacks are specified ''' - - def __init__(self): - pass - - def on_failed(self, host, res, ignore_errors=False): - call_callback_module('runner_on_failed', host, res, ignore_errors=ignore_errors) - - def on_ok(self, host, res): - call_callback_module('runner_on_ok', host, res) - - def on_skipped(self, host, item=None): - call_callback_module('runner_on_skipped', host, item=item) - - def on_unreachable(self, host, res): - call_callback_module('runner_on_unreachable', host, res) - - def on_no_hosts(self): - call_callback_module('runner_on_no_hosts') - - def on_async_poll(self, host, res, jid, clock): - call_callback_module('runner_on_async_poll', host, res, jid, clock) - - def on_async_ok(self, host, res, jid): - call_callback_module('runner_on_async_ok', host, res, jid) - - def on_async_failed(self, host, res, jid): - call_callback_module('runner_on_async_failed', host, res, jid) - - def on_file_diff(self, host, diff): - call_callback_module('runner_on_file_diff', host, diff) - -######################################################################## - -class CliRunnerCallbacks(DefaultRunnerCallbacks): - ''' callbacks for use by /usr/bin/ansible ''' - - def __init__(self): - # set by /usr/bin/ansible later - self.options = None - self._async_notified = {} - - def on_failed(self, host, res, ignore_errors=False): - self._on_any(host,res) - super(CliRunnerCallbacks, self).on_failed(host, res, ignore_errors=ignore_errors) - - def on_ok(self, host, res): - # hide magic variables used for ansible-playbook - res.pop('verbose_override', None) - res.pop('verbose_always', None) - - self._on_any(host,res) - super(CliRunnerCallbacks, self).on_ok(host, res) - - def on_unreachable(self, host, res): - if type(res) == dict: - res = res.get('msg','') - display("%s | FAILED => %s" % (host, res), stderr=True, color='red', runner=self.runner) - if self.options.tree: - utils.write_tree_file( - self.options.tree, host, - utils.jsonify(dict(failed=True, msg=res),format=True) - ) - super(CliRunnerCallbacks, self).on_unreachable(host, res) - - def on_skipped(self, host, item=None): - display("%s | skipped" % (host), runner=self.runner) - super(CliRunnerCallbacks, self).on_skipped(host, item) - - def on_no_hosts(self): - display("no hosts matched\n", stderr=True, runner=self.runner) - super(CliRunnerCallbacks, self).on_no_hosts() - - def on_async_poll(self, host, res, jid, clock): - if jid not in self._async_notified: - self._async_notified[jid] = clock + 1 - if self._async_notified[jid] > clock: - self._async_notified[jid] = clock - display(" polling on %s, %ss remaining" % (jid, host, clock), runner=self.runner) - super(CliRunnerCallbacks, self).on_async_poll(host, res, jid, clock) - - def on_async_ok(self, host, res, jid): - if jid: - display(" finished on %s => %s"%(jid, host, utils.jsonify(res,format=True)), runner=self.runner) - super(CliRunnerCallbacks, self).on_async_ok(host, res, jid) - - def on_async_failed(self, host, res, jid): - display(" FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)), color='red', stderr=True, runner=self.runner) - super(CliRunnerCallbacks, self).on_async_failed(host,res,jid) - - def _on_any(self, host, result): - result2 = result.copy() - result2.pop('invocation', None) - (msg, color) = host_report_msg(host, self.options.module_name, result2, self.options.one_line) - display(msg, color=color, runner=self.runner) - if self.options.tree: - utils.write_tree_file(self.options.tree, host, utils.jsonify(result2,format=True)) - - def on_file_diff(self, host, diff): - display(utils.get_diff(diff), runner=self.runner) - super(CliRunnerCallbacks, self).on_file_diff(host, diff) - -######################################################################## - -class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): - ''' callbacks used for Runner() from /usr/bin/ansible-playbook ''' - - def __init__(self, stats, verbose=None): - - if verbose is None: - verbose = utils.VERBOSITY - - self.verbose = verbose - self.stats = stats - self._async_notified = {} - - def on_unreachable(self, host, results): - if self.runner.delegate_to: - host = '%s -> %s' % (host, self.runner.delegate_to) - - item = None - if type(results) == dict: - item = results.get('item', None) - if isinstance(item, unicode): - item = utils.unicode.to_bytes(item) - results = basic.json_dict_unicode_to_bytes(results) - else: - results = utils.unicode.to_bytes(results) - host = utils.unicode.to_bytes(host) - if item: - msg = "fatal: [%s] => (item=%s) => %s" % (host, item, results) - else: - msg = "fatal: [%s] => %s" % (host, results) - display(msg, color='red', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_unreachable(host, results) - - def on_failed(self, host, results, ignore_errors=False): - if self.runner.delegate_to: - host = '%s -> %s' % (host, self.runner.delegate_to) - - results2 = results.copy() - results2.pop('invocation', None) - - item = results2.get('item', None) - parsed = results2.get('parsed', True) - module_msg = '' - if not parsed: - module_msg = results2.pop('msg', None) - stderr = results2.pop('stderr', None) - stdout = results2.pop('stdout', None) - returned_msg = results2.pop('msg', None) - - results2['task'] = self.task.name - results2['role'] = self.task.role_name - results2['playbook'] = self.playbook.filename - - if item: - msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results2)) - else: - msg = "failed: [%s] => %s" % (host, utils.jsonify(results2)) - display(msg, color='red', runner=self.runner) - - if stderr: - display("stderr: %s" % stderr, color='red', runner=self.runner) - if stdout: - display("stdout: %s" % stdout, color='red', runner=self.runner) - if returned_msg: - display("msg: %s" % returned_msg, color='red', runner=self.runner) - if not parsed and module_msg: - display(module_msg, color='red', runner=self.runner) - if ignore_errors: - display("...ignoring", color='cyan', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_failed(host, results, ignore_errors=ignore_errors) - - def on_ok(self, host, host_result): - if self.runner.delegate_to: - host = '%s -> %s' % (host, self.runner.delegate_to) - - item = host_result.get('item', None) - - host_result2 = host_result.copy() - host_result2.pop('invocation', None) - verbose_always = host_result2.pop('verbose_always', False) - changed = host_result.get('changed', False) - ok_or_changed = 'ok' - if changed: - ok_or_changed = 'changed' - - # show verbose output for non-setup module results if --verbose is used - msg = '' - if (not self.verbose or host_result2.get("verbose_override",None) is not - None) and not verbose_always: - if item: - msg = "%s: [%s] => (item=%s)" % (ok_or_changed, host, item) - else: - if 'ansible_job_id' not in host_result or 'finished' in host_result: - msg = "%s: [%s]" % (ok_or_changed, host) - else: - # verbose ... - if item: - msg = "%s: [%s] => (item=%s) => %s" % (ok_or_changed, host, item, utils.jsonify(host_result2, format=verbose_always)) - else: - if 'ansible_job_id' not in host_result or 'finished' in host_result2: - msg = "%s: [%s] => %s" % (ok_or_changed, host, utils.jsonify(host_result2, format=verbose_always)) - - if msg != '': - if not changed: - display(msg, color='green', runner=self.runner) - else: - display(msg, color='yellow', runner=self.runner) - if constants.COMMAND_WARNINGS and 'warnings' in host_result2 and host_result2['warnings']: - for warning in host_result2['warnings']: - display("warning: %s" % warning, color='purple', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_ok(host, host_result) - - def on_skipped(self, host, item=None): - if self.runner.delegate_to: - host = '%s -> %s' % (host, self.runner.delegate_to) - - if constants.DISPLAY_SKIPPED_HOSTS: - msg = '' - if item: - msg = "skipping: [%s] => (item=%s)" % (host, item) - else: - msg = "skipping: [%s]" % host - display(msg, color='cyan', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_skipped(host, item) - - def on_no_hosts(self): - display("FATAL: no hosts matched or all hosts have already failed -- aborting\n", color='red', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_no_hosts() - - def on_async_poll(self, host, res, jid, clock): - if jid not in self._async_notified: - self._async_notified[jid] = clock + 1 - if self._async_notified[jid] > clock: - self._async_notified[jid] = clock - msg = " polling, %ss remaining"%(jid, clock) - display(msg, color='cyan', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_async_poll(host,res,jid,clock) - - def on_async_ok(self, host, res, jid): - if jid: - msg = " finished on %s"%(jid, host) - display(msg, color='cyan', runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_async_ok(host, res, jid) - - def on_async_failed(self, host, res, jid): - msg = " FAILED on %s" % (jid, host) - display(msg, color='red', stderr=True, runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_async_failed(host,res,jid) - - def on_file_diff(self, host, diff): - display(utils.get_diff(diff), runner=self.runner) - super(PlaybookRunnerCallbacks, self).on_file_diff(host, diff) - -######################################################################## - -class PlaybookCallbacks(object): - ''' playbook.py callbacks used by /usr/bin/ansible-playbook ''' - - def __init__(self, verbose=False): - - self.verbose = verbose - - def on_start(self): - call_callback_module('playbook_on_start') - - def on_notify(self, host, handler): - call_callback_module('playbook_on_notify', host, handler) - - def on_no_hosts_matched(self): - display("skipping: no hosts matched", color='cyan') - call_callback_module('playbook_on_no_hosts_matched') - - def on_no_hosts_remaining(self): - display("\nFATAL: all hosts have already failed -- aborting", color='red') - call_callback_module('playbook_on_no_hosts_remaining') - - def on_task_start(self, name, is_conditional): - name = utils.unicode.to_bytes(name) - msg = "TASK: [%s]" % name - if is_conditional: - msg = "NOTIFIED: [%s]" % name - - if hasattr(self, 'start_at'): - self.start_at = utils.unicode.to_bytes(self.start_at) - if name == self.start_at or fnmatch.fnmatch(name, self.start_at): - # we found out match, we can get rid of this now - del self.start_at - elif self.task.role_name: - # handle tasks prefixed with rolenames - actual_name = name.split('|', 1)[1].lstrip() - if actual_name == self.start_at or fnmatch.fnmatch(actual_name, self.start_at): - del self.start_at - - if hasattr(self, 'start_at'): # we still have start_at so skip the task - self.skip_task = True - elif hasattr(self, 'step') and self.step: - if isinstance(name, str): - name = utils.unicode.to_unicode(name) - msg = u'Perform task: %s (y/n/c): ' % name - if sys.stdout.encoding: - msg = to_bytes(msg, sys.stdout.encoding) - else: - msg = to_bytes(msg) - resp = raw_input(msg) - if resp.lower() in ['y','yes']: - self.skip_task = False - display(banner(msg)) - elif resp.lower() in ['c', 'continue']: - self.skip_task = False - self.step = False - display(banner(msg)) - else: - self.skip_task = True - else: - self.skip_task = False - display(banner(msg)) - - call_callback_module('playbook_on_task_start', name, is_conditional) - - def on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): - - if prompt and default is not None: - msg = "%s [%s]: " % (prompt, default) - elif prompt: - msg = "%s: " % prompt - else: - msg = 'input for %s: ' % varname - - def do_prompt(prompt, private): - if sys.stdout.encoding: - msg = prompt.encode(sys.stdout.encoding) - else: - # when piping the output, or at other times when stdout - # may not be the standard file descriptor, the stdout - # encoding may not be set, so default to something sane - msg = prompt.encode(locale.getpreferredencoding()) - if private: - return getpass.getpass(msg) - return raw_input(msg) - - - if confirm: - while True: - result = do_prompt(msg, private) - second = do_prompt("confirm " + msg, private) - if result == second: - break - display("***** VALUES ENTERED DO NOT MATCH ****") - else: - result = do_prompt(msg, private) - - # if result is false and default is not None - if not result and default is not None: - result = default - - - if encrypt: - result = utils.do_encrypt(result, encrypt, salt_size, salt) - - # handle utf-8 chars - result = to_unicode(result, errors='strict') - call_callback_module( 'playbook_on_vars_prompt', varname, private=private, prompt=prompt, - encrypt=encrypt, confirm=confirm, salt_size=salt_size, salt=None, default=default - ) - - return result - - def on_setup(self): - display(banner("GATHERING FACTS")) - call_callback_module('playbook_on_setup') - - def on_import_for_host(self, host, imported_file): - msg = "%s: importing %s" % (host, imported_file) - display(msg, color='cyan') - call_callback_module('playbook_on_import_for_host', host, imported_file) - - def on_not_import_for_host(self, host, missing_file): - msg = "%s: not importing file: %s" % (host, missing_file) - display(msg, color='cyan') - call_callback_module('playbook_on_not_import_for_host', host, missing_file) - - def on_play_start(self, name): - display(banner("PLAY [%s]" % name)) - call_callback_module('playbook_on_play_start', name) - - def on_stats(self, stats): - call_callback_module('playbook_on_stats', stats) - - diff --git a/v1/ansible/color.py b/v1/ansible/color.py deleted file mode 100644 index b3127d85fe..0000000000 --- a/v1/ansible/color.py +++ /dev/null @@ -1,74 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -import sys -import constants - -ANSIBLE_COLOR=True -if constants.ANSIBLE_NOCOLOR: - ANSIBLE_COLOR=False -elif not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty(): - ANSIBLE_COLOR=False -else: - try: - import curses - curses.setupterm() - if curses.tigetnum('colors') < 0: - ANSIBLE_COLOR=False - except ImportError: - # curses library was not found - pass - except curses.error: - # curses returns an error (e.g. could not find terminal) - ANSIBLE_COLOR=False - -if constants.ANSIBLE_FORCE_COLOR: - ANSIBLE_COLOR=True - -# --- begin "pretty" -# -# pretty - A miniature library that provides a Python print and stdout -# wrapper that makes colored terminal text easier to use (e.g. without -# having to mess around with ANSI escape sequences). This code is public -# domain - there is no license except that you must leave this header. -# -# Copyright (C) 2008 Brian Nez -# -# http://nezzen.net/2008/06/23/colored-text-in-python-using-ansi-escape-sequences/ - -codeCodes = { - 'black': '0;30', 'bright gray': '0;37', - 'blue': '0;34', 'white': '1;37', - 'green': '0;32', 'bright blue': '1;34', - 'cyan': '0;36', 'bright green': '1;32', - 'red': '0;31', 'bright cyan': '1;36', - 'purple': '0;35', 'bright red': '1;31', - 'yellow': '0;33', 'bright purple': '1;35', - 'dark gray': '1;30', 'bright yellow': '1;33', - 'normal': '0' -} - -def stringc(text, color): - """String in color.""" - - if ANSIBLE_COLOR: - return "\033["+codeCodes[color]+"m"+text+"\033[0m" - else: - return text - -# --- end "pretty" - diff --git a/v1/ansible/constants.py b/v1/ansible/constants.py deleted file mode 100644 index 2cdc08d8ce..0000000000 --- a/v1/ansible/constants.py +++ /dev/null @@ -1,212 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -import os -import pwd -import sys -import ConfigParser -from string import ascii_letters, digits - -# copied from utils, avoid circular reference fun :) -def mk_boolean(value): - if value is None: - return False - val = str(value) - if val.lower() in [ "true", "t", "y", "1", "yes" ]: - return True - else: - return False - -def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False): - ''' return a configuration variable with casting ''' - value = _get_config(p, section, key, env_var, default) - if boolean: - return mk_boolean(value) - if value and integer: - return int(value) - if value and floating: - return float(value) - if value and islist: - return [x.strip() for x in value.split(',')] - return value - -def _get_config(p, section, key, env_var, default): - ''' helper function for get_config ''' - if env_var is not None: - value = os.environ.get(env_var, None) - if value is not None: - return value - if p is not None: - try: - return p.get(section, key, raw=True) - except: - return default - return default - -def load_config_file(): - ''' Load Config File order(first found is used): ENV, CWD, HOME, /etc/ansible ''' - - p = ConfigParser.ConfigParser() - - path0 = os.getenv("ANSIBLE_CONFIG", None) - if path0 is not None: - path0 = os.path.expanduser(path0) - path1 = os.getcwd() + "/ansible.cfg" - path2 = os.path.expanduser("~/.ansible.cfg") - path3 = "/etc/ansible/ansible.cfg" - - for path in [path0, path1, path2, path3]: - if path is not None and os.path.exists(path): - try: - p.read(path) - except ConfigParser.Error as e: - print "Error reading config file: \n%s" % e - sys.exit(1) - return p - return None - -def shell_expand_path(path): - ''' shell_expand_path is needed as os.path.expanduser does not work - when path is None, which is the default for ANSIBLE_PRIVATE_KEY_FILE ''' - if path: - path = os.path.expanduser(os.path.expandvars(path)) - return path - -p = load_config_file() - -active_user = pwd.getpwuid(os.geteuid())[0] - -# check all of these extensions when looking for yaml files for things like -# group variables -- really anything we can load -YAML_FILENAME_EXTENSIONS = [ "", ".yml", ".yaml", ".json" ] - -# sections in config file -DEFAULTS='defaults' - -# configurable things -DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'inventory', 'ANSIBLE_INVENTORY', get_config(p, DEFAULTS,'hostfile','ANSIBLE_HOSTS', '/etc/ansible/hosts'))) -DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None) -DEFAULT_ROLES_PATH = shell_expand_path(get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles')) -DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp') -DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command') -DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, '*') -DEFAULT_FORKS = get_config(p, DEFAULTS, 'forks', 'ANSIBLE_FORKS', 5, integer=True) -DEFAULT_MODULE_ARGS = get_config(p, DEFAULTS, 'module_args', 'ANSIBLE_MODULE_ARGS', '') -DEFAULT_MODULE_LANG = get_config(p, DEFAULTS, 'module_lang', 'ANSIBLE_MODULE_LANG', 'en_US.UTF-8') -DEFAULT_TIMEOUT = get_config(p, DEFAULTS, 'timeout', 'ANSIBLE_TIMEOUT', 10, integer=True) -DEFAULT_POLL_INTERVAL = get_config(p, DEFAULTS, 'poll_interval', 'ANSIBLE_POLL_INTERVAL', 15, integer=True) -DEFAULT_REMOTE_USER = get_config(p, DEFAULTS, 'remote_user', 'ANSIBLE_REMOTE_USER', active_user) -DEFAULT_ASK_PASS = get_config(p, DEFAULTS, 'ask_pass', 'ANSIBLE_ASK_PASS', False, boolean=True) -DEFAULT_PRIVATE_KEY_FILE = shell_expand_path(get_config(p, DEFAULTS, 'private_key_file', 'ANSIBLE_PRIVATE_KEY_FILE', None)) -DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False, boolean=True) -DEFAULT_REMOTE_PORT = get_config(p, DEFAULTS, 'remote_port', 'ANSIBLE_REMOTE_PORT', None, integer=True) -DEFAULT_ASK_VAULT_PASS = get_config(p, DEFAULTS, 'ask_vault_pass', 'ANSIBLE_ASK_VAULT_PASS', False, boolean=True) -DEFAULT_VAULT_PASSWORD_FILE = shell_expand_path(get_config(p, DEFAULTS, 'vault_password_file', 'ANSIBLE_VAULT_PASSWORD_FILE', None)) -DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'smart') -DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False, boolean=True) -DEFAULT_MANAGED_STR = get_config(p, DEFAULTS, 'ansible_managed', None, 'Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}') -DEFAULT_SYSLOG_FACILITY = get_config(p, DEFAULTS, 'syslog_facility', 'ANSIBLE_SYSLOG_FACILITY', 'LOG_USER') -DEFAULT_KEEP_REMOTE_FILES = get_config(p, DEFAULTS, 'keep_remote_files', 'ANSIBLE_KEEP_REMOTE_FILES', False, boolean=True) -DEFAULT_SUDO = get_config(p, DEFAULTS, 'sudo', 'ANSIBLE_SUDO', False, boolean=True) -DEFAULT_SUDO_USER = get_config(p, DEFAULTS, 'sudo_user', 'ANSIBLE_SUDO_USER', 'root') -DEFAULT_SUDO_EXE = get_config(p, DEFAULTS, 'sudo_exe', 'ANSIBLE_SUDO_EXE', 'sudo') -DEFAULT_SUDO_FLAGS = get_config(p, DEFAULTS, 'sudo_flags', 'ANSIBLE_SUDO_FLAGS', '-H') -DEFAULT_HASH_BEHAVIOUR = get_config(p, DEFAULTS, 'hash_behaviour', 'ANSIBLE_HASH_BEHAVIOUR', 'replace') -DEFAULT_JINJA2_EXTENSIONS = get_config(p, DEFAULTS, 'jinja2_extensions', 'ANSIBLE_JINJA2_EXTENSIONS', None) -DEFAULT_EXECUTABLE = get_config(p, DEFAULTS, 'executable', 'ANSIBLE_EXECUTABLE', '/bin/sh') -DEFAULT_SU_EXE = get_config(p, DEFAULTS, 'su_exe', 'ANSIBLE_SU_EXE', 'su') -DEFAULT_SU = get_config(p, DEFAULTS, 'su', 'ANSIBLE_SU', False, boolean=True) -DEFAULT_SU_FLAGS = get_config(p, DEFAULTS, 'su_flags', 'ANSIBLE_SU_FLAGS', '') -DEFAULT_SU_USER = get_config(p, DEFAULTS, 'su_user', 'ANSIBLE_SU_USER', 'root') -DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_SU_PASS', False, boolean=True) -DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower() -DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', '')) - -# selinux -DEFAULT_SELINUX_SPECIAL_FS = get_config(p, 'selinux', 'special_context_filesystems', None, 'fuse, nfs, vboxsf', islist=True) - -#TODO: get rid of ternary chain mess -BECOME_METHODS = ['sudo','su','pbrun','pfexec','runas'] -BECOME_ERROR_STRINGS = {'sudo': 'Sorry, try again.', 'su': 'Authentication failure', 'pbrun': '', 'pfexec': '', 'runas': ''} -DEFAULT_BECOME = get_config(p, 'privilege_escalation', 'become', 'ANSIBLE_BECOME',False, boolean=True) -DEFAULT_BECOME_METHOD = get_config(p, 'privilege_escalation', 'become_method', 'ANSIBLE_BECOME_METHOD','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo' ).lower() -DEFAULT_BECOME_USER = get_config(p, 'privilege_escalation', 'become_user', 'ANSIBLE_BECOME_USER',default=None) -DEFAULT_BECOME_ASK_PASS = get_config(p, 'privilege_escalation', 'become_ask_pass', 'ANSIBLE_BECOME_ASK_PASS', False, boolean=True) -# need to rethink impementing these 2 -DEFAULT_BECOME_EXE = None -#DEFAULT_BECOME_EXE = get_config(p, DEFAULTS, 'become_exe', 'ANSIBLE_BECOME_EXE','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo') -#DEFAULT_BECOME_FLAGS = get_config(p, DEFAULTS, 'become_flags', 'ANSIBLE_BECOME_FLAGS',DEFAULT_SUDO_FLAGS if DEFAULT_SUDO else DEFAULT_SU_FLAGS if DEFAULT_SU else '-H') - - -DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action_plugins:/usr/share/ansible_plugins/action_plugins') -DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache_plugins:/usr/share/ansible_plugins/cache_plugins') -DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '~/.ansible/plugins/callback_plugins:/usr/share/ansible_plugins/callback_plugins') -DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '~/.ansible/plugins/connection_plugins:/usr/share/ansible_plugins/connection_plugins') -DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '~/.ansible/plugins/lookup_plugins:/usr/share/ansible_plugins/lookup_plugins') -DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '~/.ansible/plugins/vars_plugins:/usr/share/ansible_plugins/vars_plugins') -DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '~/.ansible/plugins/filter_plugins:/usr/share/ansible_plugins/filter_plugins') - -CACHE_PLUGIN = get_config(p, DEFAULTS, 'fact_caching', 'ANSIBLE_CACHE_PLUGIN', 'memory') -CACHE_PLUGIN_CONNECTION = get_config(p, DEFAULTS, 'fact_caching_connection', 'ANSIBLE_CACHE_PLUGIN_CONNECTION', None) -CACHE_PLUGIN_PREFIX = get_config(p, DEFAULTS, 'fact_caching_prefix', 'ANSIBLE_CACHE_PLUGIN_PREFIX', 'ansible_facts') -CACHE_PLUGIN_TIMEOUT = get_config(p, DEFAULTS, 'fact_caching_timeout', 'ANSIBLE_CACHE_PLUGIN_TIMEOUT', 24 * 60 * 60, integer=True) - -ANSIBLE_FORCE_COLOR = get_config(p, DEFAULTS, 'force_color', 'ANSIBLE_FORCE_COLOR', None, boolean=True) -ANSIBLE_NOCOLOR = get_config(p, DEFAULTS, 'nocolor', 'ANSIBLE_NOCOLOR', None, boolean=True) -ANSIBLE_NOCOWS = get_config(p, DEFAULTS, 'nocows', 'ANSIBLE_NOCOWS', None, boolean=True) -DISPLAY_SKIPPED_HOSTS = get_config(p, DEFAULTS, 'display_skipped_hosts', 'DISPLAY_SKIPPED_HOSTS', True, boolean=True) -DEFAULT_UNDEFINED_VAR_BEHAVIOR = get_config(p, DEFAULTS, 'error_on_undefined_vars', 'ANSIBLE_ERROR_ON_UNDEFINED_VARS', True, boolean=True) -HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', 'ANSIBLE_HOST_KEY_CHECKING', True, boolean=True) -SYSTEM_WARNINGS = get_config(p, DEFAULTS, 'system_warnings', 'ANSIBLE_SYSTEM_WARNINGS', True, boolean=True) -DEPRECATION_WARNINGS = get_config(p, DEFAULTS, 'deprecation_warnings', 'ANSIBLE_DEPRECATION_WARNINGS', True, boolean=True) -DEFAULT_CALLABLE_WHITELIST = get_config(p, DEFAULTS, 'callable_whitelist', 'ANSIBLE_CALLABLE_WHITELIST', [], islist=True) -COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'ANSIBLE_COMMAND_WARNINGS', False, boolean=True) -DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks', 'ANSIBLE_LOAD_CALLBACK_PLUGINS', False, boolean=True) -DEFAULT_FORCE_HANDLERS = get_config(p, DEFAULTS, 'force_handlers', 'ANSIBLE_FORCE_HANDLERS', False, boolean=True) - - -RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True) -RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/') - -# CONNECTION RELATED -ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) -ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r") -ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=True) -PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) -# obsolete -- will be formally removed -ZEROMQ_PORT = get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099, integer=True) -ACCELERATE_PORT = get_config(p, 'accelerate', 'accelerate_port', 'ACCELERATE_PORT', 5099, integer=True) -ACCELERATE_TIMEOUT = get_config(p, 'accelerate', 'accelerate_timeout', 'ACCELERATE_TIMEOUT', 30, integer=True) -ACCELERATE_CONNECT_TIMEOUT = get_config(p, 'accelerate', 'accelerate_connect_timeout', 'ACCELERATE_CONNECT_TIMEOUT', 1.0, floating=True) -ACCELERATE_DAEMON_TIMEOUT = get_config(p, 'accelerate', 'accelerate_daemon_timeout', 'ACCELERATE_DAEMON_TIMEOUT', 30, integer=True) -ACCELERATE_KEYS_DIR = get_config(p, 'accelerate', 'accelerate_keys_dir', 'ACCELERATE_KEYS_DIR', '~/.fireball.keys') -ACCELERATE_KEYS_DIR_PERMS = get_config(p, 'accelerate', 'accelerate_keys_dir_perms', 'ACCELERATE_KEYS_DIR_PERMS', '700') -ACCELERATE_KEYS_FILE_PERMS = get_config(p, 'accelerate', 'accelerate_keys_file_perms', 'ACCELERATE_KEYS_FILE_PERMS', '600') -ACCELERATE_MULTI_KEY = get_config(p, 'accelerate', 'accelerate_multi_key', 'ACCELERATE_MULTI_KEY', False, boolean=True) -PARAMIKO_PTY = get_config(p, 'paramiko_connection', 'pty', 'ANSIBLE_PARAMIKO_PTY', True, boolean=True) - -# characters included in auto-generated passwords -DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_" - -# non-configurable things -DEFAULT_BECOME_PASS = None -DEFAULT_SUDO_PASS = None -DEFAULT_REMOTE_PASS = None -DEFAULT_SUBSET = None -DEFAULT_SU_PASS = None -VAULT_VERSION_MIN = 1.0 -VAULT_VERSION_MAX = 1.0 diff --git a/v1/ansible/errors.py b/v1/ansible/errors.py deleted file mode 100644 index 65edbc294a..0000000000 --- a/v1/ansible/errors.py +++ /dev/null @@ -1,35 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -class AnsibleError(Exception): - ''' The base Ansible exception from which all others should subclass ''' - pass - -class AnsibleFileNotFound(AnsibleError): - pass - -class AnsibleConnectionFailed(AnsibleError): - pass - -class AnsibleYAMLValidationFailed(AnsibleError): - pass - -class AnsibleUndefinedVariable(AnsibleError): - pass - -class AnsibleFilterError(AnsibleError): - pass diff --git a/v1/ansible/inventory/__init__.py b/v1/ansible/inventory/__init__.py deleted file mode 100644 index f012246e22..0000000000 --- a/v1/ansible/inventory/__init__.py +++ /dev/null @@ -1,654 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -############################################# -import fnmatch -import os -import sys -import re -import subprocess - -import ansible.constants as C -from ansible.inventory.ini import InventoryParser -from ansible.inventory.script import InventoryScript -from ansible.inventory.dir import InventoryDirectory -from ansible.inventory.group import Group -from ansible.inventory.host import Host -from ansible import errors -from ansible import utils - -class Inventory(object): - """ - Host inventory for ansible. - """ - - __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', - 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list', - '_pattern_cache', '_vault_password', '_vars_plugins', '_playbook_basedir'] - - def __init__(self, host_list=C.DEFAULT_HOST_LIST, vault_password=None): - - # the host file file, or script path, or list of hosts - # if a list, inventory data will NOT be loaded - self.host_list = host_list - self._vault_password=vault_password - - # caching to avoid repeated calculations, particularly with - # external inventory scripts. - - self._vars_per_host = {} - self._vars_per_group = {} - self._hosts_cache = {} - self._groups_list = {} - self._pattern_cache = {} - - # to be set by calling set_playbook_basedir by playbook code - self._playbook_basedir = None - - # the inventory object holds a list of groups - self.groups = [] - - # a list of host(names) to contain current inquiries to - self._restriction = None - self._also_restriction = None - self._subset = None - - if isinstance(host_list, basestring): - if "," in host_list: - host_list = host_list.split(",") - host_list = [ h for h in host_list if h and h.strip() ] - - if host_list is None: - self.parser = None - elif isinstance(host_list, list): - self.parser = None - all = Group('all') - self.groups = [ all ] - ipv6_re = re.compile('\[([a-f:A-F0-9]*[%[0-z]+]?)\](?::(\d+))?') - for x in host_list: - m = ipv6_re.match(x) - if m: - all.add_host(Host(m.groups()[0], m.groups()[1])) - else: - if ":" in x: - tokens = x.rsplit(":", 1) - # if there is ':' in the address, then this is an ipv6 - if ':' in tokens[0]: - all.add_host(Host(x)) - else: - all.add_host(Host(tokens[0], tokens[1])) - else: - all.add_host(Host(x)) - elif os.path.exists(host_list): - if os.path.isdir(host_list): - # Ensure basedir is inside the directory - self.host_list = os.path.join(self.host_list, "") - self.parser = InventoryDirectory(filename=host_list) - self.groups = self.parser.groups.values() - else: - # check to see if the specified file starts with a - # shebang (#!/), so if an error is raised by the parser - # class we can show a more apropos error - shebang_present = False - try: - inv_file = open(host_list) - first_line = inv_file.readlines()[0] - inv_file.close() - if first_line.startswith('#!'): - shebang_present = True - except: - pass - - if utils.is_executable(host_list): - try: - self.parser = InventoryScript(filename=host_list) - self.groups = self.parser.groups.values() - except: - if not shebang_present: - raise errors.AnsibleError("The file %s is marked as executable, but failed to execute correctly. " % host_list + \ - "If this is not supposed to be an executable script, correct this with `chmod -x %s`." % host_list) - else: - raise - else: - try: - self.parser = InventoryParser(filename=host_list) - self.groups = self.parser.groups.values() - except: - if shebang_present: - raise errors.AnsibleError("The file %s looks like it should be an executable inventory script, but is not marked executable. " % host_list + \ - "Perhaps you want to correct this with `chmod +x %s`?" % host_list) - else: - raise - - utils.plugins.vars_loader.add_directory(self.basedir(), with_subdir=True) - else: - raise errors.AnsibleError("Unable to find an inventory file, specify one with -i ?") - - self._vars_plugins = [ x for x in utils.plugins.vars_loader.all(self) ] - - # get group vars from group_vars/ files and vars plugins - for group in self.groups: - group.vars = utils.combine_vars(group.vars, self.get_group_variables(group.name, vault_password=self._vault_password)) - - # get host vars from host_vars/ files and vars plugins - for host in self.get_hosts(): - host.vars = utils.combine_vars(host.vars, self.get_host_variables(host.name, vault_password=self._vault_password)) - - - def _match(self, str, pattern_str): - try: - if pattern_str.startswith('~'): - return re.search(pattern_str[1:], str) - else: - return fnmatch.fnmatch(str, pattern_str) - except Exception, e: - raise errors.AnsibleError('invalid host pattern: %s' % pattern_str) - - def _match_list(self, items, item_attr, pattern_str): - results = [] - try: - if not pattern_str.startswith('~'): - pattern = re.compile(fnmatch.translate(pattern_str)) - else: - pattern = re.compile(pattern_str[1:]) - except Exception, e: - raise errors.AnsibleError('invalid host pattern: %s' % pattern_str) - - for item in items: - if pattern.match(getattr(item, item_attr)): - results.append(item) - return results - - def get_hosts(self, pattern="all"): - """ - find all host names matching a pattern string, taking into account any inventory restrictions or - applied subsets. - """ - - # process patterns - if isinstance(pattern, list): - pattern = ';'.join(pattern) - patterns = pattern.replace(";",":").split(":") - hosts = self._get_hosts(patterns) - - # exclude hosts not in a subset, if defined - if self._subset: - subset = self._get_hosts(self._subset) - hosts = [ h for h in hosts if h in subset ] - - # exclude hosts mentioned in any restriction (ex: failed hosts) - if self._restriction is not None: - hosts = [ h for h in hosts if h.name in self._restriction ] - if self._also_restriction is not None: - hosts = [ h for h in hosts if h.name in self._also_restriction ] - - return hosts - - def _get_hosts(self, patterns): - """ - finds hosts that match a list of patterns. Handles negative - matches as well as intersection matches. - """ - - # Host specifiers should be sorted to ensure consistent behavior - pattern_regular = [] - pattern_intersection = [] - pattern_exclude = [] - for p in patterns: - if p.startswith("!"): - pattern_exclude.append(p) - elif p.startswith("&"): - pattern_intersection.append(p) - elif p: - pattern_regular.append(p) - - # if no regular pattern was given, hence only exclude and/or intersection - # make that magically work - if pattern_regular == []: - pattern_regular = ['all'] - - # when applying the host selectors, run those without the "&" or "!" - # first, then the &s, then the !s. - patterns = pattern_regular + pattern_intersection + pattern_exclude - - hosts = [] - - for p in patterns: - # avoid resolving a pattern that is a plain host - if p in self._hosts_cache: - hosts.append(self.get_host(p)) - else: - that = self.__get_hosts(p) - if p.startswith("!"): - hosts = [ h for h in hosts if h not in that ] - elif p.startswith("&"): - hosts = [ h for h in hosts if h in that ] - else: - to_append = [ h for h in that if h.name not in [ y.name for y in hosts ] ] - hosts.extend(to_append) - return hosts - - def __get_hosts(self, pattern): - """ - finds hosts that positively match a particular pattern. Does not - take into account negative matches. - """ - - if pattern in self._pattern_cache: - return self._pattern_cache[pattern] - - (name, enumeration_details) = self._enumeration_info(pattern) - hpat = self._hosts_in_unenumerated_pattern(name) - result = self._apply_ranges(pattern, hpat) - self._pattern_cache[pattern] = result - return result - - def _enumeration_info(self, pattern): - """ - returns (pattern, limits) taking a regular pattern and finding out - which parts of it correspond to start/stop offsets. limits is - a tuple of (start, stop) or None - """ - - # Do not parse regexes for enumeration info - if pattern.startswith('~'): - return (pattern, None) - - # The regex used to match on the range, which can be [x] or [x-y]. - pattern_re = re.compile("^(.*)\[([-]?[0-9]+)(?:(?:-)([0-9]+))?\](.*)$") - m = pattern_re.match(pattern) - if m: - (target, first, last, rest) = m.groups() - first = int(first) - if last: - if first < 0: - raise errors.AnsibleError("invalid range: negative indices cannot be used as the first item in a range") - last = int(last) - else: - last = first - return (target, (first, last)) - else: - return (pattern, None) - - def _apply_ranges(self, pat, hosts): - """ - given a pattern like foo, that matches hosts, return all of hosts - given a pattern like foo[0:5], where foo matches hosts, return the first 6 hosts - """ - - # If there are no hosts to select from, just return the - # empty set. This prevents trying to do selections on an empty set. - # issue#6258 - if not hosts: - return hosts - - (loose_pattern, limits) = self._enumeration_info(pat) - if not limits: - return hosts - - (left, right) = limits - - if left == '': - left = 0 - if right == '': - right = 0 - left=int(left) - right=int(right) - try: - if left != right: - return hosts[left:right] - else: - return [ hosts[left] ] - except IndexError: - raise errors.AnsibleError("no hosts matching the pattern '%s' were found" % pat) - - def _create_implicit_localhost(self, pattern): - new_host = Host(pattern) - new_host.set_variable("ansible_python_interpreter", sys.executable) - new_host.set_variable("ansible_connection", "local") - ungrouped = self.get_group("ungrouped") - if ungrouped is None: - self.add_group(Group('ungrouped')) - ungrouped = self.get_group('ungrouped') - self.get_group('all').add_child_group(ungrouped) - ungrouped.add_host(new_host) - return new_host - - def _hosts_in_unenumerated_pattern(self, pattern): - """ Get all host names matching the pattern """ - - results = [] - hosts = [] - hostnames = set() - - # ignore any negative checks here, this is handled elsewhere - pattern = pattern.replace("!","").replace("&", "") - - def __append_host_to_results(host): - if host not in results and host.name not in hostnames: - hostnames.add(host.name) - results.append(host) - - groups = self.get_groups() - for group in groups: - if pattern == 'all': - for host in group.get_hosts(): - __append_host_to_results(host) - else: - if self._match(group.name, pattern): - for host in group.get_hosts(): - __append_host_to_results(host) - else: - matching_hosts = self._match_list(group.get_hosts(), 'name', pattern) - for host in matching_hosts: - __append_host_to_results(host) - - if pattern in ["localhost", "127.0.0.1"] and len(results) == 0: - new_host = self._create_implicit_localhost(pattern) - results.append(new_host) - return results - - def clear_pattern_cache(self): - ''' called exclusively by the add_host plugin to allow patterns to be recalculated ''' - self._pattern_cache = {} - - def groups_for_host(self, host): - if host in self._hosts_cache: - return self._hosts_cache[host].get_groups() - else: - return [] - - def groups_list(self): - if not self._groups_list: - groups = {} - for g in self.groups: - groups[g.name] = [h.name for h in g.get_hosts()] - ancestors = g.get_ancestors() - for a in ancestors: - if a.name not in groups: - groups[a.name] = [h.name for h in a.get_hosts()] - self._groups_list = groups - return self._groups_list - - def get_groups(self): - return self.groups - - def get_host(self, hostname): - if hostname not in self._hosts_cache: - self._hosts_cache[hostname] = self._get_host(hostname) - return self._hosts_cache[hostname] - - def _get_host(self, hostname): - if hostname in ['localhost','127.0.0.1']: - for host in self.get_group('all').get_hosts(): - if host.name in ['localhost', '127.0.0.1']: - return host - return self._create_implicit_localhost(hostname) - else: - for group in self.groups: - for host in group.get_hosts(): - if hostname == host.name: - return host - return None - - def get_group(self, groupname): - for group in self.groups: - if group.name == groupname: - return group - return None - - def get_group_variables(self, groupname, update_cached=False, vault_password=None): - if groupname not in self._vars_per_group or update_cached: - self._vars_per_group[groupname] = self._get_group_variables(groupname, vault_password=vault_password) - return self._vars_per_group[groupname] - - def _get_group_variables(self, groupname, vault_password=None): - - group = self.get_group(groupname) - if group is None: - raise errors.AnsibleError("group not found: %s" % groupname) - - vars = {} - - # plugin.get_group_vars retrieves just vars for specific group - vars_results = [ plugin.get_group_vars(group, vault_password=vault_password) for plugin in self._vars_plugins if hasattr(plugin, 'get_group_vars')] - for updated in vars_results: - if updated is not None: - vars = utils.combine_vars(vars, updated) - - # Read group_vars/ files - vars = utils.combine_vars(vars, self.get_group_vars(group)) - - return vars - - def get_variables(self, hostname, update_cached=False, vault_password=None): - - host = self.get_host(hostname) - if not host: - raise errors.AnsibleError("host not found: %s" % hostname) - return host.get_variables() - - def get_host_variables(self, hostname, update_cached=False, vault_password=None): - - if hostname not in self._vars_per_host or update_cached: - self._vars_per_host[hostname] = self._get_host_variables(hostname, vault_password=vault_password) - return self._vars_per_host[hostname] - - def _get_host_variables(self, hostname, vault_password=None): - - host = self.get_host(hostname) - if host is None: - raise errors.AnsibleError("host not found: %s" % hostname) - - vars = {} - - # plugin.run retrieves all vars (also from groups) for host - vars_results = [ plugin.run(host, vault_password=vault_password) for plugin in self._vars_plugins if hasattr(plugin, 'run')] - for updated in vars_results: - if updated is not None: - vars = utils.combine_vars(vars, updated) - - # plugin.get_host_vars retrieves just vars for specific host - vars_results = [ plugin.get_host_vars(host, vault_password=vault_password) for plugin in self._vars_plugins if hasattr(plugin, 'get_host_vars')] - for updated in vars_results: - if updated is not None: - vars = utils.combine_vars(vars, updated) - - # still need to check InventoryParser per host vars - # which actually means InventoryScript per host, - # which is not performant - if self.parser is not None: - vars = utils.combine_vars(vars, self.parser.get_host_variables(host)) - - # Read host_vars/ files - vars = utils.combine_vars(vars, self.get_host_vars(host)) - - return vars - - def add_group(self, group): - if group.name not in self.groups_list(): - self.groups.append(group) - self._groups_list = None # invalidate internal cache - else: - raise errors.AnsibleError("group already in inventory: %s" % group.name) - - def list_hosts(self, pattern="all"): - - """ return a list of hostnames for a pattern """ - - result = [ h.name for h in self.get_hosts(pattern) ] - if len(result) == 0 and pattern in ["localhost", "127.0.0.1"]: - result = [pattern] - return result - - def list_groups(self): - return sorted([ g.name for g in self.groups ], key=lambda x: x) - - # TODO: remove this function - def get_restriction(self): - return self._restriction - - def restrict_to(self, restriction): - """ - Restrict list operations to the hosts given in restriction. This is used - to exclude failed hosts in main playbook code, don't use this for other - reasons. - """ - if not isinstance(restriction, list): - restriction = [ restriction ] - self._restriction = restriction - - def also_restrict_to(self, restriction): - """ - Works like restict_to but offers an additional restriction. Playbooks use this - to implement serial behavior. - """ - if not isinstance(restriction, list): - restriction = [ restriction ] - self._also_restriction = restriction - - def subset(self, subset_pattern): - """ - Limits inventory results to a subset of inventory that matches a given - pattern, such as to select a given geographic of numeric slice amongst - a previous 'hosts' selection that only select roles, or vice versa. - Corresponds to --limit parameter to ansible-playbook - """ - if subset_pattern is None: - self._subset = None - else: - subset_pattern = subset_pattern.replace(',',':') - subset_pattern = subset_pattern.replace(";",":").split(":") - results = [] - # allow Unix style @filename data - for x in subset_pattern: - if x.startswith("@"): - fd = open(x[1:]) - results.extend(fd.read().split("\n")) - fd.close() - else: - results.append(x) - self._subset = results - - def lift_restriction(self): - """ Do not restrict list operations """ - self._restriction = None - - def lift_also_restriction(self): - """ Clears the also restriction """ - self._also_restriction = None - - def is_file(self): - """ did inventory come from a file? """ - if not isinstance(self.host_list, basestring): - return False - return os.path.exists(self.host_list) - - def basedir(self): - """ if inventory came from a file, what's the directory? """ - if not self.is_file(): - return None - dname = os.path.dirname(self.host_list) - if dname is None or dname == '' or dname == '.': - cwd = os.getcwd() - return os.path.abspath(cwd) - return os.path.abspath(dname) - - def src(self): - """ if inventory came from a file, what's the directory and file name? """ - if not self.is_file(): - return None - return self.host_list - - def playbook_basedir(self): - """ returns the directory of the current playbook """ - return self._playbook_basedir - - def set_playbook_basedir(self, dir): - """ - sets the base directory of the playbook so inventory can use it as a - basedir for host_ and group_vars, and other things. - """ - # Only update things if dir is a different playbook basedir - if dir != self._playbook_basedir: - self._playbook_basedir = dir - # get group vars from group_vars/ files - for group in self.groups: - group.vars = utils.combine_vars(group.vars, self.get_group_vars(group, new_pb_basedir=True)) - # get host vars from host_vars/ files - for host in self.get_hosts(): - host.vars = utils.combine_vars(host.vars, self.get_host_vars(host, new_pb_basedir=True)) - # invalidate cache - self._vars_per_host = {} - self._vars_per_group = {} - - def get_host_vars(self, host, new_pb_basedir=False): - """ Read host_vars/ files """ - return self._get_hostgroup_vars(host=host, group=None, new_pb_basedir=new_pb_basedir) - - def get_group_vars(self, group, new_pb_basedir=False): - """ Read group_vars/ files """ - return self._get_hostgroup_vars(host=None, group=group, new_pb_basedir=new_pb_basedir) - - def _get_hostgroup_vars(self, host=None, group=None, new_pb_basedir=False): - """ - Loads variables from group_vars/ and host_vars/ in directories parallel - to the inventory base directory or in the same directory as the playbook. Variables in the playbook - dir will win over the inventory dir if files are in both. - """ - - results = {} - scan_pass = 0 - _basedir = self.basedir() - - # look in both the inventory base directory and the playbook base directory - # unless we do an update for a new playbook base dir - if not new_pb_basedir: - basedirs = [_basedir, self._playbook_basedir] - else: - basedirs = [self._playbook_basedir] - - for basedir in basedirs: - - # this can happen from particular API usages, particularly if not run - # from /usr/bin/ansible-playbook - if basedir is None: - continue - - scan_pass = scan_pass + 1 - - # it's not an eror if the directory does not exist, keep moving - if not os.path.exists(basedir): - continue - - # save work of second scan if the directories are the same - if _basedir == self._playbook_basedir and scan_pass != 1: - continue - - if group and host is None: - # load vars in dir/group_vars/name_of_group - base_path = os.path.join(basedir, "group_vars/%s" % group.name) - results = utils.load_vars(base_path, results, vault_password=self._vault_password) - - elif host and group is None: - # same for hostvars in dir/host_vars/name_of_host - base_path = os.path.join(basedir, "host_vars/%s" % host.name) - results = utils.load_vars(base_path, results, vault_password=self._vault_password) - - # all done, results is a dictionary of variables for this particular host. - return results - diff --git a/v1/ansible/inventory/dir.py b/v1/ansible/inventory/dir.py deleted file mode 100644 index 9ac23fff89..0000000000 --- a/v1/ansible/inventory/dir.py +++ /dev/null @@ -1,229 +0,0 @@ -# (c) 2013, Daniel Hokka Zakrisson -# (c) 2014, Serge van Ginderachter -# -# 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 . - -############################################# - -import os -import ansible.constants as C -from ansible.inventory.host import Host -from ansible.inventory.group import Group -from ansible.inventory.ini import InventoryParser -from ansible.inventory.script import InventoryScript -from ansible import utils -from ansible import errors - -class InventoryDirectory(object): - ''' Host inventory parser for ansible using a directory of inventories. ''' - - def __init__(self, filename=C.DEFAULT_HOST_LIST): - self.names = os.listdir(filename) - self.names.sort() - self.directory = filename - self.parsers = [] - self.hosts = {} - self.groups = {} - - for i in self.names: - - # Skip files that end with certain extensions or characters - if any(i.endswith(ext) for ext in ("~", ".orig", ".bak", ".ini", ".retry", ".pyc", ".pyo")): - continue - # Skip hidden files - if i.startswith('.') and not i.startswith('./'): - continue - # These are things inside of an inventory basedir - if i in ("host_vars", "group_vars", "vars_plugins"): - continue - fullpath = os.path.join(self.directory, i) - if os.path.isdir(fullpath): - parser = InventoryDirectory(filename=fullpath) - elif utils.is_executable(fullpath): - parser = InventoryScript(filename=fullpath) - else: - parser = InventoryParser(filename=fullpath) - self.parsers.append(parser) - - # retrieve all groups and hosts form the parser and add them to - # self, don't look at group lists yet, to avoid - # recursion trouble, but just make sure all objects exist in self - newgroups = parser.groups.values() - for group in newgroups: - for host in group.hosts: - self._add_host(host) - for group in newgroups: - self._add_group(group) - - # now check the objects lists so they contain only objects from - # self; membership data in groups is already fine (except all & - # ungrouped, see later), but might still reference objects not in self - for group in self.groups.values(): - # iterate on a copy of the lists, as those lists get changed in - # the loop - # list with group's child group objects: - for child in group.child_groups[:]: - if child != self.groups[child.name]: - group.child_groups.remove(child) - group.child_groups.append(self.groups[child.name]) - # list with group's parent group objects: - for parent in group.parent_groups[:]: - if parent != self.groups[parent.name]: - group.parent_groups.remove(parent) - group.parent_groups.append(self.groups[parent.name]) - # list with group's host objects: - for host in group.hosts[:]: - if host != self.hosts[host.name]: - group.hosts.remove(host) - group.hosts.append(self.hosts[host.name]) - # also check here that the group that contains host, is - # also contained in the host's group list - if group not in self.hosts[host.name].groups: - self.hosts[host.name].groups.append(group) - - # extra checks on special groups all and ungrouped - # remove hosts from 'ungrouped' if they became member of other groups - if 'ungrouped' in self.groups: - ungrouped = self.groups['ungrouped'] - # loop on a copy of ungrouped hosts, as we want to change that list - for host in ungrouped.hosts[:]: - if len(host.groups) > 1: - host.groups.remove(ungrouped) - ungrouped.hosts.remove(host) - - # remove hosts from 'all' if they became member of other groups - # all should only contain direct children, not grandchildren - # direct children should have dept == 1 - if 'all' in self.groups: - allgroup = self.groups['all' ] - # loop on a copy of all's child groups, as we want to change that list - for group in allgroup.child_groups[:]: - # groups might once have beeen added to all, and later be added - # to another group: we need to remove the link wit all then - if len(group.parent_groups) > 1 and allgroup in group.parent_groups: - # real children of all have just 1 parent, all - # this one has more, so not a direct child of all anymore - group.parent_groups.remove(allgroup) - allgroup.child_groups.remove(group) - elif allgroup not in group.parent_groups: - # this group was once added to all, but doesn't list it as - # a parent any more; the info in the group is the correct - # info - allgroup.child_groups.remove(group) - - - def _add_group(self, group): - """ Merge an existing group or add a new one; - Track parent and child groups, and hosts of the new one """ - - if group.name not in self.groups: - # it's brand new, add him! - self.groups[group.name] = group - if self.groups[group.name] != group: - # different object, merge - self._merge_groups(self.groups[group.name], group) - - def _add_host(self, host): - if host.name not in self.hosts: - # Papa's got a brand new host - self.hosts[host.name] = host - if self.hosts[host.name] != host: - # different object, merge - self._merge_hosts(self.hosts[host.name], host) - - def _merge_groups(self, group, newgroup): - """ Merge all of instance newgroup into group, - update parent/child relationships - group lists may still contain group objects that exist in self with - same name, but was instanciated as a different object in some other - inventory parser; these are handled later """ - - # name - if group.name != newgroup.name: - raise errors.AnsibleError("Cannot merge group %s with %s" % (group.name, newgroup.name)) - - # depth - group.depth = max([group.depth, newgroup.depth]) - - # hosts list (host objects are by now already added to self.hosts) - for host in newgroup.hosts: - grouphosts = dict([(h.name, h) for h in group.hosts]) - if host.name in grouphosts: - # same host name but different object, merge - self._merge_hosts(grouphosts[host.name], host) - else: - # new membership, add host to group from self - # group from self will also be added again to host.groups, but - # as different object - group.add_host(self.hosts[host.name]) - # now remove this the old object for group in host.groups - for hostgroup in [g for g in host.groups]: - if hostgroup.name == group.name and hostgroup != self.groups[group.name]: - self.hosts[host.name].groups.remove(hostgroup) - - - # group child membership relation - for newchild in newgroup.child_groups: - # dict with existing child groups: - childgroups = dict([(g.name, g) for g in group.child_groups]) - # check if child of new group is already known as a child - if newchild.name not in childgroups: - self.groups[group.name].add_child_group(newchild) - - # group parent membership relation - for newparent in newgroup.parent_groups: - # dict with existing parent groups: - parentgroups = dict([(g.name, g) for g in group.parent_groups]) - # check if parent of new group is already known as a parent - if newparent.name not in parentgroups: - if newparent.name not in self.groups: - # group does not exist yet in self, import him - self.groups[newparent.name] = newparent - # group now exists but not yet as a parent here - self.groups[newparent.name].add_child_group(group) - - # variables - group.vars = utils.combine_vars(group.vars, newgroup.vars) - - def _merge_hosts(self,host, newhost): - """ Merge all of instance newhost into host """ - - # name - if host.name != newhost.name: - raise errors.AnsibleError("Cannot merge host %s with %s" % (host.name, newhost.name)) - - # group membership relation - for newgroup in newhost.groups: - # dict with existing groups: - hostgroups = dict([(g.name, g) for g in host.groups]) - # check if new group is already known as a group - if newgroup.name not in hostgroups: - if newgroup.name not in self.groups: - # group does not exist yet in self, import him - self.groups[newgroup.name] = newgroup - # group now exists but doesn't have host yet - self.groups[newgroup.name].add_host(host) - - # variables - host.vars = utils.combine_vars(host.vars, newhost.vars) - - def get_host_variables(self, host): - """ Gets additional host variables from all inventories """ - vars = {} - for i in self.parsers: - vars.update(i.get_host_variables(host)) - return vars - diff --git a/v1/ansible/inventory/expand_hosts.py b/v1/ansible/inventory/expand_hosts.py deleted file mode 100644 index f129740935..0000000000 --- a/v1/ansible/inventory/expand_hosts.py +++ /dev/null @@ -1,116 +0,0 @@ -# (c) 2012, Zettar Inc. -# Written by Chin Fang -# -# This file is part of Ansible -# -# This module 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. -# -# This software 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 this software. If not, see . -# - -''' -This module is for enhancing ansible's inventory parsing capability such -that it can deal with hostnames specified using a simple pattern in the -form of [beg:end], example: [1:5], [a:c], [D:G]. If beg is not specified, -it defaults to 0. - -If beg is given and is left-zero-padded, e.g. '001', it is taken as a -formatting hint when the range is expanded. e.g. [001:010] is to be -expanded into 001, 002 ...009, 010. - -Note that when beg is specified with left zero padding, then the length of -end must be the same as that of beg, else an exception is raised. -''' -import string - -from ansible import errors - -def detect_range(line = None): - ''' - A helper function that checks a given host line to see if it contains - a range pattern described in the docstring above. - - Returnes True if the given line contains a pattern, else False. - ''' - if 0 <= line.find("[") < line.find(":") < line.find("]"): - return True - else: - return False - -def expand_hostname_range(line = None): - ''' - A helper function that expands a given line that contains a pattern - specified in top docstring, and returns a list that consists of the - expanded version. - - The '[' and ']' characters are used to maintain the pseudo-code - appearance. They are replaced in this function with '|' to ease - string splitting. - - References: http://ansible.github.com/patterns.html#hosts-and-groups - ''' - all_hosts = [] - if line: - # A hostname such as db[1:6]-node is considered to consists - # three parts: - # head: 'db' - # nrange: [1:6]; range() is a built-in. Can't use the name - # tail: '-node' - - # Add support for multiple ranges in a host so: - # db[01:10:3]node-[01:10] - # - to do this we split off at the first [...] set, getting the list - # of hosts and then repeat until none left. - # - also add an optional third parameter which contains the step. (Default: 1) - # so range can be [01:10:2] -> 01 03 05 07 09 - # FIXME: make this work for alphabetic sequences too. - - (head, nrange, tail) = line.replace('[','|',1).replace(']','|',1).split('|') - bounds = nrange.split(":") - if len(bounds) != 2 and len(bounds) != 3: - raise errors.AnsibleError("host range incorrectly specified") - beg = bounds[0] - end = bounds[1] - if len(bounds) == 2: - step = 1 - else: - step = bounds[2] - if not beg: - beg = "0" - if not end: - raise errors.AnsibleError("host range end value missing") - if beg[0] == '0' and len(beg) > 1: - rlen = len(beg) # range length formatting hint - if rlen != len(end): - raise errors.AnsibleError("host range format incorrectly specified!") - fill = lambda _: str(_).zfill(rlen) # range sequence - else: - fill = str - - try: - i_beg = string.ascii_letters.index(beg) - i_end = string.ascii_letters.index(end) - if i_beg > i_end: - raise errors.AnsibleError("host range format incorrectly specified!") - seq = string.ascii_letters[i_beg:i_end+1] - except ValueError: # not an alpha range - seq = range(int(beg), int(end)+1, int(step)) - - for rseq in seq: - hname = ''.join((head, fill(rseq), tail)) - - if detect_range(hname): - all_hosts.extend( expand_hostname_range( hname ) ) - else: - all_hosts.append(hname) - - return all_hosts diff --git a/v1/ansible/inventory/group.py b/v1/ansible/inventory/group.py deleted file mode 100644 index 262558e69c..0000000000 --- a/v1/ansible/inventory/group.py +++ /dev/null @@ -1,117 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -class Group(object): - ''' a group of ansible hosts ''' - - __slots__ = [ 'name', 'hosts', 'vars', 'child_groups', 'parent_groups', 'depth', '_hosts_cache' ] - - def __init__(self, name=None): - - self.depth = 0 - self.name = name - self.hosts = [] - self.vars = {} - self.child_groups = [] - self.parent_groups = [] - self._hosts_cache = None - #self.clear_hosts_cache() - if self.name is None: - raise Exception("group name is required") - - def add_child_group(self, group): - - if self == group: - raise Exception("can't add group to itself") - - # don't add if it's already there - if not group in self.child_groups: - self.child_groups.append(group) - - # update the depth of the child - group.depth = max([self.depth+1, group.depth]) - - # update the depth of the grandchildren - group._check_children_depth() - - # now add self to child's parent_groups list, but only if there - # isn't already a group with the same name - if not self.name in [g.name for g in group.parent_groups]: - group.parent_groups.append(self) - - self.clear_hosts_cache() - - def _check_children_depth(self): - - for group in self.child_groups: - group.depth = max([self.depth+1, group.depth]) - group._check_children_depth() - - def add_host(self, host): - - self.hosts.append(host) - host.add_group(self) - self.clear_hosts_cache() - - def set_variable(self, key, value): - - self.vars[key] = value - - def clear_hosts_cache(self): - - self._hosts_cache = None - for g in self.parent_groups: - g.clear_hosts_cache() - - def get_hosts(self): - - if self._hosts_cache is None: - self._hosts_cache = self._get_hosts() - - return self._hosts_cache - - def _get_hosts(self): - - hosts = [] - seen = {} - for kid in self.child_groups: - kid_hosts = kid.get_hosts() - for kk in kid_hosts: - if kk not in seen: - seen[kk] = 1 - hosts.append(kk) - for mine in self.hosts: - if mine not in seen: - seen[mine] = 1 - hosts.append(mine) - return hosts - - def get_variables(self): - return self.vars.copy() - - def _get_ancestors(self): - - results = {} - for g in self.parent_groups: - results[g.name] = g - results.update(g._get_ancestors()) - return results - - def get_ancestors(self): - - return self._get_ancestors().values() - diff --git a/v1/ansible/inventory/host.py b/v1/ansible/inventory/host.py deleted file mode 100644 index d4dc20fa46..0000000000 --- a/v1/ansible/inventory/host.py +++ /dev/null @@ -1,67 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -import ansible.constants as C -from ansible import utils - -class Host(object): - ''' a single ansible host ''' - - __slots__ = [ 'name', 'vars', 'groups' ] - - def __init__(self, name=None, port=None): - - self.name = name - self.vars = {} - self.groups = [] - if port and port != C.DEFAULT_REMOTE_PORT: - self.set_variable('ansible_ssh_port', int(port)) - - if self.name is None: - raise Exception("host name is required") - - def add_group(self, group): - - self.groups.append(group) - - def set_variable(self, key, value): - - self.vars[key]=value - - def get_groups(self): - - groups = {} - for g in self.groups: - groups[g.name] = g - ancestors = g.get_ancestors() - for a in ancestors: - groups[a.name] = a - return groups.values() - - def get_variables(self): - - results = {} - groups = self.get_groups() - for group in sorted(groups, key=lambda g: g.depth): - results = utils.combine_vars(results, group.get_variables()) - results = utils.combine_vars(results, self.vars) - results['inventory_hostname'] = self.name - results['inventory_hostname_short'] = self.name.split('.')[0] - results['group_names'] = sorted([ g.name for g in groups if g.name != 'all']) - return results - - diff --git a/v1/ansible/inventory/ini.py b/v1/ansible/inventory/ini.py deleted file mode 100644 index bd9a98e7f8..0000000000 --- a/v1/ansible/inventory/ini.py +++ /dev/null @@ -1,208 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -############################################# - -import ansible.constants as C -from ansible.inventory.host import Host -from ansible.inventory.group import Group -from ansible.inventory.expand_hosts import detect_range -from ansible.inventory.expand_hosts import expand_hostname_range -from ansible import errors -from ansible import utils -import shlex -import re -import ast - -class InventoryParser(object): - """ - Host inventory for ansible. - """ - - def __init__(self, filename=C.DEFAULT_HOST_LIST): - - with open(filename) as fh: - self.filename = filename - self.lines = fh.readlines() - self.groups = {} - self.hosts = {} - self._parse() - - def _parse(self): - - self._parse_base_groups() - self._parse_group_children() - self._add_allgroup_children() - self._parse_group_variables() - return self.groups - - @staticmethod - def _parse_value(v): - if "#" not in v: - try: - ret = ast.literal_eval(v) - if not isinstance(ret, float): - # Do not trim floats. Eg: "1.20" to 1.2 - return ret - # Using explicit exceptions. - # Likely a string that literal_eval does not like. We wil then just set it. - except ValueError: - # For some reason this was thought to be malformed. - pass - except SyntaxError: - # Is this a hash with an equals at the end? - pass - return v - - # [webservers] - # alpha - # beta:2345 - # gamma sudo=True user=root - # delta asdf=jkl favcolor=red - - def _add_allgroup_children(self): - - for group in self.groups.values(): - if group.depth == 0 and group.name != 'all': - self.groups['all'].add_child_group(group) - - - def _parse_base_groups(self): - # FIXME: refactor - - ungrouped = Group(name='ungrouped') - all = Group(name='all') - all.add_child_group(ungrouped) - - self.groups = dict(all=all, ungrouped=ungrouped) - active_group_name = 'ungrouped' - - for lineno in range(len(self.lines)): - line = utils.before_comment(self.lines[lineno]).strip() - if line.startswith("[") and line.endswith("]"): - active_group_name = line.replace("[","").replace("]","") - if ":vars" in line or ":children" in line: - active_group_name = active_group_name.rsplit(":", 1)[0] - if active_group_name not in self.groups: - new_group = self.groups[active_group_name] = Group(name=active_group_name) - active_group_name = None - elif active_group_name not in self.groups: - new_group = self.groups[active_group_name] = Group(name=active_group_name) - elif line.startswith(";") or line == '': - pass - elif active_group_name: - tokens = shlex.split(line) - if len(tokens) == 0: - continue - hostname = tokens[0] - port = C.DEFAULT_REMOTE_PORT - # Three cases to check: - # 0. A hostname that contains a range pesudo-code and a port - # 1. A hostname that contains just a port - if hostname.count(":") > 1: - # Possible an IPv6 address, or maybe a host line with multiple ranges - # IPv6 with Port XXX:XXX::XXX.port - # FQDN foo.example.com - if hostname.count(".") == 1: - (hostname, port) = hostname.rsplit(".", 1) - elif ("[" in hostname and - "]" in hostname and - ":" in hostname and - (hostname.rindex("]") < hostname.rindex(":")) or - ("]" not in hostname and ":" in hostname)): - (hostname, port) = hostname.rsplit(":", 1) - - hostnames = [] - if detect_range(hostname): - hostnames = expand_hostname_range(hostname) - else: - hostnames = [hostname] - - for hn in hostnames: - host = None - if hn in self.hosts: - host = self.hosts[hn] - else: - host = Host(name=hn, port=port) - self.hosts[hn] = host - if len(tokens) > 1: - for t in tokens[1:]: - if t.startswith('#'): - break - try: - (k,v) = t.split("=", 1) - except ValueError, e: - raise errors.AnsibleError("%s:%s: Invalid ini entry: %s - %s" % (self.filename, lineno + 1, t, str(e))) - host.set_variable(k, self._parse_value(v)) - self.groups[active_group_name].add_host(host) - - # [southeast:children] - # atlanta - # raleigh - - def _parse_group_children(self): - group = None - - for lineno in range(len(self.lines)): - line = self.lines[lineno].strip() - if line is None or line == '': - continue - if line.startswith("[") and ":children]" in line: - line = line.replace("[","").replace(":children]","") - group = self.groups.get(line, None) - if group is None: - group = self.groups[line] = Group(name=line) - elif line.startswith("#") or line.startswith(";"): - pass - elif line.startswith("["): - group = None - elif group: - kid_group = self.groups.get(line, None) - if kid_group is None: - raise errors.AnsibleError("%s:%d: child group is not defined: (%s)" % (self.filename, lineno + 1, line)) - else: - group.add_child_group(kid_group) - - - # [webservers:vars] - # http_port=1234 - # maxRequestsPerChild=200 - - def _parse_group_variables(self): - group = None - for lineno in range(len(self.lines)): - line = self.lines[lineno].strip() - if line.startswith("[") and ":vars]" in line: - line = line.replace("[","").replace(":vars]","") - group = self.groups.get(line, None) - if group is None: - raise errors.AnsibleError("%s:%d: can't add vars to undefined group: %s" % (self.filename, lineno + 1, line)) - elif line.startswith("#") or line.startswith(";"): - pass - elif line.startswith("["): - group = None - elif line == '': - pass - elif group: - if "=" not in line: - raise errors.AnsibleError("%s:%d: variables assigned to group must be in key=value form" % (self.filename, lineno + 1)) - else: - (k, v) = [e.strip() for e in line.split("=", 1)] - group.set_variable(k, self._parse_value(v)) - - def get_host_variables(self, host): - return {} diff --git a/v1/ansible/inventory/script.py b/v1/ansible/inventory/script.py deleted file mode 100644 index b83cb9bcc7..0000000000 --- a/v1/ansible/inventory/script.py +++ /dev/null @@ -1,154 +0,0 @@ -# (c) 2012-2014, Michael DeHaan -# -# 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 . - -############################################# - -import os -import subprocess -import ansible.constants as C -from ansible.inventory.host import Host -from ansible.inventory.group import Group -from ansible.module_utils.basic import json_dict_bytes_to_unicode -from ansible import utils -from ansible import errors -import sys - - -class InventoryScript(object): - ''' Host inventory parser for ansible using external inventory scripts. ''' - - def __init__(self, filename=C.DEFAULT_HOST_LIST): - - # Support inventory scripts that are not prefixed with some - # path information but happen to be in the current working - # directory when '.' is not in PATH. - self.filename = os.path.abspath(filename) - cmd = [ self.filename, "--list" ] - try: - sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError, e: - raise errors.AnsibleError("problem running %s (%s)" % (' '.join(cmd), e)) - (stdout, stderr) = sp.communicate() - - if sp.returncode != 0: - raise errors.AnsibleError("Inventory script (%s) had an execution error: %s " % (filename,stderr)) - - self.data = stdout - # see comment about _meta below - self.host_vars_from_top = None - self.groups = self._parse(stderr) - - - def _parse(self, err): - - all_hosts = {} - - # not passing from_remote because data from CMDB is trusted - self.raw = utils.parse_json(self.data) - self.raw = json_dict_bytes_to_unicode(self.raw) - - all = Group('all') - groups = dict(all=all) - group = None - - - if 'failed' in self.raw: - sys.stderr.write(err + "\n") - raise errors.AnsibleError("failed to parse executable inventory script results: %s" % self.raw) - - for (group_name, data) in self.raw.items(): - - # in Ansible 1.3 and later, a "_meta" subelement may contain - # a variable "hostvars" which contains a hash for each host - # if this "hostvars" exists at all then do not call --host for each - # host. This is for efficiency and scripts should still return data - # if called with --host for backwards compat with 1.2 and earlier. - - if group_name == '_meta': - if 'hostvars' in data: - self.host_vars_from_top = data['hostvars'] - continue - - if group_name != all.name: - group = groups[group_name] = Group(group_name) - else: - group = all - host = None - - if not isinstance(data, dict): - data = {'hosts': data} - # is not those subkeys, then simplified syntax, host with vars - elif not any(k in data for k in ('hosts','vars','children')): - data = {'hosts': [group_name], 'vars': data} - - if 'hosts' in data: - if not isinstance(data['hosts'], list): - raise errors.AnsibleError("You defined a group \"%s\" with bad " - "data for the host list:\n %s" % (group_name, data)) - - for hostname in data['hosts']: - if not hostname in all_hosts: - all_hosts[hostname] = Host(hostname) - host = all_hosts[hostname] - group.add_host(host) - - if 'vars' in data: - if not isinstance(data['vars'], dict): - raise errors.AnsibleError("You defined a group \"%s\" with bad " - "data for variables:\n %s" % (group_name, data)) - - for k, v in data['vars'].iteritems(): - if group.name == all.name: - all.set_variable(k, v) - else: - group.set_variable(k, v) - - # Separate loop to ensure all groups are defined - for (group_name, data) in self.raw.items(): - if group_name == '_meta': - continue - if isinstance(data, dict) and 'children' in data: - for child_name in data['children']: - if child_name in groups: - groups[group_name].add_child_group(groups[child_name]) - - for group in groups.values(): - if group.depth == 0 and group.name != 'all': - all.add_child_group(group) - - return groups - - def get_host_variables(self, host): - """ Runs