summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDag Wieers <dag@wieers.com>2016-02-22 17:04:33 +0100
committerDag Wieers <dag@wieers.com>2016-02-22 17:08:42 +0100
commitccbc849b2094e434df2e6f40b5df81c17659ec8e (patch)
treeeb4821ebdbc8a22225e1332f9bb1394dc9be622c
parentfb442206ca3cc1e9734dd231af005e2af4fee478 (diff)
parentf36896b2bba1399846085cdaca3ecbd1966d9462 (diff)
downloadansible-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.md3
-rw-r--r--lib/ansible/cache/memcached.py4
-rw-r--r--lib/ansible/module_utils/basic.py76
m---------lib/ansible/modules/core0
m---------lib/ansible/modules/extras0
-rw-r--r--lib/ansible/runner/__init__.py3
-rw-r--r--lib/ansible/runner/action_plugins/assemble.py3
-rw-r--r--lib/ansible/runner/action_plugins/copy.py5
-rw-r--r--lib/ansible/runner/action_plugins/raw.py2
-rw-r--r--lib/ansible/runner/action_plugins/template.py18
-rw-r--r--lib/ansible/runner/action_plugins/win_copy.py6
-rw-r--r--lib/ansible/runner/action_plugins/win_template.py16
-rw-r--r--lib/ansible/runner/connection_plugins/accelerate.py17
-rw-r--r--lib/ansible/runner/connection_plugins/ssh.py5
-rw-r--r--lib/ansible/runner/lookup_plugins/csvfile.py5
-rw-r--r--lib/ansible/utils/__init__.py104
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.'''