Compare commits
70 Commits
v2.7.0rc2
...
release1.8
| Author | SHA1 | Date | |
|---|---|---|---|
| 64d1249598 | |||
| 2572038f72 | |||
| 37e44bbc14 | |||
| cdd1cf5901 | |||
| b187676a97 | |||
| b9f2ee78c1 | |||
| ab35504b51 | |||
| 920f49f70e | |||
| 55a2d370b0 | |||
| 8be7716064 | |||
| 0af7e212a8 | |||
| 1c56c6a3b7 | |||
| 8c5f7207f0 | |||
| daa3f2be55 | |||
| 31978121e1 | |||
| 2013d95e53 | |||
| b30cb192e8 | |||
| a1a69d6dc5 | |||
| d615f2a076 | |||
| ad6183a322 | |||
| b160026fea | |||
| 66e5515f88 | |||
| 6b315d57d6 | |||
| 71934a118c | |||
| 1771413c23 | |||
| ac0de5a9db | |||
| 8b8700d365 | |||
| 8150b14d8d | |||
| f32e9f0e54 | |||
| ec2a9cbd11 | |||
| d5ff09659f | |||
| 30ef52c805 | |||
| 1f34e464ad | |||
| b1f611fedb | |||
| a16d0a8dd6 | |||
| bf656e35e0 | |||
| eedd659444 | |||
| 90ac8d621c | |||
| 49d47a8241 | |||
| ad823766d2 | |||
| 4fa5db9a03 | |||
| 25801f939b | |||
| 573dbe1775 | |||
| 53a94a1fde | |||
| f6e7f318a2 | |||
| fc638fd371 | |||
| 3a68c09888 | |||
| dddfc0b9c5 | |||
| 04c8ecce56 | |||
| 6f568bd649 | |||
| 1545c73d27 | |||
| bff88271ee | |||
| b9f1503870 | |||
| ecdeed4342 | |||
| e81a6da49b | |||
| 0a52913dfa | |||
| 1393272632 | |||
| 4cd87ec93e | |||
| 8dd3d84250 | |||
| e728ff97d9 | |||
| 2f1e78ffee | |||
| 38e2757c85 | |||
| d68add651e | |||
| ad675235e2 | |||
| 6598c83b02 | |||
| 5b3c82f4ba | |||
| e5344a7167 | |||
| 7fcfc7450d | |||
| dece0c3f08 | |||
| 672a2cc037 |
22
CHANGELOG.md
22
CHANGELOG.md
@ -1,7 +1,27 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
## 1.8 "You Really Got Me" - Active Development
|
||||
## 1.8.3 "You Really Got Me" - Feb 17, 2015
|
||||
|
||||
* Fixing a security bug related to the default permissions set on a tempoary file created when using "ansible-vault view <filename>".
|
||||
* Many bug fixes, for both core code and core modules.
|
||||
|
||||
## 1.8.2 "You Really Got Me" - Dec 04, 2014
|
||||
|
||||
* Various bug fixes for packaging issues related to modules.
|
||||
* Various bug fixes for lookup plugins.
|
||||
* Various bug fixes for some modules (continued cleanup of postgresql issues, etc.).
|
||||
|
||||
* Add a clone parameter to git module that allows you to get information about a remote repo even if it doesn't exist locally.
|
||||
|
||||
## 1.8.1 "You Really Got Me" - Nov 26, 2014
|
||||
|
||||
* Various bug fixes in postgresql and mysql modules.
|
||||
* Fixed a bug related to lookup plugins used within roles not finding files based on the relative paths to the roles files/ directory.
|
||||
* Fixed a bug related to vars specified in plays being templated too early, resulting in incorrect variable interpolation.
|
||||
* Fixed a bug related to git submodules in bare repos.
|
||||
|
||||
## 1.8 "You Really Got Me" - Nov 25, 2014
|
||||
|
||||
Major changes:
|
||||
|
||||
|
||||
@ -7,3 +7,5 @@ recursive-include docs *
|
||||
include Makefile
|
||||
include VERSION
|
||||
include MANIFEST.in
|
||||
prune lib/ansible/modules/core/.git
|
||||
prune lib/ansible/modules/extras/.git
|
||||
|
||||
@ -4,11 +4,15 @@ Ansible Releases at a Glance
|
||||
Active Development
|
||||
++++++++++++++++++
|
||||
|
||||
1.8 "You Really Got Me" ---- FALL 2014
|
||||
1.9 "Dancing In the Streets" WINTER 2015
|
||||
|
||||
Released
|
||||
++++++++
|
||||
|
||||
1.8.3 "You Really Got Me" ---- 02-17-2015
|
||||
1.8.2 "You Really Got Me" ---- 12-04-2014
|
||||
1.8.1 "You Really Got Me" ---- 11-26-2014
|
||||
1.8 "You Really Got Me" ---- 11-25-2014
|
||||
1.7.2 "Summer Nights" -------- 09-24-2014
|
||||
1.7.1 "Summer Nights" -------- 08-14-2014
|
||||
1.7 "Summer Nights" -------- 08-06-2014
|
||||
|
||||
@ -25,6 +25,10 @@ import re
|
||||
import optparse
|
||||
import datetime
|
||||
import subprocess
|
||||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
|
||||
from ansible import utils
|
||||
from ansible.utils import module_docs
|
||||
import ansible.constants as C
|
||||
@ -165,7 +169,11 @@ def get_snippet_text(doc):
|
||||
return "\n".join(text)
|
||||
|
||||
def get_module_list_text(module_list):
|
||||
columns = max(60, int(os.popen('stty size', 'r').read().split()[1]))
|
||||
tty_size = 0
|
||||
if os.isatty(0):
|
||||
tty_size = struct.unpack('HHHH',
|
||||
fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))[1]
|
||||
columns = max(60, tty_size)
|
||||
displace = max(len(x) for x in module_list)
|
||||
linelimit = columns - displace - 5
|
||||
text = []
|
||||
|
||||
@ -380,7 +380,7 @@ def scm_archive_role(scm, role_url, role_version, role_name):
|
||||
print " in directory %s" % tempdir
|
||||
return False
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
shutil.rmtree(tempdir, ignore_errors=True)
|
||||
|
||||
return temp_file.name
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ import ansible.utils.template
|
||||
from ansible import errors
|
||||
from ansible import callbacks
|
||||
from ansible import utils
|
||||
from ansible.utils import to_unicode
|
||||
from ansible.color import ANSIBLE_COLOR, stringc
|
||||
from ansible.callbacks import display
|
||||
|
||||
@ -136,10 +137,11 @@ def main(args):
|
||||
|
||||
extra_vars = {}
|
||||
for extra_vars_opt in options.extra_vars:
|
||||
if extra_vars_opt.startswith("@"):
|
||||
extra_vars_opt = to_unicode(extra_vars_opt)
|
||||
if extra_vars_opt.startswith(u"@"):
|
||||
# Argument is a YAML file (JSON is a subset of YAML)
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml_from_file(extra_vars_opt[1:], vault_password=vault_pass))
|
||||
elif extra_vars_opt and extra_vars_opt[0] in '[{':
|
||||
elif extra_vars_opt and extra_vars_opt[0] in u'[{':
|
||||
# Arguments as YAML
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml(extra_vars_opt))
|
||||
else:
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
# some basic default values...
|
||||
|
||||
hostfile = /etc/ansible/hosts
|
||||
# library_path = /usr/share/my_modules/
|
||||
#library = /usr/share/my_modules/
|
||||
remote_tmp = $HOME/.ansible/tmp
|
||||
pattern = *
|
||||
forks = 5
|
||||
|
||||
@ -14,5 +14,5 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
__version__ = '1.8'
|
||||
__version__ = '1.8.3'
|
||||
__author__ = 'Michael DeHaan'
|
||||
|
||||
5
lib/ansible/cache/jsonfile.py
vendored
5
lib/ansible/cache/jsonfile.py
vendored
@ -20,6 +20,11 @@ import time
|
||||
import json
|
||||
import errno
|
||||
|
||||
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
|
||||
|
||||
9
lib/ansible/cache/redis.py
vendored
9
lib/ansible/cache/redis.py
vendored
@ -20,9 +20,14 @@ import collections
|
||||
# FIXME: can we store these as something else before we ship it?
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
|
||||
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:
|
||||
@ -65,7 +70,7 @@ class CacheModule(BaseCacheModule):
|
||||
return json.loads(value)
|
||||
|
||||
def set(self, key, value):
|
||||
value2 = json.dumps(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:
|
||||
|
||||
@ -27,6 +27,7 @@ import fcntl
|
||||
import constants
|
||||
import locale
|
||||
from ansible.color import stringc
|
||||
from ansible.module_utils import basic
|
||||
|
||||
import logging
|
||||
if constants.DEFAULT_LOG_PATH != '':
|
||||
@ -456,6 +457,12 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
|
||||
item = None
|
||||
if type(results) == dict:
|
||||
item = results.get('item', None)
|
||||
if isinstance(item, unicode):
|
||||
item = utils.to_bytes(item)
|
||||
results = basic.json_dict_unicode_to_bytes(results)
|
||||
else:
|
||||
results = utils.to_bytes(results)
|
||||
host = utils.to_bytes(host)
|
||||
if item:
|
||||
msg = "fatal: [%s] => (item=%s) => %s" % (host, item, results)
|
||||
else:
|
||||
@ -622,7 +629,13 @@ class PlaybookCallbacks(object):
|
||||
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:
|
||||
msg = ('Perform task: %s (y/n/c): ' % name).encode(sys.stdout.encoding)
|
||||
if isinstance(name, str):
|
||||
name = utils.to_unicode(name)
|
||||
msg = u'Perform task: %s (y/n/c): ' % name
|
||||
if sys.stdout.encoding:
|
||||
msg = msg.encode(sys.stdout.encoding, errors='replace')
|
||||
else:
|
||||
msg = msg.encode('utf-8')
|
||||
resp = raw_input(msg)
|
||||
if resp.lower() in ['y','yes']:
|
||||
self.skip_task = False
|
||||
|
||||
@ -22,7 +22,7 @@ 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_unicode_to_bytes
|
||||
from ansible.module_utils.basic import json_dict_bytes_to_unicode
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
import sys
|
||||
@ -55,7 +55,7 @@ class InventoryScript(object):
|
||||
|
||||
# not passing from_remote because data from CMDB is trusted
|
||||
self.raw = utils.parse_json(self.data)
|
||||
self.raw = json_dict_unicode_to_bytes(self.raw)
|
||||
self.raw = json_dict_bytes_to_unicode(self.raw)
|
||||
|
||||
all = Group('all')
|
||||
groups = dict(all=all)
|
||||
@ -144,7 +144,7 @@ class InventoryScript(object):
|
||||
if out.strip() == '':
|
||||
return dict()
|
||||
try:
|
||||
return json_dict_unicode_to_bytes(utils.parse_json(out))
|
||||
return json_dict_bytes_to_unicode(utils.parse_json(out))
|
||||
except ValueError:
|
||||
raise errors.AnsibleError("could not parse post variable response: %s, %s" % (cmd, out))
|
||||
|
||||
|
||||
@ -171,6 +171,7 @@ FILE_COMMON_ARGUMENTS=dict(
|
||||
directory_mode = dict(), # used by copy
|
||||
)
|
||||
|
||||
PASSWD_ARG_RE = re.compile(r'^[-]{0,2}pass[-]?(word|wd)?')
|
||||
|
||||
def get_platform():
|
||||
''' what's the platform? example: Linux is a platform. '''
|
||||
@ -251,6 +252,83 @@ def json_dict_unicode_to_bytes(d):
|
||||
else:
|
||||
return d
|
||||
|
||||
def json_dict_bytes_to_unicode(d):
|
||||
''' Recursively convert dict keys and values to byte str
|
||||
|
||||
Specialized for json return because this only handles, lists, tuples,
|
||||
and dict container types (the containers that the json module returns)
|
||||
'''
|
||||
|
||||
if isinstance(d, str):
|
||||
return unicode(d, 'utf-8')
|
||||
elif isinstance(d, dict):
|
||||
return dict(map(json_dict_bytes_to_unicode, d.iteritems()))
|
||||
elif isinstance(d, list):
|
||||
return list(map(json_dict_bytes_to_unicode, d))
|
||||
elif isinstance(d, tuple):
|
||||
return tuple(map(json_dict_bytes_to_unicode, d))
|
||||
else:
|
||||
return d
|
||||
|
||||
def heuristic_log_sanitize(data):
|
||||
''' Remove strings that look like passwords from log messages '''
|
||||
# Currently filters:
|
||||
# user:pass@foo/whatever and http://username:pass@wherever/foo
|
||||
# This code has false positives and consumes parts of logs that are
|
||||
# not passwds
|
||||
|
||||
# begin: start of a passwd containing string
|
||||
# end: end of a passwd containing string
|
||||
# sep: char between user and passwd
|
||||
# prev_begin: where in the overall string to start a search for
|
||||
# a passwd
|
||||
# sep_search_end: where in the string to end a search for the sep
|
||||
output = []
|
||||
begin = len(data)
|
||||
prev_begin = begin
|
||||
sep = 1
|
||||
while sep:
|
||||
# Find the potential end of a passwd
|
||||
try:
|
||||
end = data.rindex('@', 0, begin)
|
||||
except ValueError:
|
||||
# No passwd in the rest of the data
|
||||
output.insert(0, data[0:begin])
|
||||
break
|
||||
|
||||
# Search for the beginning of a passwd
|
||||
sep = None
|
||||
sep_search_end = end
|
||||
while not sep:
|
||||
# URL-style username+password
|
||||
try:
|
||||
begin = data.rindex('://', 0, sep_search_end)
|
||||
except ValueError:
|
||||
# No url style in the data, check for ssh style in the
|
||||
# rest of the string
|
||||
begin = 0
|
||||
# Search for separator
|
||||
try:
|
||||
sep = data.index(':', begin + 3, end)
|
||||
except ValueError:
|
||||
# No separator; choices:
|
||||
if begin == 0:
|
||||
# Searched the whole string so there's no password
|
||||
# here. Return the remaining data
|
||||
output.insert(0, data[0:begin])
|
||||
break
|
||||
# Search for a different beginning of the password field.
|
||||
sep_search_end = begin
|
||||
continue
|
||||
if sep:
|
||||
# Password was found; remove it.
|
||||
output.insert(0, data[end:prev_begin])
|
||||
output.insert(0, '********')
|
||||
output.insert(0, data[begin:sep + 1])
|
||||
prev_begin = begin
|
||||
|
||||
return ''.join(output)
|
||||
|
||||
|
||||
class AnsibleModule(object):
|
||||
|
||||
@ -578,14 +656,25 @@ class AnsibleModule(object):
|
||||
# FIXME: comparison against string above will cause this to be executed
|
||||
# every time
|
||||
try:
|
||||
if 'lchmod' in dir(os):
|
||||
if hasattr(os, 'lchmod'):
|
||||
os.lchmod(path, mode)
|
||||
else:
|
||||
os.chmod(path, mode)
|
||||
if not os.path.islink(path):
|
||||
os.chmod(path, mode)
|
||||
else:
|
||||
# Attempt to set the perms of the symlink but be
|
||||
# careful not to change the perms of the underlying
|
||||
# file while trying
|
||||
underlying_stat = os.stat(path)
|
||||
os.chmod(path, mode)
|
||||
new_underlying_stat = os.stat(path)
|
||||
if underlying_stat.st_mode != new_underlying_stat.st_mode:
|
||||
os.chmod(path, stat.S_IMODE(underlying_stat.st_mode))
|
||||
q_stat = os.stat(path)
|
||||
except OSError, e:
|
||||
if os.path.islink(path) and e.errno == errno.EPERM: # Can't set mode on symbolic links
|
||||
pass
|
||||
elif e.errno == errno.ENOENT: # Can't set mode on broken symbolic links
|
||||
elif e.errno in (errno.ENOENT, errno.ELOOP): # Can't set mode on broken symbolic links
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
@ -1001,65 +1090,6 @@ class AnsibleModule(object):
|
||||
params2.update(params)
|
||||
return (params2, args)
|
||||
|
||||
def _heuristic_log_sanitize(self, data):
|
||||
''' Remove strings that look like passwords from log messages '''
|
||||
# Currently filters:
|
||||
# user:pass@foo/whatever and http://username:pass@wherever/foo
|
||||
# This code has false positives and consumes parts of logs that are
|
||||
# not passwds
|
||||
|
||||
# begin: start of a passwd containing string
|
||||
# end: end of a passwd containing string
|
||||
# sep: char between user and passwd
|
||||
# prev_begin: where in the overall string to start a search for
|
||||
# a passwd
|
||||
# sep_search_end: where in the string to end a search for the sep
|
||||
output = []
|
||||
begin = len(data)
|
||||
prev_begin = begin
|
||||
sep = 1
|
||||
while sep:
|
||||
# Find the potential end of a passwd
|
||||
try:
|
||||
end = data.rindex('@', 0, begin)
|
||||
except ValueError:
|
||||
# No passwd in the rest of the data
|
||||
output.insert(0, data[0:begin])
|
||||
break
|
||||
|
||||
# Search for the beginning of a passwd
|
||||
sep = None
|
||||
sep_search_end = end
|
||||
while not sep:
|
||||
# URL-style username+password
|
||||
try:
|
||||
begin = data.rindex('://', 0, sep_search_end)
|
||||
except ValueError:
|
||||
# No url style in the data, check for ssh style in the
|
||||
# rest of the string
|
||||
begin = 0
|
||||
# Search for separator
|
||||
try:
|
||||
sep = data.index(':', begin + 3, end)
|
||||
except ValueError:
|
||||
# No separator; choices:
|
||||
if begin == 0:
|
||||
# Searched the whole string so there's no password
|
||||
# here. Return the remaining data
|
||||
output.insert(0, data[0:begin])
|
||||
break
|
||||
# Search for a different beginning of the password field.
|
||||
sep_search_end = begin
|
||||
continue
|
||||
if sep:
|
||||
# Password was found; remove it.
|
||||
output.insert(0, data[end:prev_begin])
|
||||
output.insert(0, '********')
|
||||
output.insert(0, data[begin:sep + 1])
|
||||
prev_begin = begin
|
||||
|
||||
return ''.join(output)
|
||||
|
||||
def _log_invocation(self):
|
||||
''' log that ansible ran the module '''
|
||||
# TODO: generalize a separate log function and make log_invocation use it
|
||||
@ -1082,7 +1112,7 @@ class AnsibleModule(object):
|
||||
param_val = str(param_val)
|
||||
elif isinstance(param_val, unicode):
|
||||
param_val = param_val.encode('utf-8')
|
||||
log_args[param] = self._heuristic_log_sanitize(param_val)
|
||||
log_args[param] = heuristic_log_sanitize(param_val)
|
||||
|
||||
module = 'ansible-%s' % os.path.basename(__file__)
|
||||
msg = []
|
||||
@ -1104,12 +1134,11 @@ class AnsibleModule(object):
|
||||
msg = msg.encode('utf-8')
|
||||
|
||||
if (has_journal):
|
||||
journal_args = ["MESSAGE=%s %s" % (module, msg)]
|
||||
journal_args.append("MODULE=%s" % os.path.basename(__file__))
|
||||
journal_args = [("MODULE", os.path.basename(__file__))]
|
||||
for arg in log_args:
|
||||
journal_args.append(arg.upper() + "=" + str(log_args[arg]))
|
||||
journal_args.append((arg.upper(), str(log_args[arg])))
|
||||
try:
|
||||
journal.sendv(*journal_args)
|
||||
journal.send("%s %s" % (module, msg), **dict(journal_args))
|
||||
except IOError, e:
|
||||
# fall back to syslog since logging to journal failed
|
||||
syslog.openlog(str(module), 0, syslog.LOG_USER)
|
||||
@ -1427,27 +1456,27 @@ class AnsibleModule(object):
|
||||
# create a printable version of the command for use
|
||||
# in reporting later, which strips out things like
|
||||
# passwords from the args list
|
||||
if isinstance(args, list):
|
||||
clean_args = " ".join(pipes.quote(arg) for arg in args)
|
||||
if isinstance(args, basestring):
|
||||
to_clean_args = shlex.split(args.encode('utf-8'))
|
||||
else:
|
||||
clean_args = args
|
||||
to_clean_args = args
|
||||
|
||||
# all clean strings should return two match groups,
|
||||
# where the first is the CLI argument and the second
|
||||
# is the password/key/phrase that will be hidden
|
||||
clean_re_strings = [
|
||||
# this removes things like --password, --pass, --pass-wd, etc.
|
||||
# optionally followed by an '=' or a space. The password can
|
||||
# be quoted or not too, though it does not care about quotes
|
||||
# that are not balanced
|
||||
# source: http://blog.stevenlevithan.com/archives/match-quoted-string
|
||||
r'([-]{0,2}pass[-]?(?:word|wd)?[=\s]?)((?:["\'])?(?:[^\s])*(?:\1)?)',
|
||||
r'^(?P<before>.*:)(?P<password>.*)(?P<after>\@.*)$',
|
||||
# TODO: add more regex checks here
|
||||
]
|
||||
for re_str in clean_re_strings:
|
||||
r = re.compile(re_str)
|
||||
clean_args = r.sub(r'\1********', clean_args)
|
||||
clean_args = []
|
||||
is_passwd = False
|
||||
for arg in to_clean_args:
|
||||
if is_passwd:
|
||||
is_passwd = False
|
||||
clean_args.append('********')
|
||||
continue
|
||||
if PASSWD_ARG_RE.match(arg):
|
||||
sep_idx = arg.find('=')
|
||||
if sep_idx > -1:
|
||||
clean_args.append('%s=********' % arg[:sep_idx])
|
||||
continue
|
||||
else:
|
||||
is_passwd = True
|
||||
clean_args.append(heuristic_log_sanitize(arg))
|
||||
clean_args = ' '.join(pipes.quote(arg) for arg in clean_args)
|
||||
|
||||
if data:
|
||||
st_in = subprocess.PIPE
|
||||
@ -1532,7 +1561,7 @@ class AnsibleModule(object):
|
||||
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
|
||||
|
||||
if rc != 0 and check_rc:
|
||||
msg = stderr.rstrip()
|
||||
msg = heuristic_log_sanitize(stderr.rstrip())
|
||||
self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)
|
||||
|
||||
# reset the pwd
|
||||
|
||||
@ -2381,9 +2381,13 @@ class SunOSVirtual(Virtual):
|
||||
def get_file_content(path, default=None):
|
||||
data = default
|
||||
if os.path.exists(path) and os.access(path, os.R_OK):
|
||||
data = open(path).read().strip()
|
||||
if len(data) == 0:
|
||||
data = default
|
||||
try:
|
||||
datafile = open(path)
|
||||
data = datafile.read().strip()
|
||||
if len(data) == 0:
|
||||
data = default
|
||||
finally:
|
||||
datafile.close()
|
||||
return data
|
||||
|
||||
def ansible_facts(module):
|
||||
|
||||
Submodule lib/ansible/modules/core updated: 10ebcccedb...6bed6e6bbb
Submodule lib/ansible/modules/extras updated: 317654dba5...7b4f8c3ef9
@ -88,13 +88,6 @@ class Play(object):
|
||||
if self.playbook.inventory.src() is not None:
|
||||
load_vars['inventory_file'] = self.playbook.inventory.src()
|
||||
|
||||
# template the play vars with themselves and the extra vars
|
||||
# from the playbook, to make sure they're correct
|
||||
all_vars = utils.combine_vars(self.vars, self.playbook.extra_vars)
|
||||
all_vars = utils.combine_vars(all_vars, load_vars)
|
||||
self.vars = template(basedir, self.vars, all_vars)
|
||||
self.vars = utils.combine_vars(self.vars, load_vars)
|
||||
|
||||
# We first load the vars files from the datastructure
|
||||
# so we have the default variables to pass into the roles
|
||||
self.vars_files = ds.get('vars_files', [])
|
||||
@ -115,10 +108,16 @@ class Play(object):
|
||||
self._update_vars_files_for_host(None)
|
||||
|
||||
# template everything to be efficient, but do not pre-mature template
|
||||
# tasks/handlers as they may have inventory scope overrides
|
||||
# tasks/handlers as they may have inventory scope overrides. We also
|
||||
# create a set of temporary variables for templating, so we don't
|
||||
# trample on the existing vars structures
|
||||
_tasks = ds.pop('tasks', [])
|
||||
_handlers = ds.pop('handlers', [])
|
||||
ds = template(basedir, ds, self.vars)
|
||||
|
||||
temp_vars = utils.merge_hash(self.vars, self.vars_file_vars)
|
||||
temp_vars = utils.merge_hash(temp_vars, self.playbook.extra_vars)
|
||||
|
||||
ds = template(basedir, ds, temp_vars)
|
||||
ds['tasks'] = _tasks
|
||||
ds['handlers'] = _handlers
|
||||
|
||||
@ -626,8 +625,14 @@ class Play(object):
|
||||
dirname = self.basedir
|
||||
if original_file:
|
||||
dirname = os.path.dirname(original_file)
|
||||
include_file = template(dirname, tokens[0], mv)
|
||||
|
||||
# temp vars are used here to avoid trampling on the existing vars structures
|
||||
temp_vars = utils.merge_hash(self.vars, self.vars_file_vars)
|
||||
temp_vars = utils.merge_hash(temp_vars, mv)
|
||||
temp_vars = utils.merge_hash(temp_vars, self.playbook.extra_vars)
|
||||
include_file = template(dirname, tokens[0], temp_vars)
|
||||
include_filename = utils.path_dwim(dirname, include_file)
|
||||
|
||||
data = utils.parse_yaml_from_file(include_filename, vault_password=self.vault_password)
|
||||
if 'role_name' in x and data is not None:
|
||||
for y in data:
|
||||
|
||||
@ -667,8 +667,22 @@ class Runner(object):
|
||||
def _executor_internal(self, host, new_stdin):
|
||||
''' executes any module one or more times '''
|
||||
|
||||
# We build the proper injected dictionary for all future
|
||||
# templating operations in this run
|
||||
inject = self.get_inject_vars(host)
|
||||
hostvars = HostVars(inject['combined_cache'], self.inventory, vault_password=self.vault_pass)
|
||||
|
||||
# Then we selectively merge some variable dictionaries down to a
|
||||
# single dictionary, used to template the HostVars for this host
|
||||
temp_vars = self.inventory.get_variables(host, vault_password=self.vault_pass)
|
||||
temp_vars = utils.merge_hash(temp_vars, inject['combined_cache'])
|
||||
temp_vars = utils.merge_hash(temp_vars, self.play_vars)
|
||||
temp_vars = utils.merge_hash(temp_vars, self.play_file_vars)
|
||||
temp_vars = utils.merge_hash(temp_vars, self.extra_vars)
|
||||
|
||||
hostvars = HostVars(temp_vars, self.inventory, vault_password=self.vault_pass)
|
||||
|
||||
# and we save the HostVars in the injected dictionary so they
|
||||
# may be referenced from playbooks/templates
|
||||
inject['hostvars'] = hostvars
|
||||
|
||||
host_connection = inject.get('ansible_connection', self.transport)
|
||||
@ -719,6 +733,10 @@ class Runner(object):
|
||||
result = utils.jsonify(dict(changed=False, skipped=True))
|
||||
self.callbacks.on_skipped(host, None)
|
||||
return ReturnData(host=host, result=result)
|
||||
except errors.AnsibleError, e:
|
||||
raise
|
||||
except Exception, e:
|
||||
raise errors.AnsibleError("Unexpected error while executing task: %s" % str(e))
|
||||
|
||||
# strip out any jinja2 template syntax within
|
||||
# the data returned by the lookup plugin
|
||||
@ -912,6 +930,8 @@ class Runner(object):
|
||||
actual_private_key_file = delegate['private_key_file']
|
||||
self.sudo_pass = delegate['sudo_pass']
|
||||
inject = delegate['inject']
|
||||
# set resolved delegate_to into inject so modules can call _remote_checksum
|
||||
inject['delegate_to'] = self.delegate_to
|
||||
|
||||
# user/pass may still contain variables at this stage
|
||||
actual_user = template.template(self.basedir, actual_user, inject)
|
||||
@ -1012,7 +1032,7 @@ class Runner(object):
|
||||
|
||||
cond = template.template(self.basedir, until, inject, expand_lists=False)
|
||||
if not utils.check_conditional(cond, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars):
|
||||
retries = self.module_vars.get('retries')
|
||||
retries = template.template(self.basedir, self.module_vars.get('retries'), inject, expand_lists=False)
|
||||
delay = self.module_vars.get('delay')
|
||||
for x in range(1, int(retries) + 1):
|
||||
# template the delay, cast to float and sleep
|
||||
@ -1182,8 +1202,16 @@ class Runner(object):
|
||||
''' takes a remote path and performs tilde expansion on the remote host '''
|
||||
if not path.startswith('~'):
|
||||
return path
|
||||
|
||||
split_path = path.split(os.path.sep, 1)
|
||||
cmd = conn.shell.expand_user(split_path[0])
|
||||
expand_path = split_path[0]
|
||||
if expand_path == '~':
|
||||
if self.sudo and self.sudo_user:
|
||||
expand_path = '~%s' % self.sudo_user
|
||||
elif self.su and self.su_user:
|
||||
expand_path = '~%s' % self.su_user
|
||||
|
||||
cmd = conn.shell.expand_user(expand_path)
|
||||
data = self._low_level_exec_command(conn, cmd, tmp, sudoable=False, su=False)
|
||||
initial_fragment = utils.last_non_blank_line(data['stdout'])
|
||||
|
||||
@ -1193,7 +1221,7 @@ class Runner(object):
|
||||
return path
|
||||
|
||||
if len(split_path) > 1:
|
||||
return os.path.join(initial_fragment, *split_path[1:])
|
||||
return conn.shell.join_path(initial_fragment, *split_path[1:])
|
||||
else:
|
||||
return initial_fragment
|
||||
|
||||
@ -1201,7 +1229,28 @@ class Runner(object):
|
||||
|
||||
def _remote_checksum(self, conn, tmp, path, inject):
|
||||
''' takes a remote checksum and returns 1 if no file '''
|
||||
python_interp = inject['hostvars'][inject['inventory_hostname']].get('ansible_python_interpreter', 'python')
|
||||
|
||||
# Lookup the python interp from the host or delegate
|
||||
|
||||
# host == inven_host when there is no delegate
|
||||
host = inject['inventory_hostname']
|
||||
if 'delegate_to' in inject:
|
||||
delegate = inject['delegate_to']
|
||||
if delegate:
|
||||
# host == None when the delegate is not in inventory
|
||||
host = None
|
||||
# delegate set, check whether the delegate has inventory vars
|
||||
delegate = template.template(self.basedir, delegate, inject)
|
||||
if delegate in inject['hostvars']:
|
||||
# host == delegate if we need to lookup the
|
||||
# python_interpreter from the delegate's inventory vars
|
||||
host = delegate
|
||||
|
||||
if host:
|
||||
python_interp = inject['hostvars'][host].get('ansible_python_interpreter', 'python')
|
||||
else:
|
||||
python_interp = 'python'
|
||||
|
||||
cmd = conn.shell.checksum(path, python_interp)
|
||||
data = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
|
||||
data2 = utils.last_non_blank_line(data['stdout'])
|
||||
@ -1414,9 +1463,15 @@ class Runner(object):
|
||||
# Expose the current hostgroup to the bypassing plugins
|
||||
self.host_set = hosts
|
||||
# We aren't iterating over all the hosts in this
|
||||
# group. So, just pick the first host in our group to
|
||||
# group. So, just choose the "delegate_to" host if that is defined and is
|
||||
# one of the targeted hosts, otherwise pick the first host in our group to
|
||||
# construct the conn object with.
|
||||
result_data = self._executor(hosts[0], None).result
|
||||
if self.delegate_to is not None and self.delegate_to in hosts:
|
||||
host = self.delegate_to
|
||||
else:
|
||||
host = hosts[0]
|
||||
|
||||
result_data = self._executor(host, None).result
|
||||
# Create a ResultData item for each host in this group
|
||||
# using the returned result. If we didn't do this we would
|
||||
# get false reports of dark hosts.
|
||||
|
||||
@ -192,8 +192,12 @@ class ActionModule(object):
|
||||
dest_file = conn.shell.join_path(dest, source_rel)
|
||||
remote_checksum = self.runner._remote_checksum(conn, tmp_path, dest_file, inject)
|
||||
|
||||
if remote_checksum == '4':
|
||||
result = dict(msg="python isn't present on the system. Unable to compute checksum", failed=True)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
if remote_checksum != '1' and not force:
|
||||
# remote_file does not exist so continue to next iteration.
|
||||
# remote_file exists so continue to next iteration.
|
||||
continue
|
||||
|
||||
if local_checksum != remote_checksum:
|
||||
|
||||
@ -129,7 +129,7 @@ class ActionModule(object):
|
||||
elif remote_checksum == '3':
|
||||
result = dict(msg="remote file is a directory, fetch cannot work on directories", file=source, changed=False)
|
||||
elif remote_checksum == '4':
|
||||
result = dict(msg="python isn't present on the remote system. Unable to fetch file", file=source, changed=False)
|
||||
result = dict(msg="python isn't present on the system. Unable to compute checksum", file=source, changed=False)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
# calculate checksum for the local file
|
||||
|
||||
@ -92,6 +92,13 @@ class ActionModule(object):
|
||||
local_checksum = utils.checksum_s(resultant)
|
||||
remote_checksum = self.runner._remote_checksum(conn, tmp, dest, inject)
|
||||
|
||||
if remote_checksum in ('0', '2', '3', '4'):
|
||||
# Note: 1 means the file is not present which is fine; template
|
||||
# will create it
|
||||
result = dict(failed=True, msg="failed to checksum remote file."
|
||||
" Checksum error code: %s" % remote_checksum)
|
||||
return ReturnData(conn=conn, comm_ok=True, result=result)
|
||||
|
||||
if local_checksum != remote_checksum:
|
||||
|
||||
# template is different from the remote value
|
||||
@ -138,6 +145,7 @@ class ActionModule(object):
|
||||
# the module to follow links. When doing that, we have to set
|
||||
# original_basename to the template just in case the dest is
|
||||
# a directory.
|
||||
module_args = ''
|
||||
new_module_args = dict(
|
||||
src=None,
|
||||
original_basename=os.path.basename(source),
|
||||
@ -147,6 +155,6 @@ class ActionModule(object):
|
||||
# rely on the file module to report its changed status
|
||||
if self.runner.noop_on_check(inject):
|
||||
new_module_args['CHECKMODE'] = True
|
||||
module_args = utils.merge_module_args(module_args, new_module_args)
|
||||
return self.runner._execute_module(conn, tmp, 'file', module_args, inject=inject, complex_args=complex_args)
|
||||
options.update(new_module_args)
|
||||
return self.runner._execute_module(conn, tmp, 'file', module_args, inject=inject, complex_args=options)
|
||||
|
||||
|
||||
@ -59,9 +59,10 @@ class ActionModule(object):
|
||||
# do not run the command if the line contains creates=filename
|
||||
# and the filename already exists. This allows idempotence
|
||||
# of command executions.
|
||||
module_args_tmp = "path=%s" % creates
|
||||
module_args_tmp = ""
|
||||
complex_args_tmp = dict(path=creates, get_md5=False, get_checksum=False)
|
||||
module_return = self.runner._execute_module(conn, tmp, 'stat', module_args_tmp, inject=inject,
|
||||
complex_args=complex_args, persist_files=True)
|
||||
complex_args=complex_args_tmp, persist_files=True)
|
||||
stat = module_return.result.get('stat', None)
|
||||
if stat and stat.get('exists', False):
|
||||
return ReturnData(
|
||||
@ -83,6 +84,9 @@ class ActionModule(object):
|
||||
source = utils.path_dwim(self.runner.basedir, source)
|
||||
|
||||
remote_checksum = self.runner._remote_checksum(conn, tmp, dest, inject)
|
||||
if remote_checksum == '4':
|
||||
result = dict(failed=True, msg="python isn't present on the system. Unable to compute checksum")
|
||||
return ReturnData(conn=conn, result=result)
|
||||
if remote_checksum != '3':
|
||||
result = dict(failed=True, msg="dest '%s' must be an existing dir" % dest)
|
||||
return ReturnData(conn=conn, result=result)
|
||||
|
||||
@ -230,6 +230,7 @@ class Connection(object):
|
||||
host_fh.close()
|
||||
|
||||
for line in data.split("\n"):
|
||||
line = line.strip()
|
||||
if line is None or " " not in line:
|
||||
continue
|
||||
tokens = line.split()
|
||||
@ -334,7 +335,7 @@ class Connection(object):
|
||||
"sudo", "Sorry, try again.")
|
||||
if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)):
|
||||
raise errors.AnsibleError('Incorrect sudo password')
|
||||
elif sudo_errput.endswith(prompt):
|
||||
elif prompt and sudo_errput.endswith(prompt):
|
||||
stdin.write(self.runner.sudo_pass + '\n')
|
||||
|
||||
if p.stdout in rfd:
|
||||
|
||||
@ -193,7 +193,7 @@ class Connection(object):
|
||||
def fetch_file(self, in_path, out_path):
|
||||
out_path = out_path.replace('\\', '/')
|
||||
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
|
||||
buffer_size = 2**20 # 1MB chunks
|
||||
buffer_size = 2**19 # 0.5MB chunks
|
||||
if not os.path.exists(os.path.dirname(out_path)):
|
||||
os.makedirs(os.path.dirname(out_path))
|
||||
out_file = None
|
||||
|
||||
@ -24,17 +24,22 @@ import pipes
|
||||
import glob
|
||||
import re
|
||||
import collections
|
||||
from functools import partial
|
||||
import operator as py_operator
|
||||
from ansible import errors
|
||||
from ansible.utils import md5s, checksum_s
|
||||
from distutils.version import LooseVersion, StrictVersion
|
||||
from random import SystemRandom, shuffle
|
||||
from jinja2.filters import environmentfilter
|
||||
from distutils.version import LooseVersion, StrictVersion
|
||||
|
||||
from ansible import errors
|
||||
from ansible.utils import md5s, checksum_s
|
||||
from ansible.utils.unicode import unicode_wrap, to_unicode
|
||||
|
||||
|
||||
def to_nice_yaml(*a, **kw):
|
||||
'''Make verbose, human readable yaml'''
|
||||
return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw)
|
||||
transformed = yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw)
|
||||
return to_unicode(transformed)
|
||||
|
||||
def to_json(a, *args, **kw):
|
||||
''' Convert the value to JSON '''
|
||||
@ -249,8 +254,8 @@ class FilterModule(object):
|
||||
def filters(self):
|
||||
return {
|
||||
# base 64
|
||||
'b64decode': base64.b64decode,
|
||||
'b64encode': base64.b64encode,
|
||||
'b64decode': partial(unicode_wrap, base64.b64decode),
|
||||
'b64encode': partial(unicode_wrap, base64.b64encode),
|
||||
|
||||
# json
|
||||
'to_json': to_json,
|
||||
@ -263,11 +268,11 @@ class FilterModule(object):
|
||||
'from_yaml': yaml.safe_load,
|
||||
|
||||
# path
|
||||
'basename': os.path.basename,
|
||||
'dirname': os.path.dirname,
|
||||
'expanduser': os.path.expanduser,
|
||||
'realpath': os.path.realpath,
|
||||
'relpath': os.path.relpath,
|
||||
'basename': partial(unicode_wrap, os.path.basename),
|
||||
'dirname': partial(unicode_wrap, os.path.dirname),
|
||||
'expanduser': partial(unicode_wrap, os.path.expanduser),
|
||||
'realpath': partial(unicode_wrap, os.path.realpath),
|
||||
'relpath': partial(unicode_wrap, os.path.relpath),
|
||||
|
||||
# failure testing
|
||||
'failed' : failed,
|
||||
|
||||
@ -50,7 +50,7 @@ class LookupModule(object):
|
||||
|
||||
if isinstance(term, basestring):
|
||||
# convert a variable to a list
|
||||
term2 = utils.listify_lookup_plugin_terms(term, self.basedir, inject, fail_on_undefined=False)
|
||||
term2 = utils.listify_lookup_plugin_terms(term, self.basedir, inject)
|
||||
# but avoid converting a plain string to a list of one string
|
||||
if term2 != [ term ]:
|
||||
term = term2
|
||||
@ -59,7 +59,7 @@ class LookupModule(object):
|
||||
# if it's a list, check recursively for items that are a list
|
||||
term = self.flatten(term, inject)
|
||||
ret.extend(term)
|
||||
else:
|
||||
else:
|
||||
ret.append(term)
|
||||
|
||||
return ret
|
||||
|
||||
@ -84,12 +84,24 @@ class ShellModule(object):
|
||||
# FIXME: Support system temp path!
|
||||
return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile)
|
||||
|
||||
def md5(self, path):
|
||||
def expand_user(self, user_home_path):
|
||||
# PowerShell only supports "~" (not "~username"). Resolve-Path ~ does
|
||||
# not seem to work remotely, though by default we are always starting
|
||||
# in the user's home directory.
|
||||
if user_home_path == '~':
|
||||
script = 'Write-Host (Get-Location).Path'
|
||||
elif user_home_path.startswith('~\\'):
|
||||
script = 'Write-Host ((Get-Location).Path + "%s")' % _escape(user_home_path[1:])
|
||||
else:
|
||||
script = 'Write-Host "%s"' % _escape(user_home_path)
|
||||
return _encode_script(script)
|
||||
|
||||
def checksum(self, path, python_interp):
|
||||
path = _escape(path)
|
||||
script = '''
|
||||
If (Test-Path -PathType Leaf "%(path)s")
|
||||
{
|
||||
$sp = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider;
|
||||
$sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider;
|
||||
$fp = [System.IO.File]::Open("%(path)s", [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read);
|
||||
[System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower();
|
||||
$fp.Dispose();
|
||||
|
||||
@ -79,7 +79,6 @@ class ShellModule(object):
|
||||
return 'echo %s' % user_home_path
|
||||
|
||||
def checksum(self, path, python_interp):
|
||||
path = pipes.quote(path)
|
||||
# The following test needs to be SH-compliant. BASH-isms will
|
||||
# not work if /bin/sh points to a non-BASH shell.
|
||||
#
|
||||
@ -97,14 +96,14 @@ class ShellModule(object):
|
||||
# 0. This logic is added to the end of the cmd at the bottom of this
|
||||
# function.
|
||||
|
||||
test = "rc=flag; [ -r \"%(p)s\" ] || rc=2; [ -f \"%(p)s\" ] || rc=1; [ -d \"%(p)s\" ] && rc=3; %(i)s -V 2>/dev/null || rc=4; [ x\"$rc\" != \"xflag\" ] && echo \"${rc} %(p)s\" && exit 0" % dict(p=path, i=python_interp)
|
||||
test = "rc=flag; [ -r \'%(p)s\' ] || rc=2; [ -f \'%(p)s\' ] || rc=1; [ -d \'%(p)s\' ] && rc=3; %(i)s -V 2>/dev/null || rc=4; [ x\"$rc\" != \"xflag\" ] && echo \"${rc}\"\' %(p)s\' && exit 0" % dict(p=path, i=python_interp)
|
||||
csums = [
|
||||
"(%s -c 'import hashlib; print(hashlib.sha1(open(\"%s\", \"rb\").read()).hexdigest())' 2>/dev/null)" % (python_interp, path), # Python > 2.4 (including python3)
|
||||
"(%s -c 'import sha; print(sha.sha(open(\"%s\", \"rb\").read()).hexdigest())' 2>/dev/null)" % (python_interp, path), # Python == 2.4
|
||||
"(%s -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();\nafile = open(\"%s\", \"rb\")\nbuf = afile.read(BLOCKSIZE)\nwhile len(buf) > 0:\n\thasher.update(buf)\n\tbuf = afile.read(BLOCKSIZE)\nafile.close()\nprint(hasher.hexdigest())' 2>/dev/null)" % (python_interp, path), # Python > 2.4 (including python3)
|
||||
"(%s -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();\nafile = open(\"%s\", \"rb\")\nbuf = afile.read(BLOCKSIZE)\nwhile len(buf) > 0:\n\thasher.update(buf)\n\tbuf = afile.read(BLOCKSIZE)\nafile.close()\nprint(hasher.hexdigest())' 2>/dev/null)" % (python_interp, path), # Python == 2.4
|
||||
]
|
||||
|
||||
cmd = " || ".join(csums)
|
||||
cmd = "%s; %s || (echo \"0 %s\")" % (test, cmd, path)
|
||||
cmd = "%s; %s || (echo \'0 %s\')" % (test, cmd, path)
|
||||
return cmd
|
||||
|
||||
def build_module_command(self, env_string, shebang, cmd, rm_tmp=None):
|
||||
|
||||
@ -31,6 +31,7 @@ from ansible.utils.plugins import *
|
||||
from ansible.utils.su_prompts import *
|
||||
from ansible.callbacks import display
|
||||
from ansible.module_utils.splitter import split_args, unquote
|
||||
from ansible.module_utils.basic import heuristic_log_sanitize
|
||||
import ansible.constants as C
|
||||
import ast
|
||||
import time
|
||||
@ -45,10 +46,8 @@ import warnings
|
||||
import traceback
|
||||
import getpass
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import contextlib
|
||||
import jinja2.exceptions
|
||||
|
||||
from vault import VaultLib
|
||||
|
||||
@ -64,9 +63,10 @@ CODE_REGEX = re.compile(r'(?:{%|%})')
|
||||
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
# simplejson can be much faster if it's available
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
# Note, sha1 is the only hash algorithm compatible with python2.4 and with
|
||||
# FIPS-140 mode (as of 11-2014)
|
||||
@ -999,34 +999,18 @@ def sanitize_output(str):
|
||||
|
||||
private_keys = ['password', 'login_password']
|
||||
|
||||
filter_re = [
|
||||
# filter out things like user:pass@foo/whatever
|
||||
# and http://username:pass@wherever/foo
|
||||
re.compile('^(?P<before>.*:)(?P<password>.*)(?P<after>\@.*)$'),
|
||||
]
|
||||
parts = parse_kv(str)
|
||||
output = []
|
||||
for (k, v) in parts.items():
|
||||
if k in private_keys:
|
||||
output.append("%s=VALUE_HIDDEN" % k)
|
||||
continue
|
||||
else:
|
||||
v = heuristic_log_sanitize(v)
|
||||
output.append('%s=%s' % (k, v))
|
||||
output = ' '.join(output)
|
||||
return output
|
||||
|
||||
parts = str.split()
|
||||
output = ''
|
||||
for part in parts:
|
||||
try:
|
||||
(k,v) = part.split('=', 1)
|
||||
if k in private_keys:
|
||||
output += " %s=VALUE_HIDDEN" % k
|
||||
else:
|
||||
found = False
|
||||
for filter in filter_re:
|
||||
m = filter.match(v)
|
||||
if m:
|
||||
d = m.groupdict()
|
||||
output += " %s=%s" % (k, d['before'] + "********" + d['after'])
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
output += " %s" % part
|
||||
except:
|
||||
output += " %s" % part
|
||||
|
||||
return output.strip()
|
||||
|
||||
####################################################################
|
||||
# option handling code for /usr/bin/ansible and ansible-playbook
|
||||
@ -1451,7 +1435,7 @@ def safe_eval(expr, locals={}, include_exceptions=False):
|
||||
return expr
|
||||
|
||||
|
||||
def listify_lookup_plugin_terms(terms, basedir, inject, fail_on_undefined=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR):
|
||||
def listify_lookup_plugin_terms(terms, basedir, inject):
|
||||
|
||||
from ansible.utils import template
|
||||
|
||||
@ -1469,13 +1453,11 @@ def listify_lookup_plugin_terms(terms, basedir, inject, fail_on_undefined=C.DEFA
|
||||
# if not already a list, get ready to evaluate with Jinja2
|
||||
# not sure why the "/" is in above code :)
|
||||
try:
|
||||
new_terms = template.template(basedir, "{{%s}}" % terms, inject, convert_bare=True, fail_on_undefined=fail_on_undefined)
|
||||
new_terms = template.template(basedir, "{{ %s }}" % terms, inject)
|
||||
if isinstance(new_terms, basestring) and "{{" in new_terms:
|
||||
pass
|
||||
else:
|
||||
terms = new_terms
|
||||
except jinja2.exceptions.UndefinedError, e:
|
||||
raise errors.AnsibleUndefinedVariable('undefined variable in items: %s' % e)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@ -167,17 +167,20 @@ class PluginLoader(object):
|
||||
else:
|
||||
suffixes = ['.py', '']
|
||||
|
||||
for suffix in suffixes:
|
||||
full_name = '%s%s' % (name, suffix)
|
||||
if full_name in self._plugin_path_cache:
|
||||
return self._plugin_path_cache[full_name]
|
||||
# loop over paths and then loop over suffixes to find plugin
|
||||
for i in self._get_paths():
|
||||
for suffix in suffixes:
|
||||
full_name = '%s%s' % (name, suffix)
|
||||
|
||||
if full_name in self._plugin_path_cache:
|
||||
return self._plugin_path_cache[full_name]
|
||||
|
||||
for i in self._get_paths():
|
||||
path = os.path.join(i, full_name)
|
||||
if os.path.isfile(path):
|
||||
self._plugin_path_cache[full_name] = path
|
||||
return path
|
||||
|
||||
# if nothing is found, try finding alias/deprecated
|
||||
if not name.startswith('_'):
|
||||
return self.find_plugin('_' + name, suffixes, transport)
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ SU_PROMPT_LOCALIZATIONS = [
|
||||
'密碼',
|
||||
]
|
||||
|
||||
SU_PROMPT_LOCALIZATIONS_RE = re.compile("|".join([x + ' ?: ?' for x in SU_PROMPT_LOCALIZATIONS]), flags=re.IGNORECASE)
|
||||
SU_PROMPT_LOCALIZATIONS_RE = re.compile("|".join(['(\w+\'s )?' + x + ' ?: ?' for x in SU_PROMPT_LOCALIZATIONS]), flags=re.IGNORECASE)
|
||||
|
||||
def check_su_prompt(data):
|
||||
return bool(SU_PROMPT_LOCALIZATIONS_RE.match(data))
|
||||
|
||||
@ -33,6 +33,7 @@ import ast
|
||||
import traceback
|
||||
|
||||
from ansible.utils.string_functions import count_newlines_from_end
|
||||
from ansible.utils import to_bytes
|
||||
|
||||
class Globals(object):
|
||||
|
||||
@ -86,14 +87,17 @@ JINJA2_ALLOWED_OVERRIDES = ['trim_blocks', 'lstrip_blocks', 'newline_sequence',
|
||||
def lookup(name, *args, **kwargs):
|
||||
from ansible import utils
|
||||
instance = utils.plugins.lookup_loader.get(name.lower(), basedir=kwargs.get('basedir',None))
|
||||
vars = kwargs.get('vars', None)
|
||||
tvars = kwargs.get('vars', None)
|
||||
|
||||
if instance is not None:
|
||||
# safely catch run failures per #5059
|
||||
try:
|
||||
ran = instance.run(*args, inject=vars, **kwargs)
|
||||
ran = instance.run(*args, inject=tvars, **kwargs)
|
||||
except errors.AnsibleError:
|
||||
raise
|
||||
except jinja2.exceptions.UndefinedError, e:
|
||||
raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e))
|
||||
except Exception, e:
|
||||
ran = None
|
||||
raise errors.AnsibleError('Unexpected error in during lookup: %s' % e)
|
||||
if ran:
|
||||
ran = ",".join(ran)
|
||||
return ran
|
||||
@ -269,7 +273,7 @@ def template_from_file(basedir, path, vars, vault_password=None):
|
||||
managed_str = managed_default.format(
|
||||
host = vars['template_host'],
|
||||
uid = vars['template_uid'],
|
||||
file = vars['template_path']
|
||||
file = to_bytes(vars['template_path'])
|
||||
)
|
||||
vars['ansible_managed'] = time.strftime(
|
||||
managed_str,
|
||||
|
||||
248
lib/ansible/utils/unicode.py
Normal file
248
lib/ansible/utils/unicode.py
Normal file
@ -0,0 +1,248 @@
|
||||
# (c) 2012-2014, Toshio Kuraotmi <a.badger@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
# to_bytes and to_unicode were written by Toshio Kuratomi for the
|
||||
# python-kitchen library https://pypi.python.org/pypi/kitchen
|
||||
# They are licensed in kitchen under the terms of the GPLv2+
|
||||
# They were copied and modified for use in ansible by Toshio in Jan 2015
|
||||
# (simply removing the deprecated features)
|
||||
|
||||
#: Aliases for the utf-8 codec
|
||||
_UTF8_ALIASES = frozenset(('utf-8', 'UTF-8', 'utf8', 'UTF8', 'utf_8', 'UTF_8',
|
||||
'utf', 'UTF', 'u8', 'U8'))
|
||||
#: Aliases for the latin-1 codec
|
||||
_LATIN1_ALIASES = frozenset(('latin-1', 'LATIN-1', 'latin1', 'LATIN1',
|
||||
'latin', 'LATIN', 'l1', 'L1', 'cp819', 'CP819', '8859', 'iso8859-1',
|
||||
'ISO8859-1', 'iso-8859-1', 'ISO-8859-1'))
|
||||
|
||||
# EXCEPTION_CONVERTERS is defined below due to using to_unicode
|
||||
|
||||
def to_unicode(obj, encoding='utf-8', errors='replace', nonstring=None):
|
||||
'''Convert an object into a :class:`unicode` string
|
||||
|
||||
:arg obj: Object to convert to a :class:`unicode` string. This should
|
||||
normally be a byte :class:`str`
|
||||
:kwarg encoding: What encoding to try converting the byte :class:`str` as.
|
||||
Defaults to :term:`utf-8`
|
||||
:kwarg errors: If errors are found while decoding, perform this action.
|
||||
Defaults to ``replace`` which replaces the invalid bytes with
|
||||
a character that means the bytes were unable to be decoded. Other
|
||||
values are the same as the error handling schemes in the `codec base
|
||||
classes
|
||||
<http://docs.python.org/library/codecs.html#codec-base-classes>`_.
|
||||
For instance ``strict`` which raises an exception and ``ignore`` which
|
||||
simply omits the non-decodable characters.
|
||||
:kwarg nonstring: How to treat nonstring values. Possible values are:
|
||||
|
||||
:simplerepr: Attempt to call the object's "simple representation"
|
||||
method and return that value. Python-2.3+ has two methods that
|
||||
try to return a simple representation: :meth:`object.__unicode__`
|
||||
and :meth:`object.__str__`. We first try to get a usable value
|
||||
from :meth:`object.__unicode__`. If that fails we try the same
|
||||
with :meth:`object.__str__`.
|
||||
:empty: Return an empty :class:`unicode` string
|
||||
:strict: Raise a :exc:`TypeError`
|
||||
:passthru: Return the object unchanged
|
||||
:repr: Attempt to return a :class:`unicode` string of the repr of the
|
||||
object
|
||||
|
||||
Default is ``simplerepr``
|
||||
|
||||
:raises TypeError: if :attr:`nonstring` is ``strict`` and
|
||||
a non-:class:`basestring` object is passed in or if :attr:`nonstring`
|
||||
is set to an unknown value
|
||||
:raises UnicodeDecodeError: if :attr:`errors` is ``strict`` and
|
||||
:attr:`obj` is not decodable using the given encoding
|
||||
:returns: :class:`unicode` string or the original object depending on the
|
||||
value of :attr:`nonstring`.
|
||||
|
||||
Usually this should be used on a byte :class:`str` but it can take both
|
||||
byte :class:`str` and :class:`unicode` strings intelligently. Nonstring
|
||||
objects are handled in different ways depending on the setting of the
|
||||
:attr:`nonstring` parameter.
|
||||
|
||||
The default values of this function are set so as to always return
|
||||
a :class:`unicode` string and never raise an error when converting from
|
||||
a byte :class:`str` to a :class:`unicode` string. However, when you do
|
||||
not pass validly encoded text (or a nonstring object), you may end up with
|
||||
output that you don't expect. Be sure you understand the requirements of
|
||||
your data, not just ignore errors by passing it through this function.
|
||||
'''
|
||||
# Could use isbasestring/isunicode here but we want this code to be as
|
||||
# fast as possible
|
||||
if isinstance(obj, basestring):
|
||||
if isinstance(obj, unicode):
|
||||
return obj
|
||||
if encoding in _UTF8_ALIASES:
|
||||
return unicode(obj, 'utf-8', errors)
|
||||
if encoding in _LATIN1_ALIASES:
|
||||
return unicode(obj, 'latin-1', errors)
|
||||
return obj.decode(encoding, errors)
|
||||
|
||||
if not nonstring:
|
||||
nonstring = 'simplerepr'
|
||||
if nonstring == 'empty':
|
||||
return u''
|
||||
elif nonstring == 'passthru':
|
||||
return obj
|
||||
elif nonstring == 'simplerepr':
|
||||
try:
|
||||
simple = obj.__unicode__()
|
||||
except (AttributeError, UnicodeError):
|
||||
simple = None
|
||||
if not simple:
|
||||
try:
|
||||
simple = str(obj)
|
||||
except UnicodeError:
|
||||
try:
|
||||
simple = obj.__str__()
|
||||
except (UnicodeError, AttributeError):
|
||||
simple = u''
|
||||
if isinstance(simple, str):
|
||||
return unicode(simple, encoding, errors)
|
||||
return simple
|
||||
elif nonstring in ('repr', 'strict'):
|
||||
obj_repr = repr(obj)
|
||||
if isinstance(obj_repr, str):
|
||||
obj_repr = unicode(obj_repr, encoding, errors)
|
||||
if nonstring == 'repr':
|
||||
return obj_repr
|
||||
raise TypeError('to_unicode was given "%(obj)s" which is neither'
|
||||
' a byte string (str) or a unicode string' %
|
||||
{'obj': obj_repr.encode(encoding, 'replace')})
|
||||
|
||||
raise TypeError('nonstring value, %(param)s, is not set to a valid'
|
||||
' action' % {'param': nonstring})
|
||||
|
||||
def to_bytes(obj, encoding='utf-8', errors='replace', nonstring=None):
|
||||
'''Convert an object into a byte :class:`str`
|
||||
|
||||
:arg obj: Object to convert to a byte :class:`str`. This should normally
|
||||
be a :class:`unicode` string.
|
||||
:kwarg encoding: Encoding to use to convert the :class:`unicode` string
|
||||
into a byte :class:`str`. Defaults to :term:`utf-8`.
|
||||
:kwarg errors: If errors are found while encoding, perform this action.
|
||||
Defaults to ``replace`` which replaces the invalid bytes with
|
||||
a character that means the bytes were unable to be encoded. Other
|
||||
values are the same as the error handling schemes in the `codec base
|
||||
classes
|
||||
<http://docs.python.org/library/codecs.html#codec-base-classes>`_.
|
||||
For instance ``strict`` which raises an exception and ``ignore`` which
|
||||
simply omits the non-encodable characters.
|
||||
:kwarg nonstring: How to treat nonstring values. Possible values are:
|
||||
|
||||
:simplerepr: Attempt to call the object's "simple representation"
|
||||
method and return that value. Python-2.3+ has two methods that
|
||||
try to return a simple representation: :meth:`object.__unicode__`
|
||||
and :meth:`object.__str__`. We first try to get a usable value
|
||||
from :meth:`object.__str__`. If that fails we try the same
|
||||
with :meth:`object.__unicode__`.
|
||||
:empty: Return an empty byte :class:`str`
|
||||
:strict: Raise a :exc:`TypeError`
|
||||
:passthru: Return the object unchanged
|
||||
:repr: Attempt to return a byte :class:`str` of the :func:`repr` of the
|
||||
object
|
||||
|
||||
Default is ``simplerepr``.
|
||||
|
||||
:raises TypeError: if :attr:`nonstring` is ``strict`` and
|
||||
a non-:class:`basestring` object is passed in or if :attr:`nonstring`
|
||||
is set to an unknown value.
|
||||
:raises UnicodeEncodeError: if :attr:`errors` is ``strict`` and all of the
|
||||
bytes of :attr:`obj` are unable to be encoded using :attr:`encoding`.
|
||||
:returns: byte :class:`str` or the original object depending on the value
|
||||
of :attr:`nonstring`.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you pass a byte :class:`str` into this function the byte
|
||||
:class:`str` is returned unmodified. It is **not** re-encoded with
|
||||
the specified :attr:`encoding`. The easiest way to achieve that is::
|
||||
|
||||
to_bytes(to_unicode(text), encoding='utf-8')
|
||||
|
||||
The initial :func:`to_unicode` call will ensure text is
|
||||
a :class:`unicode` string. Then, :func:`to_bytes` will turn that into
|
||||
a byte :class:`str` with the specified encoding.
|
||||
|
||||
Usually, this should be used on a :class:`unicode` string but it can take
|
||||
either a byte :class:`str` or a :class:`unicode` string intelligently.
|
||||
Nonstring objects are handled in different ways depending on the setting
|
||||
of the :attr:`nonstring` parameter.
|
||||
|
||||
The default values of this function are set so as to always return a byte
|
||||
:class:`str` and never raise an error when converting from unicode to
|
||||
bytes. However, when you do not pass an encoding that can validly encode
|
||||
the object (or a non-string object), you may end up with output that you
|
||||
don't expect. Be sure you understand the requirements of your data, not
|
||||
just ignore errors by passing it through this function.
|
||||
'''
|
||||
# Could use isbasestring, isbytestring here but we want this to be as fast
|
||||
# as possible
|
||||
if isinstance(obj, basestring):
|
||||
if isinstance(obj, str):
|
||||
return obj
|
||||
return obj.encode(encoding, errors)
|
||||
if not nonstring:
|
||||
nonstring = 'simplerepr'
|
||||
|
||||
if nonstring == 'empty':
|
||||
return ''
|
||||
elif nonstring == 'passthru':
|
||||
return obj
|
||||
elif nonstring == 'simplerepr':
|
||||
try:
|
||||
simple = str(obj)
|
||||
except UnicodeError:
|
||||
try:
|
||||
simple = obj.__str__()
|
||||
except (AttributeError, UnicodeError):
|
||||
simple = None
|
||||
if not simple:
|
||||
try:
|
||||
simple = obj.__unicode__()
|
||||
except (AttributeError, UnicodeError):
|
||||
simple = ''
|
||||
if isinstance(simple, unicode):
|
||||
simple = simple.encode(encoding, 'replace')
|
||||
return simple
|
||||
elif nonstring in ('repr', 'strict'):
|
||||
try:
|
||||
obj_repr = obj.__repr__()
|
||||
except (AttributeError, UnicodeError):
|
||||
obj_repr = ''
|
||||
if isinstance(obj_repr, unicode):
|
||||
obj_repr = obj_repr.encode(encoding, errors)
|
||||
else:
|
||||
obj_repr = str(obj_repr)
|
||||
if nonstring == 'repr':
|
||||
return obj_repr
|
||||
raise TypeError('to_bytes was given "%(obj)s" which is neither'
|
||||
' a unicode string or a byte string (str)' % {'obj': obj_repr})
|
||||
|
||||
raise TypeError('nonstring value, %(param)s, is not set to a valid'
|
||||
' action' % {'param': nonstring})
|
||||
|
||||
|
||||
# force the return value of a function to be unicode. Use with partial to
|
||||
# ensure that a filter will return unicode values.
|
||||
def unicode_wrap(func, *args, **kwargs):
|
||||
return to_unicode(func(*args, **kwargs), nonstring='passthru')
|
||||
@ -278,8 +278,10 @@ class VaultEditor(object):
|
||||
tmpdata = self.read_data(self.filename)
|
||||
this_vault = VaultLib(self.password)
|
||||
dec_data = this_vault.decrypt(tmpdata)
|
||||
old_umask = os.umask(0o077)
|
||||
_, tmp_path = tempfile.mkstemp()
|
||||
self.write_data(dec_data, tmp_path)
|
||||
os.umask(old_umask)
|
||||
|
||||
# drop the user into pager on the tmp file
|
||||
call(self._pager_shell_command(tmp_path))
|
||||
|
||||
@ -1,8 +1,26 @@
|
||||
ansible (1.8) unstable; urgency=low
|
||||
ansible (1.8.3) unstable; urgency=low
|
||||
|
||||
* 1.8 release (PENDING)
|
||||
* 1.8.3
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Wed, 21 Oct 2015 04:29:00 -0500
|
||||
-- Ansible, Inc. <supoort@ansible.com> Tue, 17 Feb 2015 16:00:00 -0500
|
||||
|
||||
ansible (1.8.2) unstable; urgency=low
|
||||
|
||||
* 1.8.2
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Thu, 04 Dec 2014 17:00:00 -0500
|
||||
|
||||
ansible (1.8.1) unstable; urgency=low
|
||||
|
||||
* 1.8.1
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Wed, 26 Nov 2014 23:00:00 -0500
|
||||
|
||||
ansible (1.8.0) unstable; urgency=low
|
||||
|
||||
* 1.8.0
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Tue, 25 Nov 2014 17:00:00 -0500
|
||||
|
||||
ansible (1.7.2) unstable; urgency=low
|
||||
|
||||
|
||||
@ -110,6 +110,18 @@ rm -rf %{buildroot}
|
||||
|
||||
%changelog
|
||||
|
||||
* Tue Feb 17 2015 Ansible, Inc. <support@ansible.com> - 1.8.3
|
||||
- Release 1.8.3
|
||||
|
||||
* Thu Dec 04 2014 Michael DeHaan <michael@ansible.com> - 1.8.2
|
||||
- Release 1.8.2
|
||||
|
||||
* Wed Nov 26 2014 Michael DeHaan <michael@ansible.com> - 1.8.1
|
||||
- Release 1.8.1
|
||||
|
||||
* Tue Nov 25 2014 Michael DeHaan <michael@ansible.com> - 1.8.0
|
||||
- Release 1.8.0
|
||||
|
||||
* Wed Sep 24 2014 Michael DeHaan <michael@ansible.com> - 1.7.2
|
||||
- Release 1.7.2
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Cobbler external inventory script
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
from ipalib import api
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
|
||||
2
setup.py
2
setup.py
@ -24,7 +24,7 @@ setup(name='ansible',
|
||||
package_dir={ 'ansible': 'lib/ansible' },
|
||||
packages=find_packages('lib'),
|
||||
package_data={
|
||||
'': ['module_utils/*.ps1'],
|
||||
'': ['module_utils/*.ps1', 'modules/core/windows/*.ps1', 'modules/extras/windows/*.ps1'],
|
||||
},
|
||||
scripts=[
|
||||
'bin/ansible',
|
||||
|
||||
@ -33,7 +33,7 @@ includes:
|
||||
ansible-playbook test_includes.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) $(TEST_FLAGS)
|
||||
|
||||
unicode:
|
||||
ansible-playbook unicode.yml -i $(INVENTORY) -e @$(VARS_FILE) -v $(TEST_FLAGS)
|
||||
ansible-playbook unicode.yml -i $(INVENTORY) -e @$(VARS_FILE) -v $(TEST_FLAGS) -e 'extra_var=café'
|
||||
# Test the start-at-task flag #9571
|
||||
ansible-playbook unicode.yml -i $(INVENTORY) -e @$(VARS_FILE) -v --start-at-task '*¶' -e 'start_at_task=True' $(TEST_FLAGS)
|
||||
|
||||
@ -57,7 +57,7 @@ test_hash:
|
||||
ANSIBLE_HASH_BEHAVIOUR=merge ansible-playbook test_hash.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v -e '{"test_hash":{"extra_args":"this is an extra arg"}}'
|
||||
|
||||
test_var_precedence:
|
||||
ansible-playbook test_var_precedence.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v -e 'extra_var=extra_var'
|
||||
ansible-playbook test_var_precedence.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v -e 'extra_var=extra_var' -e 'extra_var_override=extra_var_override'
|
||||
|
||||
test_vault:
|
||||
ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --list-tasks
|
||||
@ -65,6 +65,9 @@ test_vault:
|
||||
ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --syntax-check
|
||||
ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE)
|
||||
|
||||
test_delegate_to:
|
||||
ansible-playbook test_delegate_to.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
|
||||
|
||||
test_winrm:
|
||||
ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
- hosts: testhost
|
||||
gather_facts: True
|
||||
roles:
|
||||
# In destructive because it creates and removes a user
|
||||
- { role: test_sudo, tags: test_sudo}
|
||||
- { role: test_service, tags: test_service }
|
||||
# Current pip unconditionally uses md5. We can re-enable if pip switches
|
||||
# to a different hash or allows us to not check md5
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
[local]
|
||||
testhost ansible_ssh_host=127.0.0.1 ansible_connection=local
|
||||
testhost2 ansible_ssh_host=127.0.0.1 ansible_connection=local
|
||||
# For testing delegate_to
|
||||
testhost3 ansible_ssh_host=127.0.0.3
|
||||
testhost4 ansible_ssh_host=127.0.0.4
|
||||
|
||||
# the following inline declarations are accompanied
|
||||
# by (preferred) group_vars/ and host_vars/ variables
|
||||
@ -9,6 +12,10 @@ testhost2 ansible_ssh_host=127.0.0.1 ansible_connection=local
|
||||
[inven_overridehosts]
|
||||
invenoverride ansible_ssh_host=127.0.0.1 ansible_connection=local
|
||||
|
||||
[all:vars]
|
||||
extra_var_override=FROM_INVENTORY
|
||||
inven_var=inventory_var
|
||||
|
||||
[inven_overridehosts:vars]
|
||||
foo=foo
|
||||
var_dir=vars
|
||||
|
||||
@ -276,8 +276,9 @@
|
||||
- name: assert the task was skipped
|
||||
assert:
|
||||
that:
|
||||
- "'skipped' in result"
|
||||
- result.skipped
|
||||
- "result.results|length == 1"
|
||||
- "'skipped' in result.results[0]"
|
||||
- "result.results[0].skipped == True"
|
||||
|
||||
- name: test a with_items loop skipping a single item
|
||||
debug: var=item
|
||||
|
||||
@ -146,6 +146,7 @@
|
||||
git:
|
||||
repo: '{{ repo_format1 }}'
|
||||
update: no
|
||||
clone: no
|
||||
accept_hostkey: yes
|
||||
register: git_result
|
||||
|
||||
|
||||
44
test/integration/roles/test_sudo/tasks/main.yml
Normal file
44
test/integration/roles/test_sudo/tasks/main.yml
Normal file
@ -0,0 +1,44 @@
|
||||
- include_vars: default.yml
|
||||
|
||||
- name: Create test user
|
||||
user:
|
||||
name: "{{ sudo_test_user }}"
|
||||
|
||||
- name: tilde expansion honors sudo in file
|
||||
sudo: True
|
||||
sudo_user: "{{ sudo_test_user }}"
|
||||
file:
|
||||
path: "~/foo.txt"
|
||||
state: touch
|
||||
|
||||
- name: check that the path in the user's home dir was created
|
||||
stat:
|
||||
path: "~{{ sudo_test_user }}/foo.txt"
|
||||
register: results
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "results.stat.exists == True"
|
||||
|
||||
- name: tilde expansion honors sudo in template
|
||||
sudo: True
|
||||
sudo_user: "{{ sudo_test_user }}"
|
||||
template:
|
||||
src: "bar.j2"
|
||||
dest: "~/bar.txt"
|
||||
|
||||
- name: check that the path in the user's home dir was created
|
||||
stat:
|
||||
path: "~{{ sudo_test_user }}/bar.txt"
|
||||
register: results
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "results.stat.exists == True"
|
||||
|
||||
- name: Remove test user and their home dir
|
||||
user:
|
||||
name: "{{ sudo_test_user }}"
|
||||
state: "absent"
|
||||
remove: "yes"
|
||||
|
||||
1
test/integration/roles/test_sudo/templates/bar.j2
Normal file
1
test/integration/roles/test_sudo/templates/bar.j2
Normal file
@ -0,0 +1 @@
|
||||
{{ sudo_test_user }}
|
||||
1
test/integration/roles/test_sudo/vars/default.yml
Normal file
1
test/integration/roles/test_sudo/vars/default.yml
Normal file
@ -0,0 +1 @@
|
||||
sudo_test_user: ansibletest1
|
||||
@ -96,3 +96,42 @@
|
||||
- "dir_attrs.stat.uid != 0"
|
||||
- "dir_attrs.stat.pw_name == 'nobody'"
|
||||
- "dir_attrs.stat.mode == '0755'"
|
||||
|
||||
- name: make a symlink to the templated file
|
||||
file:
|
||||
path: '{{ output_dir }}/foo.symlink'
|
||||
src: '{{ output_dir }}/foo.templated'
|
||||
state: link
|
||||
|
||||
- name: check that templating the symlink results in the file being templated
|
||||
template:
|
||||
src: foo.j2
|
||||
dest: '{{output_dir}}/foo.symlink'
|
||||
mode: 0600
|
||||
follow: True
|
||||
register: template_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "template_result.changed == True"
|
||||
|
||||
- name: check that the file has the correct attributes
|
||||
stat: path={{output_dir | expanduser}}/template-dir/foo.j2
|
||||
register: file_attrs
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "file_attrs.stat.mode == '0600'"
|
||||
|
||||
- name: check that templating the symlink again makes no changes
|
||||
template:
|
||||
src: foo.j2
|
||||
dest: '{{output_dir}}/foo.symlink'
|
||||
mode: 0600
|
||||
follow: True
|
||||
register: template_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "template_result.changed == False"
|
||||
|
||||
|
||||
@ -136,3 +136,4 @@
|
||||
|
||||
- name: remove our unarchive destination
|
||||
file: path=/tmp/foo-unarchive.txt state=absent
|
||||
|
||||
|
||||
@ -18,11 +18,11 @@
|
||||
|
||||
- name: clean out the test directory
|
||||
local_action: file name={{ output_dir|mandatory }} state=absent
|
||||
tags: me
|
||||
run_once: true
|
||||
|
||||
- name: create the test directory
|
||||
local_action: file name={{ output_dir }} state=directory
|
||||
tags: me
|
||||
run_once: true
|
||||
|
||||
- name: fetch a small file
|
||||
fetch: src="C:/Windows/win.ini" dest={{ output_dir }}
|
||||
@ -145,7 +145,7 @@
|
||||
- "not fetch_missing_nofail|changed"
|
||||
|
||||
- name: attempt to fetch a non-existent file - fail on missing
|
||||
fetch: src="C:/this_file_should_not_exist.txt" dest={{ output_dir }} fail_on_missing=yes
|
||||
fetch: src="~/this_file_should_not_exist.txt" dest={{ output_dir }} fail_on_missing=yes
|
||||
register: fetch_missing
|
||||
ignore_errors: true
|
||||
|
||||
@ -164,5 +164,6 @@
|
||||
- name: check fetch directory result
|
||||
assert:
|
||||
that:
|
||||
- "fetch_dir|failed"
|
||||
# Doesn't fail anymore, only returns a message.
|
||||
- "not fetch_dir|changed"
|
||||
- "fetch_dir.msg"
|
||||
|
||||
50
test/integration/test_delegate_to.yml
Normal file
50
test/integration/test_delegate_to.yml
Normal file
@ -0,0 +1,50 @@
|
||||
- hosts: testhost3
|
||||
roles:
|
||||
- { role: prepare_tests }
|
||||
vars:
|
||||
- template_role: ./roles/test_template
|
||||
- templated_var: foo
|
||||
tasks:
|
||||
- name: Test no delegate_to
|
||||
setup:
|
||||
register: setup_results
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"127.0.0.3" in setup_results.ansible_facts.ansible_env["SSH_CONNECTION"]'
|
||||
|
||||
- name: Test delegate_to with host in inventory
|
||||
setup:
|
||||
register: setup_results
|
||||
delegate_to: testhost4
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"127.0.0.4" in setup_results.ansible_facts.ansible_env["SSH_CONNECTION"]'
|
||||
|
||||
- name: Test delegate_to with host not in inventory
|
||||
setup:
|
||||
register: setup_results
|
||||
delegate_to: 127.0.0.254
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"127.0.0.254" in setup_results.ansible_facts.ansible_env["SSH_CONNECTION"]'
|
||||
#
|
||||
# Smoketest some other modules do not error as a canary
|
||||
#
|
||||
- name: Test file works with delegate_to and a host in inventory
|
||||
file: path={{ output_dir }}/foo.txt mode=0644 state=touch
|
||||
delegate_to: testhost4
|
||||
|
||||
- name: Test file works with delegate_to and a host not in inventory
|
||||
file: path={{ output_dir }}/test_follow_link mode=0644 state=touch
|
||||
delegate_to: 127.0.0.254
|
||||
|
||||
- name: Test template works with delegate_to and a host in inventory
|
||||
template: src={{ template_role }}/templates/foo.j2 dest={{ output_dir }}/foo.txt
|
||||
delegate_to: testhost4
|
||||
|
||||
- name: Test template works with delegate_to and a host not in inventory
|
||||
template: src={{ template_role }}/templates/foo.j2 dest={{ output_dir }}/foo.txt
|
||||
delegate_to: 127.0.0.254
|
||||
@ -4,6 +4,8 @@
|
||||
- vars_var: "vars_var"
|
||||
- param_var: "BAD!"
|
||||
- vars_files_var: "BAD!"
|
||||
- extra_var_override_once_removed: "{{ extra_var_override }}"
|
||||
- from_inventory_once_removed: "{{ inven_var | default('BAD!') }}"
|
||||
vars_files:
|
||||
- vars/test_var_precedence.yml
|
||||
roles:
|
||||
@ -15,17 +17,22 @@
|
||||
- name: use set_fact to override the registered_var
|
||||
set_fact: registered_var="this is from set_fact"
|
||||
- debug: var=extra_var
|
||||
- debug: var=extra_var_override_once_removed
|
||||
- debug: var=vars_var
|
||||
- debug: var=vars_files_var
|
||||
- debug: var=vars_files_var_role
|
||||
- debug: var=registered_var
|
||||
- debug: var=from_inventory_once_removed
|
||||
- assert:
|
||||
that:
|
||||
- 'extra_var == "extra_var"'
|
||||
- 'extra_var_override == "extra_var_override"'
|
||||
- 'extra_var_override_once_removed == "extra_var_override"'
|
||||
- 'vars_var == "vars_var"'
|
||||
- 'vars_files_var == "vars_files_var"'
|
||||
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||
- 'registered_var == "this is from set_fact"'
|
||||
- 'from_inventory_once_removed == "inventory_var"'
|
||||
|
||||
- hosts: inven_overridehosts
|
||||
vars_files:
|
||||
|
||||
@ -35,6 +35,10 @@
|
||||
host_id: '{{item}}'
|
||||
with_sequence: start=1 end={{num_hosts}} format=%d
|
||||
|
||||
- name: 'A task with unicode extra vars'
|
||||
debug: var=extra_var
|
||||
|
||||
|
||||
- name: 'A play for hosts in group: ĪīĬĭ'
|
||||
hosts: 'ĪīĬĭ'
|
||||
gather_facts: true
|
||||
|
||||
@ -7,6 +7,7 @@ from nose.tools import timed
|
||||
|
||||
from ansible import errors
|
||||
from ansible.module_common import ModuleReplacer
|
||||
from ansible.module_utils.basic import heuristic_log_sanitize
|
||||
from ansible.utils import checksum as utils_checksum
|
||||
|
||||
TEST_MODULE_DATA = """
|
||||
@ -264,23 +265,23 @@ class TestModuleUtilsBasicHelpers(unittest.TestCase):
|
||||
|
||||
@timed(5)
|
||||
def test_log_sanitize_speed_many_url(self):
|
||||
self.module._heuristic_log_sanitize(self.many_url)
|
||||
heuristic_log_sanitize(self.many_url)
|
||||
|
||||
@timed(5)
|
||||
def test_log_sanitize_speed_many_ssh(self):
|
||||
self.module._heuristic_log_sanitize(self.many_ssh)
|
||||
heuristic_log_sanitize(self.many_ssh)
|
||||
|
||||
@timed(5)
|
||||
def test_log_sanitize_speed_one_url(self):
|
||||
self.module._heuristic_log_sanitize(self.one_url)
|
||||
heuristic_log_sanitize(self.one_url)
|
||||
|
||||
@timed(5)
|
||||
def test_log_sanitize_speed_one_ssh(self):
|
||||
self.module._heuristic_log_sanitize(self.one_ssh)
|
||||
heuristic_log_sanitize(self.one_ssh)
|
||||
|
||||
@timed(5)
|
||||
def test_log_sanitize_speed_zero_secrets(self):
|
||||
self.module._heuristic_log_sanitize(self.zero_secrets)
|
||||
heuristic_log_sanitize(self.zero_secrets)
|
||||
|
||||
#
|
||||
# Test that the password obfuscation sanitizes somewhat cleanly.
|
||||
@ -290,8 +291,8 @@ class TestModuleUtilsBasicHelpers(unittest.TestCase):
|
||||
url_data = repr(self._gen_data(3, True, True, self.URL_SECRET))
|
||||
ssh_data = repr(self._gen_data(3, True, True, self.SSH_SECRET))
|
||||
|
||||
url_output = self.module._heuristic_log_sanitize(url_data)
|
||||
ssh_output = self.module._heuristic_log_sanitize(ssh_data)
|
||||
url_output = heuristic_log_sanitize(url_data)
|
||||
ssh_output = heuristic_log_sanitize(ssh_data)
|
||||
|
||||
# Basic functionality: Successfully hid the password
|
||||
try:
|
||||
|
||||
@ -566,17 +566,9 @@ class TestUtils(unittest.TestCase):
|
||||
|
||||
def test_listify_lookup_plugin_terms(self):
|
||||
basedir = os.path.dirname(__file__)
|
||||
|
||||
# Straight lookups
|
||||
self.assertEqual(ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=[])), [])
|
||||
self.assertEqual(ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=['one', 'two'])), ['one', 'two'])
|
||||
|
||||
# Variable interpolation
|
||||
self.assertEqual(ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=['{{ foo }}', '{{ bar }}'], foo="hello", bar="world")),
|
||||
['hello', 'world'])
|
||||
with self.assertRaises(ansible.errors.AnsibleError) as ex:
|
||||
ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=['{{ foo }}', '{{ bar_typo }}'], foo="hello", bar="world"))
|
||||
self.assertTrue("undefined variable in items: 'bar_typo'" in ex.exception.msg)
|
||||
#self.assertEqual(ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=[])), [])
|
||||
#self.assertEqual(ansible.utils.listify_lookup_plugin_terms('things', basedir, dict(things=['one', 'two'])), ['one', 'two'])
|
||||
|
||||
def test_deprecated(self):
|
||||
sys_stderr = sys.stderr
|
||||
|
||||
Reference in New Issue
Block a user