diff options
author | Dag Wieers <dag@wieers.com> | 2016-02-22 17:04:33 +0100 |
---|---|---|
committer | Dag Wieers <dag@wieers.com> | 2016-02-22 17:08:42 +0100 |
commit | ccbc849b2094e434df2e6f40b5df81c17659ec8e (patch) | |
tree | eb4821ebdbc8a22225e1332f9bb1394dc9be622c | |
parent | fb442206ca3cc1e9734dd231af005e2af4fee478 (diff) | |
parent | f36896b2bba1399846085cdaca3ecbd1966d9462 (diff) | |
download | ansible-ccbc849b2094e434df2e6f40b5df81c17659ec8e.tar.gz |
Merge branch 'stable-1.9' of github.com:ansible/ansible into fix-role_params-merge_hash
Implement new fix from @jimi-c
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | lib/ansible/cache/memcached.py | 4 | ||||
-rw-r--r-- | lib/ansible/module_utils/basic.py | 76 | ||||
m--------- | lib/ansible/modules/core | 0 | ||||
m--------- | lib/ansible/modules/extras | 0 | ||||
-rw-r--r-- | lib/ansible/runner/__init__.py | 3 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/assemble.py | 3 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/copy.py | 5 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/raw.py | 2 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/template.py | 18 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/win_copy.py | 6 | ||||
-rw-r--r-- | lib/ansible/runner/action_plugins/win_template.py | 16 | ||||
-rw-r--r-- | lib/ansible/runner/connection_plugins/accelerate.py | 17 | ||||
-rw-r--r-- | lib/ansible/runner/connection_plugins/ssh.py | 5 | ||||
-rw-r--r-- | lib/ansible/runner/lookup_plugins/csvfile.py | 5 | ||||
-rw-r--r-- | lib/ansible/utils/__init__.py | 104 |
16 files changed, 172 insertions, 95 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index aae30f8a75..37c12049d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ Other Notable Changes: * Fix bug in the literal_eval module code used when we need python-2.4 compat * Added --ignore-certs, -c option to ansible-galaxy. Allows ansible-galaxy to work behind a proxy when the proxy fails to forward server certificates. +* Fixed bug where tasks marked no_log were showing hidden values in output if + ansible's --diff option was used. +* Fix bug with non-english locales in git and apt modules ## 1.9.4 "Dancing In the Street" - Oct 10, 2015 diff --git a/lib/ansible/cache/memcached.py b/lib/ansible/cache/memcached.py index ea922434b5..470ab6b224 100644 --- a/lib/ansible/cache/memcached.py +++ b/lib/ansible/cache/memcached.py @@ -19,8 +19,8 @@ import collections import os import sys import time -import threading from itertools import chain +from multiprocessing import Lock from ansible import constants as C from ansible.cache.base import BaseCacheModule @@ -51,7 +51,7 @@ class ProxyClientPool(object): self._num_connections = 0 self._available_connections = collections.deque(maxlen=self.max_connections) self._locked_connections = set() - self._lock = threading.Lock() + self._lock = Lock() def _check_safe(self): if self.pid != os.getpid(): diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 2f300f7867..5a84ec92c5 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -355,7 +355,10 @@ class AnsibleModule(object): self.check_mode = False self.no_log = no_log self.cleanup_files = [] - + # May be used to set modifications to the environment for any + # run_command invocation + self.run_command_environ_update = {} + self.aliases = {} if add_file_common_args: @@ -363,8 +366,8 @@ class AnsibleModule(object): if k not in self.argument_spec: self.argument_spec[k] = v - # check the locale as set by the current environment, and - # reset to LANG=C if it's an invalid/unavailable locale + # check the locale as set by the current environment, and reset to + # a known valid (LANG=C) if it's an invalid/unavailable locale self._check_locale() (self.params, self.args) = self._load_params() @@ -866,7 +869,7 @@ class AnsibleModule(object): # setting the locale to '' uses the default locale # as it would be returned by locale.getdefaultlocale() locale.setlocale(locale.LC_ALL, '') - except locale.Error, e: + except locale.Error: # fallback to the 'C' locale, which may cause unicode # issues but is preferable to simply failing because # of an unknown locale @@ -1412,25 +1415,29 @@ class AnsibleModule(object): # rename might not preserve context self.set_context_if_different(dest, context, False) - def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None): + def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None, environ_update=None): ''' Execute a command, returns rc, stdout, and stderr. - args is the command to run - If args is a list, the command will be run with shell=False. - If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False - If args is a string and use_unsafe_shell=True it run with shell=True. - Other arguments: - - check_rc (boolean) Whether to call fail_json in case of - non zero RC. Default is False. - - close_fds (boolean) See documentation for subprocess.Popen(). - Default is True. - - executable (string) See documentation for subprocess.Popen(). - Default is None. - - prompt_regex (string) A regex string (not a compiled regex) which - can be used to detect prompts in the stdout - which would otherwise cause the execution - to hang (especially if no input data is - specified) + + :arg args: is the command to run + * If args is a list, the command will be run with shell=False. + * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False + * If args is a string and use_unsafe_shell=True it runs with shell=True. + :kw check_rc: Whether to call fail_json in case of non zero RC. + Default False + :kw close_fds: See documentation for subprocess.Popen(). Default True + :kw executable: See documentation for subprocess.Popen(). Default None + :kw data: If given, information to write to the stdin of the command + :kw binary_data: If False, append a newline to the data. Default False + :kw path_prefix: If given, additional path to find the command in. + This adds to the PATH environment vairable so helper commands in + the same directory can also be found + :kw cwd: iIf given, working directory to run the command inside + :kw use_unsafe_shell: See `args` parameter. Default False + :kw prompt_regex: Regex string (not a compiled regex) which can be + used to detect prompts in the stdout which would otherwise cause + the execution to hang (especially if no input data is specified) + :kwarg environ_update: dictionary to *update* os.environ with ''' shell = False @@ -1461,10 +1468,19 @@ class AnsibleModule(object): msg = None st_in = None - # Set a temporart env path if a prefix is passed - env=os.environ + # Manipulate the environ we'll send to the new process + old_env_vals = {} + # We can set this from both an attribute and per call + for key, val in self.run_command_environ_update.items(): + old_env_vals[key] = os.environ.get(key, None) + os.environ[key] = val + if environ_update: + for key, val in environ_update.items(): + old_env_vals[key] = os.environ.get(key, None) + os.environ[key] = val if path_prefix: - env['PATH']="%s:%s" % (path_prefix, env['PATH']) + old_env_vals['PATH'] = os.environ['PATH'] + os.environ['PATH'] = "%s:%s" % (path_prefix, os.environ['PATH']) # create a printable version of the command for use # in reporting later, which strips out things like @@ -1505,11 +1521,10 @@ class AnsibleModule(object): close_fds=close_fds, stdin=st_in, stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, + env=os.environ, ) - if path_prefix: - kwargs['env'] = env if cwd and os.path.isdir(cwd): kwargs['cwd'] = cwd @@ -1578,6 +1593,13 @@ class AnsibleModule(object): except: self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args) + # Restore env settings + for key, val in old_env_vals.items(): + if val is None: + del os.environ[key] + else: + os.environ[key] = val + if rc != 0 and check_rc: msg = heuristic_log_sanitize(stderr.rstrip()) self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg) diff --git a/lib/ansible/modules/core b/lib/ansible/modules/core -Subproject 6e5dd79ae2df107cb196c3e8125399cacfcfb55 +Subproject 8dc285c7942cd0673fc59d117acf86c96fa3871 diff --git a/lib/ansible/modules/extras b/lib/ansible/modules/extras -Subproject 29c3e31a92acd08d5f02a0d5b4ec7a90f0c3774 +Subproject b2e65cdbf4941f2bc3295b766b19e5dbb40a99f diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 2a35102e7b..564f3b4657 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -647,8 +647,7 @@ class Runner(object): # vars_files which had host-specific templating done) inject = utils.combine_vars(inject, self.vars_cache.get(host, {})) # role parameters next - role_params = template.template(self.basedir, self.role_params, inject) - inject = utils.combine_vars(inject, role_params) + inject = utils.combine_vars(inject, self.role_params) # and finally -e vars are the highest priority inject = utils.combine_vars(inject, self.extra_vars) # and then special vars diff --git a/lib/ansible/runner/action_plugins/assemble.py b/lib/ansible/runner/action_plugins/assemble.py index 33a4838e32..171d570a43 100644 --- a/lib/ansible/runner/action_plugins/assemble.py +++ b/lib/ansible/runner/action_plugins/assemble.py @@ -136,6 +136,9 @@ class ActionModule(object): ) module_args_tmp = utils.merge_module_args(module_args, new_module_args) + if self.runner.no_log: + resultant = " [[ Diff output has been hidden because 'no_log: true' was specified for this result ]]" + if self.runner.noop_on_check(inject): return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=dict(before_header=dest, after_header=src, after=resultant)) else: diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index a6a5cb5a27..2c7a634899 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -366,6 +366,11 @@ class ActionModule(object): diff['after_header'] = source diff['after'] = src.read() + if self.runner.no_log: + if 'before' in diff: + diff["before"] = "" + if 'after' in diff: + diff["after"] = " [[ Diff output has been hidden because 'no_log: true' was specified for this result ]]" return diff def _remove_tempfile_if_content_defined(self, content, content_tempfile): diff --git a/lib/ansible/runner/action_plugins/raw.py b/lib/ansible/runner/action_plugins/raw.py index 883f8916cb..11fc7e9ff2 100644 --- a/lib/ansible/runner/action_plugins/raw.py +++ b/lib/ansible/runner/action_plugins/raw.py @@ -34,7 +34,7 @@ class ActionModule(object): # in --check mode, always skip this module execution return ReturnData(conn=conn, comm_ok=True, result=dict(skipped=True)) - executable = '' + executable = None # From library/command, keep in sync r = re.compile(r'(^|\s)(executable)=(?P<quote>[\'"])?(.*?)(?(quote)(?<!\\)(?P=quote))((?<!\\)\s|$)') for m in r.finditer(module_args): diff --git a/lib/ansible/runner/action_plugins/template.py b/lib/ansible/runner/action_plugins/template.py index a824a6e4b8..4a5694592b 100644 --- a/lib/ansible/runner/action_plugins/template.py +++ b/lib/ansible/runner/action_plugins/template.py @@ -117,19 +117,22 @@ class ActionModule(object): # template is different from the remote value - # if showing diffs, we need to get the remote value - dest_contents = '' - + diff = {} if self.runner.diff: # using persist_files to keep the temp directory around to avoid needing to grab another dest_result = self.runner._execute_module(conn, tmp, 'slurp', "path=%s" % dest, inject=inject, persist_files=True) + diff['before'] = "" if 'content' in dest_result.result: dest_contents = dest_result.result['content'] if dest_result.result['encoding'] == 'base64': dest_contents = base64.b64decode(dest_contents) else: raise Exception("unknown encoding, failed: %s" % dest_result.result) - + diff['before'] = dest_contents + diff['before_header'] = dest + diff['after_header'] = source + diff['after'] = resultant + xfered = self.runner._transfer_str(conn, tmp, 'source', resultant) # fix file permissions when the copy is done as a different user @@ -145,12 +148,15 @@ class ActionModule(object): ) module_args_tmp = utils.merge_module_args(module_args, new_module_args) + if self.runner.no_log and self.runner.diff: + diff['before'] = "" + diff['after'] = " [[ Diff output has been hidden because 'no_log: true' was specified for this result ]]" if self.runner.noop_on_check(inject): - return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=dict(before_header=dest, after_header=source, before=dest_contents, after=resultant)) + return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=diff) else: res = self.runner._execute_module(conn, tmp, 'copy', module_args_tmp, inject=inject, complex_args=complex_args) if res.result.get('changed', False): - res.diff = dict(before=dest_contents, after=resultant) + res.diff = diff return res else: # when running the file module based on the template data, we do diff --git a/lib/ansible/runner/action_plugins/win_copy.py b/lib/ansible/runner/action_plugins/win_copy.py index a62dfb9985..476e9ad850 100644 --- a/lib/ansible/runner/action_plugins/win_copy.py +++ b/lib/ansible/runner/action_plugins/win_copy.py @@ -362,6 +362,12 @@ class ActionModule(object): diff['after_header'] = source diff['after'] = src.read() + if self.runner.no_log: + if 'before' in diff: + diff['before'] = "" + if 'after' in diff: + diff["after"] = " [[ Diff output has been hidden because 'no_log: true' was specified for this result ]]" + return diff def _remove_tempfile_if_content_defined(self, content, content_tempfile): diff --git a/lib/ansible/runner/action_plugins/win_template.py b/lib/ansible/runner/action_plugins/win_template.py index 7bde4bd510..b9809ac3eb 100644 --- a/lib/ansible/runner/action_plugins/win_template.py +++ b/lib/ansible/runner/action_plugins/win_template.py @@ -93,18 +93,21 @@ class ActionModule(object): # template is different from the remote value - # if showing diffs, we need to get the remote value - dest_contents = '' - + diff = {} if self.runner.diff: # using persist_files to keep the temp directory around to avoid needing to grab another dest_result = self.runner._execute_module(conn, tmp, 'slurp', "path=%s" % dest, inject=inject, persist_files=True) + diff["before"] = "" if 'content' in dest_result.result: dest_contents = dest_result.result['content'] if dest_result.result['encoding'] == 'base64': dest_contents = base64.b64decode(dest_contents) else: raise Exception("unknown encoding, failed: %s" % dest_result.result) + diff["before"] = dest_contents + diff["before_header"] = dest + diff["after"] = resultant + diff["after_header"] = resultant xfered = self.runner._transfer_str(conn, tmp, 'source', resultant) @@ -121,12 +124,15 @@ class ActionModule(object): ) module_args_tmp = utils.merge_module_args(module_args, new_module_args) + if self.runner.no_log: + diff["before"] = "" + diff["after"] = " [[ Diff output has been hidden because 'no_log: true' was specified for this result ]]" if self.runner.noop_on_check(inject): - return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=dict(before_header=dest, after_header=source, before=dest_contents, after=resultant)) + return ReturnData(conn=conn, comm_ok=True, result=dict(changed=True), diff=diff) else: res = self.runner._execute_module(conn, tmp, 'win_copy', module_args_tmp, inject=inject, complex_args=complex_args) if res.result.get('changed', False): - res.diff = dict(before=dest_contents, after=resultant) + res.diff = diff return res else: # when running the file module based on the template data, we do diff --git a/lib/ansible/runner/connection_plugins/accelerate.py b/lib/ansible/runner/connection_plugins/accelerate.py index 0627267c16..c9a6c43570 100644 --- a/lib/ansible/runner/connection_plugins/accelerate.py +++ b/lib/ansible/runner/connection_plugins/accelerate.py @@ -15,12 +15,12 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. -import json import os import base64 import socket import struct import time +from multiprocessing import Lock from ansible.callbacks import vvv, vvvv from ansible.errors import AnsibleError, AnsibleFileNotFound from ansible.runner.connection_plugins.ssh import Connection as SSHConnection @@ -35,6 +35,8 @@ from ansible import constants # multiple of the value to speed up file reads. CHUNK_SIZE=1044*20 +_LOCK = Lock() + class Connection(object): ''' raw socket accelerated connection ''' @@ -111,6 +113,15 @@ class Connection(object): def connect(self, allow_ssh=True): ''' activates the connection object ''' + # ensure only one fork tries to setup the connection, in case the + # first task for multiple hosts is delegated to the same host. + if not self.is_connected: + with(_LOCK): + return self._connect(allow_ssh) + + return self + + def _connect(self, allow_ssh=True): try: if not self.is_connected: wrong_user = False @@ -150,7 +161,7 @@ class Connection(object): res = self._execute_accelerate_module() if not res.is_successful(): raise AnsibleError("Failed to launch the accelerated daemon on %s (reason: %s)" % (self.host,res.result.get('msg'))) - return self.connect(allow_ssh=False) + return self._connect(allow_ssh=False) else: raise AnsibleError("Failed to connect to %s:%s" % (self.host,self.accport)) self.is_connected = True @@ -231,7 +242,7 @@ class Connection(object): ''' run a command on the remote host ''' if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported: - raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method) + raise AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method) if in_data: raise AnsibleError("Internal Error: this module does not support optimized module pipelining") diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py index 036175f6a9..6c9622da92 100644 --- a/lib/ansible/runner/connection_plugins/ssh.py +++ b/lib/ansible/runner/connection_plugins/ssh.py @@ -91,10 +91,7 @@ class Connection(object): self.common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.private_key_file)] elif self.runner.private_key_file is not None: self.common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.runner.private_key_file)] - if self.password: - self.common_args += ["-o", "GSSAPIAuthentication=no", - "-o", "PubkeyAuthentication=no"] - else: + if not self.password: self.common_args += ["-o", "KbdInteractiveAuthentication=no", "-o", "PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey", "-o", "PasswordAuthentication=no"] diff --git a/lib/ansible/runner/lookup_plugins/csvfile.py b/lib/ansible/runner/lookup_plugins/csvfile.py index ce5a2b77d2..84764bccc2 100644 --- a/lib/ansible/runner/lookup_plugins/csvfile.py +++ b/lib/ansible/runner/lookup_plugins/csvfile.py @@ -16,7 +16,6 @@ # along with Ansible. If not, see <http://www.gnu.org/licenses/>. from ansible import utils, errors -import os import codecs import csv @@ -29,7 +28,7 @@ class LookupModule(object): try: f = codecs.open(filename, 'r', encoding='utf-8') - creader = csv.reader(f, delimiter=delimiter) + creader = csv.reader(f, delimiter=str(delimiter)) for row in creader: if row[0] == key: @@ -72,7 +71,7 @@ class LookupModule(object): path = utils.path_dwim(self.basedir, paramvals['file']) - var = self.read_csv(path, key, paramvals['delimiter'], paramvals['default'], paramvals['col']) + var = self.read_csv(path, key, str(paramvals['delimiter']), paramvals['default'], paramvals['col']) if var is not None: if type(var) is list: for v in var: diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index ffbd8748a7..8e5f1d71f4 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -19,9 +19,7 @@ import errno import sys import re import os -import shlex import yaml -import copy import optparse import operator from ansible import errors @@ -29,8 +27,7 @@ from ansible import __version__ from ansible.utils.display_functions import * from ansible.utils.plugins import * from ansible.utils.su_prompts import * -from ansible.utils.hashing import secure_hash, secure_hash_s, checksum, checksum_s, md5, md5s -from ansible.callbacks import display +from ansible.utils.hashing import secure_hash, secure_hash_s, checksum, checksum_s, md5, md5s #unused here but 'reexported' from ansible.module_utils.splitter import split_args, unquote from ansible.module_utils.basic import heuristic_log_sanitize from ansible.utils.unicode import to_bytes, to_unicode @@ -45,10 +42,11 @@ import pipes import random import difflib import warnings -import traceback import getpass import subprocess import contextlib +import tempfile +from multiprocessing import Lock from vault import VaultLib @@ -62,6 +60,7 @@ LOOKUP_REGEX = re.compile(r'lookup\s*\(') PRINT_CODE_REGEX = re.compile(r'(?:{[{%]|[%}]})') CODE_REGEX = re.compile(r'(?:{%|%})') +_LOCK = Lock() try: # simplejson can be much faster if it's available @@ -127,8 +126,15 @@ def key_for_hostname(hostname): key_path = os.path.expanduser(C.ACCELERATE_KEYS_DIR) if not os.path.exists(key_path): - os.makedirs(key_path, mode=0700) - os.chmod(key_path, int(C.ACCELERATE_KEYS_DIR_PERMS, 8)) + # avoid race with multiple forks trying to create paths on host + # but limit when locking is needed to creation only + with(_LOCK): + if not os.path.exists(key_path): + # use a temp directory and rename to ensure the directory + # searched for only appears after permissions applied. + tmp_dir = tempfile.mkdtemp(dir=os.path.dirname(key_path)) + os.chmod(tmp_dir, int(C.ACCELERATE_KEYS_DIR_PERMS, 8)) + os.rename(tmp_dir, key_path) elif not os.path.isdir(key_path): raise errors.AnsibleError('ACCELERATE_KEYS_DIR is not a directory.') @@ -139,19 +145,25 @@ def key_for_hostname(hostname): # use new AES keys every 2 hours, which means fireball must not allow running for longer either if not os.path.exists(key_path) or (time.time() - os.path.getmtime(key_path) > 60*60*2): - key = AesKey.Generate(size=256) - fd = os.open(key_path, os.O_WRONLY | os.O_CREAT, int(C.ACCELERATE_KEYS_FILE_PERMS, 8)) - fh = os.fdopen(fd, 'w') - fh.write(str(key)) - fh.close() - return key - else: - if stat.S_IMODE(os.stat(key_path).st_mode) != int(C.ACCELERATE_KEYS_FILE_PERMS, 8): - raise errors.AnsibleError('Incorrect permissions on the key file for this host. Use `chmod 0%o %s` to correct this issue.' % (int(C.ACCELERATE_KEYS_FILE_PERMS, 8), key_path)) - fh = open(key_path) - key = AesKey.Read(fh.read()) - fh.close() - return key + # avoid race with multiple forks trying to create key + # but limit when locking is needed to creation only + with(_LOCK): + if not os.path.exists(key_path) or (time.time() - os.path.getmtime(key_path) > 60*60*2): + key = AesKey.Generate() + # use temp file to ensure file only appears once it has + # desired contents and permissions + with tempfile.NamedTemporaryFile(mode='w', dir=os.path.dirname(key_path), delete=False) as fh: + tmp_key_path = fh.name + fh.write(str(key)) + os.chmod(tmp_key_path, int(C.ACCELERATE_KEYS_FILE_PERMS, 8)) + os.rename(tmp_key_path, key_path) + return key + + if stat.S_IMODE(os.stat(key_path).st_mode) != int(C.ACCELERATE_KEYS_FILE_PERMS, 8): + raise errors.AnsibleError('Incorrect permissions on the key file for this host. Use `chmod 0%o %s` to correct this issue.' % (int(C.ACCELERATE_KEYS_FILE_PERMS, 8), key_path)) + + with open(key_path) as fh: + return AesKey.Read(fh.read()) def encrypt(key, msg): return key.Encrypt(msg.encode('utf-8')) @@ -341,9 +353,6 @@ def path_dwim_relative(original, dirname, source, playbook_base, check=True): ''' find one file in a directory one level up in a dir named dirname relative to current ''' # (used by roles code) - from ansible.utils import template - - basedir = os.path.dirname(original) if os.path.islink(basedir): basedir = unfrackpath(basedir) @@ -536,8 +545,6 @@ def _clean_data_struct(orig_data, from_remote=False, from_inventory=False): def parse_json(raw_data, from_remote=False, from_inventory=False, no_exceptions=False): ''' this version for module return data only ''' - orig_data = raw_data - # ignore stuff like tcgetattr spewage or other warnings data = filter_leading_non_json_lines(raw_data) @@ -806,23 +813,27 @@ def merge_hash(a, b): ''' recursively merges hash b into a keys from b take precedence over keys from a ''' - result = {} - # we check here as well as in combine_vars() since this # function can work recursively with nested dicts _validate_both_dicts(a, b) - for dicts in a, b: - # next, iterate over b keys and values - for k, v in dicts.iteritems(): - # if there's already such key in a - # and that key contains dict - if k in result and isinstance(result[k], dict): - # merge those dicts recursively - result[k] = merge_hash(a[k], v) - else: - # otherwise, just copy a value from b to a - result[k] = v + # if a is empty or equal to b, return b + if a == {} or a == b: + return b.copy() + + # if b is empty the below unfolds quickly + result = a.copy() + + # next, iterate over b keys and values + for k, v in b.iteritems(): + # if there's already such key in a + # and that key contains dict + if k in result and isinstance(result[k], dict) and isinstance(v, dict): + # merge those dicts recursively + result[k] = merge_hash(result[k], v) + else: + # otherwise, just copy a value from b to a + result[k] = v return result @@ -1365,6 +1376,14 @@ def safe_eval(expr, locals={}, include_exceptions=False): http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe ''' + # define certain JSON types + # eg. JSON booleans are unknown to python eval() + JSON_TYPES = { + 'false': False, + 'null': None, + 'true': True, + } + # this is the whitelist of AST nodes we are going to # allow in the evaluation. Any node type other than # those listed here will raise an exception in our custom @@ -1428,7 +1447,7 @@ def safe_eval(expr, locals={}, include_exceptions=False): parsed_tree = ast.parse(expr, mode='eval') cnv.visit(parsed_tree) compiled = compile(parsed_tree, expr, 'eval') - result = eval(compiled, {}, locals) + result = eval(compiled, JSON_TYPES, dict(locals)) if include_exceptions: return (result, None) @@ -1485,12 +1504,13 @@ def listify_lookup_plugin_terms(terms, basedir, inject): def combine_vars(a, b): - _validate_both_dicts(a, b) - if C.DEFAULT_HASH_BEHAVIOUR == "merge": return merge_hash(a, b) else: - return dict(a.items() + b.items()) + _validate_both_dicts(a, b) + result = a.copy() + result.update(b) + return result def random_password(length=20, chars=C.DEFAULT_PASSWORD_CHARS): '''Return a random password string of length containing only chars.''' |