Compare commits

...

50 Commits

Author SHA1 Message Date
268e72318f Version bump for 1.7.1 2014-08-14 15:50:38 -05:00
48b4805d77 Disallow args: to be specified as a string 2014-08-14 15:41:57 -05:00
7589105c23 Catch error in atomic_move when creating temporary file
Fixes #8480
2014-08-14 15:04:42 -05:00
ef1ae4db86 Load group_vars for new groups loaded via add_host
Fixes #8605
2014-08-14 15:04:38 -05:00
789196b2b8 Use match() instead of search() for pattern matching
Also related to f48fa3, fix for #8614
2014-08-14 15:04:35 -05:00
9f9e975990 Fix host pattern matching and enhance error detection
Fixes #8614
2014-08-14 15:04:31 -05:00
d9fb94378c Fixing variable name bug introduced in ac8950f
Fixes #8617
2014-08-14 15:04:27 -05:00
293b11e45d Fix vault_password positional args error 2014-08-14 15:04:23 -05:00
7a68f4e428 Make sure playbook_dir variable is an absolute path
Fixes #8317
2014-08-14 15:03:42 -05:00
4dcbeed7af Adding extra member to mock inventory for unittests 2014-08-14 15:03:37 -05:00
d6266ba72b Set special vars in play.vars rather than load_vars
Also refactors playbook loading code relating to vars in playbooks
to a sub-function.

Fixes #8534
2014-08-14 15:03:33 -05:00
a096240d17 If ansible and ansible-playbook accept a script for --vault-password-file so should ansible-vault
Conflicts:
	bin/ansible-vault
2014-08-14 15:03:18 -05:00
dcde900e84 Fixing broken patch 20957a1
Also adds integration tests for nested includes with conditionals
2014-08-14 15:02:15 -05:00
d07107ab71 Remove duplicate compiling of task when: statements
Fixes #8538
2014-08-14 15:02:12 -05:00
165572fbb8 Need to check e.errno instead of os.errno. Fixes #8548 2014-08-14 15:02:08 -05:00
df136769eb Make sure we only use unquote on quoted lines in lineinfile when needed 2014-08-14 15:02:04 -05:00
f1f88f8904 Unquote lines that were quoted for safety during eval in lineinfile
Fixes #8535
2014-08-14 15:02:00 -05:00
38123579fb Catch task parameter splitting errors nicely
Fixes #8481
2014-08-14 15:01:56 -05:00
da08e62017 Correct parser to ignore escaped quotes when not in quotes already
Related to #8481
2014-08-14 15:01:51 -05:00
bb1ca99db7 Pre-load whole string and use seek to alter tags 2014-08-14 15:01:46 -05:00
27223b1253 Switch cStringIO to StringIO for unicode support
The performance difference isn't too bad
2014-08-14 15:01:39 -05:00
6f8498d0b1 Test escaping strings with two variables 2014-08-14 15:01:35 -05:00
08c968a999 Fix copy/paste error 2014-08-14 15:01:32 -05:00
f0270bb289 Optimise string handling in ansible.utils._clean_data 2014-08-14 15:01:28 -05:00
5ae0bb176a Issue 8502: Fixed bug where failed when permissions weren't specified. 2014-08-14 15:01:24 -05:00
cd643f0d32 Add validate_certs option to apt_repository 2014-08-14 15:01:19 -05:00
3cf0f27f7b Preserve all line breaks in literal blocks
Fixes #8512
2014-08-14 15:01:15 -05:00
7f5bf63a9b InventoryDir: fixing yet another corner case 2014-08-14 15:01:11 -05:00
ba82d3e423 Fix missing variable initialization for async jid
Fixes #8491
2014-08-14 15:01:07 -05:00
be76296067 Implements the proper method for terminated an auto scale group. 2014-08-14 15:01:02 -05:00
adcd53c67e Make sure default vars are used in template calls for tasks
Fixes #8499
2014-08-14 15:00:57 -05:00
4eb5faa1b5 Include *.ps1 files in the library for installation
Fixes #8490
2014-08-14 15:00:53 -05:00
a77674145f Catch auth errors in winrm and properly raise errors
Fixes #7875
2014-08-14 15:00:49 -05:00
1dd484cc2d execute the mount after path validation
This avoids a stale situation where name/path contains some impossible path,
but gets configured (faultly) in fstab, and the module only fails after that,
when creating that path.
2014-08-14 15:00:45 -05:00
3121008475 Fixes 8362, returns stderr if No space left on device 2014-08-14 15:00:39 -05:00
ce28176fa2 apache2_module handles multiple lines of output, fixes #7736 and #7857
Conflicts:
	library/web_infrastructure/apache2_module
2014-08-14 15:00:21 -05:00
2b9d439b03 Throw an exception for a missing <file_name> param in ansible-vault
Without this fix you have to enter your vault password before you realize
that you forgot to pass in the filename.  This commit checks that an
filename argument was at least passed on the command line.
2014-08-14 14:59:23 -05:00
29ff0df4b9 Hack to fix #7738: Allow tag in image parameter of docker module
It also defaults to the "latest" tag for pulling the image
instead of pulling all versions.
2014-08-14 14:59:18 -05:00
edb08b56fc fix incorrect secondary ip handling, fixes #7724 2014-08-14 14:59:13 -05:00
c783c6754b fix spacing 2014-08-14 14:59:08 -05:00
c32e1961c4 fix typo 2014-08-14 14:59:01 -05:00
67821f0122 support rw bind mode in docker module 2014-08-14 14:58:56 -05:00
e2ea7b3297 apache2_module: Handle dep resolution
The apache2_module module did not properly handle when a2enmod would
handle apache module dependancies.  It would always return a state of
changed.  I've updated the regular expression to properly parse that
output as well as the normal output.  A good example of this is the
mod_proxy_http module.
2014-08-14 14:58:50 -05:00
f961082c05 Fix parameters check for ec2_vol
Split the error check into two error checks and delay the
checks so that listing existing volumes works more nicely.

The error check should check that:
* One and only one of volume_size id or name is set

This fix adds the 'only one' part of that check and provides
more useful error messages.
2014-08-14 14:58:45 -05:00
5c00381e26 set type and default value 2014-08-14 14:58:38 -05:00
2912793087 Removing leftover import of atomic_move 2014-08-06 15:37:24 -05:00
9b5619d741 Revert "Atomically move known hosts file into place when edited by paramiko connections"
This reverts commit bd49a835b4.
2014-08-06 15:32:31 -05:00
92382c4181 Add path checking for relative/escaped tar filenames in the ansible-galaxy command 2014-08-06 13:25:36 -05:00
650e967b30 Avoid templating raw lookup strings 2014-08-06 13:25:06 -05:00
953a2dfb8a Back-porting in CHANGELOG/etc. for older versions 2014-08-06 13:22:32 -05:00
41 changed files with 352 additions and 193 deletions

View File

@ -1,7 +1,19 @@
Ansible Changes By Release
==========================
## 1.7 "Summer Nights" - Active Development
## 1.8 "You Really Got Me" - Active Development
## 1.7.1 "Summer Nights" - Aug 14, 2014
- Security fix to disallow specifying 'args:' as a string, which could allow the insertion of extra module parameters through variables.
- Performance enhancements related to previous security fixes, which could cause slowness when modules returned very large JSON results. This specifically impacted the unarchive module frequently, which returns the details of all unarchived files in the result.
- Docker module bug fixes:
* Fixed support for specifying rw/ro bind modes for volumes
* Fixed support for allowing the tag in the image parameter
- Various other bug fixes
## 1.7 "Summer Nights" - Aug 06, 2014
Major new features:
@ -34,6 +46,16 @@ Other notable changes:
* Inventory speed improvements for very large inventories.
* Vault password files can now be executable, to support scripts that fetch the vault password.
## 1.6.10 "And the Cradle Will Rock" - Jul 25, 2014
- Fixes an issue with the copy module when copying a directory that fails when changing file attributes and the target file already exists
- Improved unicode handling when splitting args
## 1.6.9 "And the Cradle Will Rock" - Jul 24, 2014
- Further improvements to module parameter parsing to address additional regressions caused by security fixes
## 1.6.8 "And the Cradle Will Rock" - Jul 22, 2014
- Corrects a regression in the way shell and command parameters were being parsed

View File

@ -3,7 +3,7 @@ include examples/hosts
include examples/ansible.cfg
graft examples/playbooks
include packaging/distutils/setup.py
include lib/ansible/module_common/*.ps1
include lib/ansible/module_utils/powershell.ps1
recursive-include docs *
recursive-include library *
include Makefile

View File

@ -4,8 +4,12 @@ Ansible Releases at a Glance
Active Development
++++++++++++++++++
1.7 "Summer Nights" -------- SUMMER 2014
1.8 "You Really Got Me" ---- FALL 2014
1.7.1 "Summer Nights" -------- 08-14-2014
1.7 "Summer Nights" -------- 08-06-2014
1.6.10 "The Cradle Will Rock" - 07-25-2014
1.6.9 "The Cradle Will Rock" - 07-24-2014
1.6.8 "The Cradle Will Rock" - 07-22-2014
1.6.7 "The Cradle Will Rock" - 07-21-2014
1.6.6 "The Cradle Will Rock" - 07-01-2014

View File

@ -1 +1 @@
1.7
1.7.1

View File

@ -445,6 +445,7 @@ def install_role(role_name, role_version, role_filename, options):
# verify the role's meta file
meta_file = None
members = role_tar_file.getmembers()
# next find the metadata file
for member in members:
if "/meta/main.yml" in member.name:
meta_file = member
@ -484,9 +485,16 @@ def install_role(role_name, role_version, role_filename, options):
# now we do the actual extraction to the role_path
for member in members:
# we only extract files
# we only extract files, and remove any relative path
# bits that might be in the file for security purposes
# and drop the leading directory, as mentioned above
if member.isreg():
member.name = "/".join(member.name.split("/")[1:])
parts = member.name.split("/")[1:]
final_parts = []
for part in parts:
if part != '..' and '~' not in part and '$' not in part:
final_parts.append(part)
member.name = os.path.join(*final_parts)
role_tar_file.extract(member, role_path)
# write out the install info file for later use

View File

@ -27,6 +27,8 @@ import os
import sys
import traceback
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible.utils.vault import VaultEditor
@ -58,7 +60,7 @@ def build_option_parser(action):
#parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
parser.add_option('--debug', dest='debug', action="store_true", help="debug")
parser.add_option('--vault-password-file', dest='password_file',
help="vault password file")
help="vault password file", default=C.DEFAULT_VAULT_PASSWORD_FILE)
# options specific to actions
if action == "create":
@ -104,22 +106,14 @@ def get_opt(options, k, defval=""):
# Command functions
#-------------------------------------------------------------------------------------
def _read_password(filename):
f = open(filename, "rb")
data = f.read()
f.close()
data = data.strip()
return data
def execute_create(args, options, parser):
if len(args) > 1:
raise errors.AnsibleError("'create' does not accept more than one filename")
if not options.password_file:
if not options.password_file:
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
else:
password = _read_password(options.password_file)
password = utils.read_vault_file(options.password_file)
cipher = 'AES256'
if hasattr(options, 'cipher'):
@ -130,10 +124,10 @@ def execute_create(args, options, parser):
def execute_decrypt(args, options, parser):
if not options.password_file:
if not options.password_file:
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
else:
password = _read_password(options.password_file)
password = utils.read_vault_file(options.vault_password_file)
cipher = 'AES256'
if hasattr(options, 'cipher'):
@ -150,10 +144,10 @@ def execute_edit(args, options, parser):
if len(args) > 1:
raise errors.AnsibleError("create does not accept more than one filename")
if not options.password_file:
if not options.password_file:
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
else:
password = _read_password(options.password_file)
password = utils.read_vault_file(options.password_file)
cipher = None
@ -163,10 +157,10 @@ def execute_edit(args, options, parser):
def execute_encrypt(args, options, parser):
if not options.password_file:
if not options.password_file:
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
else:
password = _read_password(options.password_file)
password = utils.read_vault_file(options.password_file)
cipher = 'AES256'
if hasattr(options, 'cipher'):
@ -180,10 +174,10 @@ def execute_encrypt(args, options, parser):
def execute_rekey(args, options, parser):
if not options.password_file:
if not options.password_file:
password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
else:
password = _read_password(options.password_file)
password = utils.read_vault_file(options.password_file)
__, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
@ -204,6 +198,11 @@ def main():
parser = build_option_parser(action)
(options, args) = parser.parse_args()
if not len(args):
raise errors.AnsibleError(
"The '%s' command requires a filename as the first argument" % action
)
# execute the desired action
try:
fn = globals()["execute_%s" % action]
@ -216,4 +215,3 @@ def main():
if __name__ == "__main__":
main()

View File

@ -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.7'
__version__ = '1.7.1'
__author__ = 'Michael DeHaan'

View File

@ -142,27 +142,34 @@ class Inventory(object):
# get group vars from group_vars/ files and vars plugins
for group in self.groups:
group.vars = utils.combine_vars(group.vars, self.get_group_variables(group.name, self._vault_password))
group.vars = utils.combine_vars(group.vars, self.get_group_variables(group.name, vault_password=self._vault_password))
# get host vars from host_vars/ files and vars plugins
for host in self.get_hosts():
host.vars = utils.combine_vars(host.vars, self.get_variables(host.name, self._vault_password))
host.vars = utils.combine_vars(host.vars, self.get_variables(host.name, vault_password=self._vault_password))
def _match(self, str, pattern_str):
if pattern_str.startswith('~'):
return re.search(pattern_str[1:], str)
else:
return fnmatch.fnmatch(str, pattern_str)
try:
if pattern_str.startswith('~'):
return re.search(pattern_str[1:], str)
else:
return fnmatch.fnmatch(str, pattern_str)
except Exception, e:
raise errors.AnsibleError('invalid host pattern: %s' % pattern_str)
def _match_list(self, items, item_attr, pattern_str):
results = []
if not pattern_str.startswith('~'):
pattern = re.compile(fnmatch.translate(pattern_str))
else:
pattern = re.compile(pattern_str[1:])
try:
if not pattern_str.startswith('~'):
pattern = re.compile(fnmatch.translate(pattern_str))
else:
pattern = re.compile(pattern_str[1:])
except Exception, e:
raise errors.AnsibleError('invalid host pattern: %s' % pattern_str)
for item in items:
if pattern.search(getattr(item, item_attr)):
if pattern.match(getattr(item, item_attr)):
results.append(item)
return results

View File

@ -113,7 +113,7 @@ class InventoryDirectory(object):
for group in allgroup.child_groups[:]:
# groups might once have beeen added to all, and later be added
# to another group: we need to remove the link wit all then
if len(group.parent_groups) > 1:
if len(group.parent_groups) > 1 and allgroup in group.parent_groups:
# real children of all have just 1 parent, all
# this one has more, so not a direct child of all anymore
group.parent_groups.remove(allgroup)

View File

@ -1130,8 +1130,11 @@ class AnsibleModule(object):
dest_dir = os.path.dirname(dest)
dest_file = os.path.basename(dest)
tmp_dest = tempfile.NamedTemporaryFile(
prefix=".ansible_tmp", dir=dest_dir, suffix=dest_file)
try:
tmp_dest = tempfile.NamedTemporaryFile(
prefix=".ansible_tmp", dir=dest_dir, suffix=dest_file)
except (OSError, IOError), e:
self.fail_json(msg='The destination directory (%s) is not writable by the current user.' % dest_dir)
try: # leaves tmp file behind when sudo and not root
if switched_user and os.getuid() != 0:

View File

@ -1581,7 +1581,7 @@ class LinuxNetwork(Network):
iface = words[-1]
if iface != device:
interfaces[iface] = {}
if not secondary or "ipv4" not in interfaces[iface]:
if not secondary and "ipv4" not in interfaces[iface]:
interfaces[iface]['ipv4'] = {'address': address,
'netmask': netmask,
'network': network}

View File

@ -26,9 +26,9 @@ def _get_quote_state(token, quote_char):
for idx, cur_char in enumerate(token):
if idx > 0:
prev_char = token[idx-1]
if cur_char in '"\'':
if cur_char in '"\'' and prev_char != '\\':
if quote_char:
if cur_char == quote_char and prev_char != '\\':
if cur_char == quote_char:
quote_char = None
else:
quote_char = cur_char
@ -101,15 +101,22 @@ def split_args(args):
# inside quotation characters
tokens = item.split('\n')
line_continuation = False
for idx,token in enumerate(tokens):
# if we're at the end of the enumeration, the character separator
# used when reassembling quoted bits should be a space, otherwise
# it will be a newline character
# if there was a newline split, we will re-assemble using
# newlines, otherwise we re-assemble using spaces
spacer = ' '
if idx > 0:
spacer = '\n'
# if we hit a line continuation character, but
# we're not inside quotes, ignore it and continue
# on to the next token while setting a flag
if token == '\\' and not inside_quotes:
line_continuation = True
continue
# store the previous quoting state for checking later
was_inside_quotes = inside_quotes
quote_char = _get_quote_state(token, quote_char)
@ -155,7 +162,19 @@ def split_args(args):
# finally, if we're at zero depth for all blocks and not inside quotes, and have not
# yet appended anything to the list of params, we do so now
if not (print_depth or block_depth or comment_depth) and not inside_quotes and not appended and token != '':
params.append(token)
# if the spacer was a newline, prepend this param
# with it so that newlines are preserved, unless
# we previously hit a line continuation character
if spacer == '\n':
if line_continuation:
params.append(token)
else:
params.append('\n%s' % token)
else:
params.append(token)
# always clear the line continuation flag
line_continuation = False
# If we're done and things are not at zero depth or we're still inside quotes,
# raise an error to indicate that the args were unbalanced
@ -165,11 +184,15 @@ def split_args(args):
# finally, we decode each param back to the unicode it was in the arg string
if do_decode:
params = [x.decode('utf-8') for x in params]
return params
def is_quoted(data):
return len(data) > 0 and (data[0] == '"' and data[-1] == '"' or data[0] == "'" and data[-1] == "'")
def unquote(data):
''' removes first and last quotes from a string, if the string starts and ends with the same quotes '''
if len(data) > 0 and (data[0] == '"' and data[-1] == '"' or data[0] == "'" and data[-1] == "'"):
if is_quoted(data):
return data[1:-1]
return data

View File

@ -169,7 +169,7 @@ class PlayBook(object):
self.inventory.set_playbook_basedir(self.basedir)
vars = extra_vars.copy()
vars['playbook_dir'] = self.basedir
vars['playbook_dir'] = os.path.abspath(self.basedir)
if self.inventory.basedir() is not None:
vars['inventory_dir'] = self.inventory.basedir()
@ -222,12 +222,34 @@ class PlayBook(object):
# *****************************************************
def _extend_play_vars(self, play, vars={}):
'''
Extends the given play's variables with the additional specified vars.
'''
if 'vars' not in play or not play['vars']:
# someone left out or put an empty "vars:" entry in their playbook
return vars.copy()
play_vars = None
if isinstance(play['vars'], dict):
play_vars = play['vars'].copy()
play_vars.update(vars)
elif isinstance(play['vars'], list):
# nobody should really do this, but handle vars: a=1 b=2
play_vars = play['vars'][:]
play_vars.extend([{k:v} for k,v in vars.iteritems()])
return play_vars
# *****************************************************
def _load_playbook_from_file(self, path, vars={}, vars_files=[]):
'''
run top level error checking on playbooks and allow them to include other playbooks.
'''
playbook_data = utils.parse_yaml_from_file(path, vault_password=self.vault_password)
playbook_data = utils.parse_yaml_from_file(path, vault_password=self.vault_password)
accumulated_plays = []
play_basedirs = []
@ -254,17 +276,7 @@ class PlayBook(object):
for p in plays:
# support for parameterized play includes works by passing
# those variables along to the subservient play
if 'vars' not in p:
p['vars'] = {}
if isinstance(p['vars'], dict):
p['vars'].update(play_vars)
elif isinstance(p['vars'], list):
# nobody should really do this, but handle vars: a=1 b=2
p['vars'].extend([{k:v} for k,v in play_vars.iteritems()])
elif p['vars'] == None:
# someone specified an empty 'vars:', so reset
# it to the vars we currently have
p['vars'] = play_vars.copy()
p['vars'] = self._extend_play_vars(p, play_vars)
# now add in the vars_files
p['vars_files'] = utils.list_union(p.get('vars_files', []), play_vars_files)

View File

@ -75,6 +75,13 @@ class Play(object):
elif type(self.tags) != list:
self.tags = []
# make sure we have some special internal variables set
self.vars['playbook_dir'] = os.path.abspath(self.basedir)
if self.playbook.inventory.basedir() is not None:
self.vars['inventory_dir'] = self.playbook.inventory.basedir()
if self.playbook.inventory.src() is not None:
self.vars['inventory_file'] = self.playbook.inventory.src()
# 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', [])
@ -146,9 +153,6 @@ class Play(object):
load_vars = {}
load_vars['role_names'] = ds.get('role_names',[])
load_vars['playbook_dir'] = self.basedir
if self.playbook.inventory.basedir() is not None:
load_vars['inventory_dir'] = self.playbook.inventory.basedir()
self._tasks = self._load_tasks(self._ds.get('tasks', []), load_vars)
self._handlers = self._load_tasks(self._ds.get('handlers', []), load_vars)
@ -538,11 +542,10 @@ class Play(object):
elif k.startswith("when_"):
utils.deprecated("\"when_<criteria>:\" is a removed deprecated feature, use the simplified 'when:' conditional directly", None, removed=True)
elif k == 'when':
if type(x[k]) is str:
included_additional_conditions.insert(0, x[k])
if isinstance(x[k], (basestring, bool)):
included_additional_conditions.append(x[k])
elif type(x[k]) is list:
for i in x[k]:
included_additional_conditions.insert(0, i)
included_additional_conditions.extend(x[k])
elif k in ("include", "vars", "default_vars", "sudo", "sudo_user", "role_name", "no_log"):
continue
else:
@ -561,12 +564,6 @@ class Play(object):
if 'vars' in x:
task_vars = utils.combine_vars(task_vars, x['vars'])
if 'when' in x:
if isinstance(x['when'], (basestring, bool)):
included_additional_conditions.append(x['when'])
elif isinstance(x['when'], list):
included_additional_conditions.extend(x['when'])
new_role = None
if 'role_name' in x:
new_role = x['role_name']

View File

@ -208,11 +208,15 @@ class Task(object):
self.changed_when = ds.get('changed_when', None)
self.failed_when = ds.get('failed_when', None)
# combine the default and module vars here for use in templating
all_vars = self.default_vars.copy()
all_vars = utils.combine_vars(all_vars, self.module_vars)
self.async_seconds = ds.get('async', 0) # not async by default
self.async_seconds = template.template_from_string(play.basedir, self.async_seconds, self.module_vars)
self.async_seconds = template.template_from_string(play.basedir, self.async_seconds, all_vars)
self.async_seconds = int(self.async_seconds)
self.async_poll_interval = ds.get('poll', 10) # default poll = 10 seconds
self.async_poll_interval = template.template_from_string(play.basedir, self.async_poll_interval, self.module_vars)
self.async_poll_interval = template.template_from_string(play.basedir, self.async_poll_interval, all_vars)
self.async_poll_interval = int(self.async_poll_interval)
self.notify = ds.get('notify', [])
self.first_available_file = ds.get('first_available_file', None)
@ -235,7 +239,14 @@ class Task(object):
self.notify = [ self.notify ]
# split the action line into a module name + arguments
tokens = split_args(self.action)
try:
tokens = split_args(self.action)
except Exception, e:
if "unbalanced" in str(e):
raise errors.AnsibleError("There was an error while parsing the task %s.\n" % repr(self.action) + \
"Make sure quotes are matched or escaped properly")
else:
raise
if len(tokens) < 1:
raise errors.AnsibleError("invalid/missing action in task. name: %s" % self.name)
self.module_name = tokens[0]
@ -289,6 +300,7 @@ class Task(object):
self.tags.extend(import_tags)
if additional_conditions:
new_conditions = additional_conditions
new_conditions.append(self.when)
new_conditions = additional_conditions[:]
if self.when:
new_conditions.append(self.when)
self.when = new_conditions

View File

@ -215,6 +215,10 @@ class Runner(object):
# changed later via options like accelerate
self.original_transport = self.transport
# enforce complex_args as a dict
if type(self.complex_args) != dict:
raise errors.AnsibleError("args must be a dictionary, received %s (%s)" % (self.complex_args, type(self.complex_args)))
# misc housekeeping
if subset and self.inventory._subset is None:
# don't override subset when passed from playbook
@ -609,7 +613,7 @@ class Runner(object):
inject['vars'] = self.module_vars
inject['defaults'] = self.default_vars
inject['environment'] = self.environment
inject['playbook_dir'] = self.basedir
inject['playbook_dir'] = os.path.abspath(self.basedir)
# template this one is available, callbacks use this
delegate_to = self.module_vars.get('delegate_to')
@ -659,11 +663,6 @@ class Runner(object):
# logic to decide how to run things depends on whether with_items is used
if items is None:
if isinstance(complex_args, basestring):
complex_args = template.template(self.basedir, complex_args, inject, convert_bare=True)
complex_args = utils.safe_eval(complex_args)
if type(complex_args) != dict:
raise errors.AnsibleError("args must be a dictionary, received %s" % complex_args)
return self._executor_internal_inner(host, self.module_name, self.module_args, inject, port, complex_args=complex_args)
elif len(items) > 0:
@ -1095,6 +1094,8 @@ class Runner(object):
output = 'SSH encountered an unknown error. The output was:\n%s' % (result['stdout']+result['stderr'])
else:
output = 'SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue'
elif 'No space left on device' in result['stderr']:
output = result['stderr']
else:
output = 'Authentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in "/tmp". Failed command was: %s, exited with result %d' % (cmd, result['rc'])
if 'stdout' in result and result['stdout'] != '':

View File

@ -83,6 +83,7 @@ class ActionModule(object):
if not inventory.get_group(group_name):
new_group = Group(group_name)
inventory.add_group(new_group)
new_group.vars = inventory.get_group_variables(group_name, vault_password=inventory._vault_password)
grp = inventory.get_group(group_name)
grp.add_host(new_host)

View File

@ -50,7 +50,7 @@ class ActionModule(object):
result = dict(failed=True, msg=args['msg'])
else:
result = dict(msg=args['msg'])
elif 'var' in args:
elif 'var' in args and not utils.LOOKUP_REGEX.search(args['var']):
results = template.template(self.basedir, "{{ %s }}" % args['var'], inject)
result[args['var']] = results

View File

@ -29,7 +29,6 @@ import pipes
import socket
import random
import logging
import tempfile
import traceback
import fcntl
import re
@ -381,25 +380,7 @@ class Connection(object):
self.ssh.load_system_host_keys()
self.ssh._host_keys.update(self.ssh._system_host_keys)
# gather information about the current key file, so
# we can ensure the new file has the correct mode/owner
key_dir = os.path.dirname(self.keyfile)
key_stat = os.stat(self.keyfile)
# Save the new keys to a temporary file and move it into place
# rather than rewriting the file. We set delete=False because
# the file will be moved into place rather than cleaned up.
tmp_keyfile = tempfile.NamedTemporaryFile(dir=key_dir, delete=False)
os.chmod(tmp_keyfile.name, key_stat.st_mode & 07777)
os.chown(tmp_keyfile.name, key_stat.st_uid, key_stat.st_gid)
self._save_ssh_host_keys(tmp_keyfile.name)
tmp_keyfile.close()
os.rename(tmp_keyfile.name, self.keyfile)
self._save_ssh_host_keys(self.keyfile)
except:

View File

@ -89,17 +89,19 @@ class Connection(object):
except WinRMTransportError, exc:
err_msg = str(exc.args[0])
if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I):
raise
raise errors.AnsibleError("the connection attempt timed out")
m = re.search(r'Code\s+?(\d{3})', err_msg)
if m:
code = int(m.groups()[0])
if code == 411:
if code == 401:
raise errors.AnsibleError("the username/password specified for this server was incorrect")
elif code == 411:
_winrm_cache[cache_key] = protocol
return protocol
vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host)
continue
if exc:
raise exc
raise errors.AnsibleError(str(exc))
def _winrm_exec(self, command, args=(), from_exec=False):
if from_exec:

View File

@ -34,6 +34,7 @@ class AsyncPoller(object):
self.active = False
# True to work with & below
skipped = True
jid = None
for (host, res) in results['contacted'].iteritems():
if res.get('started', False):
self.hosts_to_poll.append(host)

View File

@ -46,6 +46,7 @@ import getpass
import sys
import json
import subprocess
import contextlib
from vault import VaultLib
@ -55,7 +56,9 @@ MAX_FILE_SIZE_FOR_DIFF=1*1024*1024
# caching the compilation of the regex used
# to check for lookup calls within data
LOOKUP_REGEX=re.compile(r'lookup\s*\(')
LOOKUP_REGEX = re.compile(r'lookup\s*\(')
PRINT_CODE_REGEX = re.compile(r'(?:{[{%]|[%}]})')
CODE_REGEX = re.compile(r'(?:{%|%})')
try:
import json
@ -355,64 +358,48 @@ def _clean_data(orig_data, from_remote=False, from_inventory=False):
if not isinstance(orig_data, basestring):
return orig_data
data = StringIO.StringIO("")
# when the data is marked as having come from a remote, we always
# replace any print blocks (ie. {{var}}), however when marked as coming
# from inventory we only replace print blocks that contain a call to
# a lookup plugin (ie. {{lookup('foo','bar'))}})
replace_prints = from_remote or (from_inventory and '{{' in orig_data and LOOKUP_REGEX.search(orig_data) is not None)
# these variables keep track of opening block locations, as we only
# want to replace matched pairs of print/block tags
print_openings = []
block_openings = []
regex = PRINT_CODE_REGEX if replace_prints else CODE_REGEX
with contextlib.closing(StringIO.StringIO(orig_data)) as data:
# these variables keep track of opening block locations, as we only
# want to replace matched pairs of print/block tags
print_openings = []
block_openings = []
for mo in regex.finditer(orig_data):
token = mo.group(0)
token_start = mo.start(0)
if token[0] == '{':
if token == '{%':
block_openings.append(token_start)
elif token == '{{':
print_openings.append(token_start)
elif token[1] == '}':
prev_idx = None
if token == '%}' and block_openings:
prev_idx = block_openings.pop()
elif token == '}}' and print_openings:
prev_idx = print_openings.pop()
if prev_idx is not None:
# replace the opening
data.seek(prev_idx, os.SEEK_SET)
data.write('{#')
# replace the closing
data.seek(token_start, os.SEEK_SET)
data.write('#}')
for idx,c in enumerate(orig_data):
# if the current character is an opening brace, check to
# see if this is a jinja2 token. Otherwise, if the current
# character is a closing brace, we backup one character to
# see if we have a closing.
if c == '{' and idx < len(orig_data) - 1:
token = orig_data[idx:idx+2]
# if so, and we want to replace this block, push
# this token's location onto the appropriate array
if token == '{{' and replace_prints:
print_openings.append(idx)
elif token == '{%':
block_openings.append(idx)
# finally we write the data to the buffer and write
data.seek(0, os.SEEK_END)
data.write(c)
elif c == '}' and idx > 0:
token = orig_data[idx-1:idx+1]
prev_idx = -1
if token == '%}' and len(block_openings) > 0:
prev_idx = block_openings.pop()
elif token == '}}' and len(print_openings) > 0:
prev_idx = print_openings.pop()
# if we have a closing token, and we have previously found
# the opening to the same kind of block represented by this
# token, replace both occurrences, otherwise we just write
# the current character to the buffer
if prev_idx != -1:
# replace the opening
data.seek(prev_idx, os.SEEK_SET)
data.write('{#')
# replace the closing
data.seek(-1, os.SEEK_END)
data.write('#}')
else:
data.seek(0, os.SEEK_END)
data.write(c)
else:
# not a jinja2 token, so we just write the current char
# to the output buffer
data.seek(0, os.SEEK_END)
data.write(c)
return_data = data.getvalue()
data.close()
return return_data
assert False, 'Unhandled regex match'
return data.getvalue()
def _clean_data_struct(orig_data, from_remote=False, from_inventory=False):
'''
@ -704,7 +691,7 @@ def parse_kv(args):
for x in vargs:
if "=" in x:
k, v = x.split("=",1)
options[k] = unquote(v.strip())
options[k.strip()] = unquote(v.strip())
return options
def merge_hash(a, b):
@ -1257,7 +1244,10 @@ def listify_lookup_plugin_terms(terms, basedir, inject):
# with_items: {{ alist }}
stripped = terms.strip()
if not (stripped.startswith('{') or stripped.startswith('[')) and not stripped.startswith("/") and not stripped.startswith('set(['):
if not (stripped.startswith('{') or stripped.startswith('[')) and \
not stripped.startswith("/") and \
not stripped.startswith('set([') and \
not LOOKUP_REGEX.search(terms):
# if not already a list, get ready to evaluate with Jinja2
# not sure why the "/" is in above code :)
try:

View File

@ -358,6 +358,13 @@ class DockerManager:
if len(parts) == 2:
self.volumes[parts[1]] = {}
self.binds[parts[0]] = parts[1]
# with bind mode
elif len(parts) == 3:
if parts[2] not in ['ro', 'rw']:
self.module.fail_json(msg='bind mode needs to either be "ro" or "rw"')
ro = parts[2] == 'ro'
self.volumes[parts[1]] = {}
self.binds[parts[0]] = {'bind': parts[1], 'ro': ro}
# docker mount (e.g. /www, mounts a docker volume /www on the container at the same location)
else:
self.volumes[parts[0]] = {}
@ -477,6 +484,13 @@ class DockerManager:
else:
return image, tag
def get_split_full_image_tag(self, image):
tag = "latest"
if image.find(':') > 0:
return image.split(':')
else:
return image, tag
def get_summary_counters_msg(self):
msg = ""
for k, v in self.counters.iteritems():
@ -576,7 +590,8 @@ class DockerManager:
try:
containers = do_create(count, params)
except:
self.client.pull(params['image'])
image, tag = self.get_split_full_image_tag(params['image'])
self.client.pull(image, tag = tag)
self.increment_counter('pull')
containers = do_create(count, params)

View File

@ -1134,7 +1134,7 @@ def main():
exact_count = dict(type='int', default=None),
count_tag = dict(),
volumes = dict(type='list'),
ebs_optimized = dict(),
ebs_optimized = dict(type='bool', default=False),
)
)

View File

@ -283,15 +283,17 @@ def delete_autoscaling_group(connection, module):
groups = connection.get_all_groups(names=[group_name])
if groups:
group = groups[0]
group.shutdown_instances()
group.max_size = 0
group.min_size = 0
group.desired_capacity = 0
group.update()
instances = True
while instances:
groups = connection.get_all_groups()
for group in groups:
if group.name == group_name:
if not group.instances:
instances = False
tmp_groups = connection.get_all_groups(names=[group_name])
if tmp_groups:
tmp_group = tmp_groups[0]
if not tmp_group.instances:
instances = False
time.sleep(10)
group.delete()

View File

@ -325,9 +325,6 @@ def main():
if id and name:
module.fail_json(msg="Both id and name cannot be specified")
if not (id or name or volume_size):
module.fail_json(msg="Cannot specify volume_size and either one of name or id")
# Here we need to get the zone info for the instance. This covers situation where
# instance is specified but zone isn't.
# Useful for playbooks chaining instance launch with volume create + attach and where the
@ -345,6 +342,15 @@ def main():
device=device_name,
changed=False)
# Delaying the checks until after the instance check allows us to get volume ids for existing volumes
# without needing to pass an unused volume_size
if not volume_size and not (id or name):
module.fail_json(msg="You must specify an existing volume with id or name or a volume_size")
if volume_size and (id or name):
module.fail_json(msg="Cannot specify volume_size and either one of name or id")
if state == 'absent':
delete_volume(module, ec2)
else:

View File

@ -211,11 +211,14 @@ def main():
entry = module.params.get('entry')
entity = module.params.get('entity')
etype = module.params.get('etype')
permissions = normalize_permissions(module.params.get('permissions'))
permissions = module.params.get('permissions')
state = module.params.get('state')
follow = module.params.get('follow')
default = module.params.get('default')
if permissions:
permissions = normalize_permissions(permissions)
if not os.path.exists(path):
module.fail_json(msg="path not found or not accessible!")

View File

@ -363,15 +363,23 @@ def main():
if ins_bef is None and ins_aft is None:
ins_aft = 'EOF'
line = params['line']
# The safe_eval call will remove some quoting, but not others,
# so we need to know if we should specifically unquote it.
should_unquote = not is_quoted(line)
# Replace escape sequences like '\n' while being sure
# not to replace octal escape sequences (\ooo) since they
# match the backref syntax
# match the backref syntax.
if backrefs:
line = re.sub(r'(\\[0-9]{1,3})', r'\\\1', params['line'])
else:
line = params['line']
line = re.sub(r'(\\[0-9]{1,3})', r'\\\1', line)
line = module.safe_eval(pipes.quote(line))
# Now remove quotes around the string, if needed
if should_unquote:
line = unquote(line)
present(module, dest, params['regexp'], line,
ins_aft, ins_bef, create, backup, backrefs)
else:
@ -382,5 +390,6 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.splitter import *
main()

View File

@ -55,6 +55,13 @@ options:
required: false
default: "yes"
choices: [ "yes", "no" ]
validate_certs:
description:
- If C(no), SSL certificates for the target repo will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
required: false
default: 'yes'
choices: ['yes', 'no']
author: Alexander Saltanov
version_added: "0.7"
requirements: [ python-apt ]
@ -377,6 +384,7 @@ def main():
update_cache = dict(aliases=['update-cache'], type='bool', default='yes'),
# this should not be needed, but exists as a failsafe
install_python_apt=dict(required=False, default="yes", type='bool'),
validate_certs = dict(default='yes', type='bool'),
),
supports_check_mode=True,
)

View File

@ -307,7 +307,6 @@ def main():
module.exit_json(changed=changed, **args)
if state in ['mounted', 'present']:
name, changed = set_mount(**args)
if state == 'mounted':
if not os.path.exists(name):
try:
@ -315,6 +314,8 @@ def main():
except (OSError, IOError), e:
module.fail_json(msg="Error making dir %s: %s" % (name, str(e)))
name, changed = set_mount(**args)
if state == 'mounted':
res = 0
if os.path.ismount(name):
if changed:

View File

@ -198,7 +198,7 @@ def main():
break
except OSError, e:
# File not present
if os.errno == 2:
if e.errno == 2:
time.sleep(1)
else:
elapsed = datetime.datetime.now() - start

View File

@ -51,7 +51,7 @@ def _disable_module(module):
a2dismod_binary = module.get_bin_path("a2dismod")
result, stdout, stderr = module.run_command("%s %s" % (a2dismod_binary, name))
if re.match(r'.*already disabled.*', stdout):
if re.match(r'.*' + name + r' already disabled.*', stdout, re.S):
module.exit_json(changed = False, result = "Success")
elif result != 0:
module.fail_json(msg="Failed to disable module %s: %s" % (name, stdout))
@ -63,7 +63,7 @@ def _enable_module(module):
a2enmod_binary = module.get_bin_path("a2enmod")
result, stdout, stderr = module.run_command("%s %s" % (a2enmod_binary, name))
if re.match(r'.*already enabled.*', stdout):
if re.match(r'.*' + name + r' already enabled.*', stdout, re.S):
module.exit_json(changed = False, result = "Success")
elif result != 0:
module.fail_json(msg="Failed to enable module %s: %s" % (name, stdout))

View File

@ -1,8 +1,26 @@
ansible (1.7.1) unstable; urgency=low
* 1.7.1 release
-- Michael DeHaan <michael@ansible.com> Thu, 14 Aug 2014 17:00:00 -0500
ansible (1.7) unstable; urgency=low
* 1.7 release (PENDING)
* 1.7 release
-- Michael DeHaan <michael@ansible.com> Wed, 07 May 2014 15:00:03 -0500
-- Michael DeHaan <michael@ansible.com> Web, 06 Aug 2014 13:00:00 -0500
ansible (1.6.10) unstable; urgency=low
* 1.6.10 release
-- Michael DeHaan <michael@ansible.com> Fri, 25 Jul 2014 13:00:00 -0500
ansible (1.6.9) unstable; urgency=low
* 1.6.9 release
-- Michael DeHaan <michael@ansible.com> Thu, 24 Jul 2014 23:30:00 -0500
ansible (1.6.8) unstable; urgency=low

View File

@ -115,6 +115,18 @@ rm -rf %{buildroot}
%changelog
* Thu Aug 14 2014 Michael DeHaan <michael@ansible.com> - 1.7.1
- Release 1.7.1
* Wed Aug 06 2014 Michael DeHaan <michael@ansible.com> - 1.7.0
- Release 1.7.0
* Fri Jul 25 2014 Michael DeHaan <michael@ansible.com> - 1.6.10
- Release 1.6.10
* Thu Jul 24 2014 Michael DeHaan <michael@ansible.com> - 1.6.9
- Release 1.6.9
* Tue Jul 22 2014 Michael DeHaan <michael@ansible.com> - 1.6.8
- Release 1.6.8

View File

@ -52,6 +52,9 @@ setup(name='ansible',
'ansible.callback_plugins',
'ansible.module_utils'
],
package_data={
'': ['module_utils/*.ps1'],
},
scripts=[
'bin/ansible',
'bin/ansible-playbook',

View File

@ -170,14 +170,17 @@
- "shell_result4.changed == False"
- name: execute a shell command using a literal multiline block
args:
executable: /bin/bash
shell: |
echo this is a
"multiline echo"
"with a new line
in quotes"
| md5sum
| tr -s ' '
in quotes" \
| md5sum \
| tr -s ' ' \
| cut -f1 -d ' '
echo "this is a second line"
register: shell_result5
- debug: var=shell_result5
@ -186,5 +189,5 @@
assert:
that:
- "shell_result5.changed"
- "shell_result5.stdout == '32f3cc201b69ed8afa3902b80f554ca8'"
- "shell_result5.stdout == '32f3cc201b69ed8afa3902b80f554ca8\nthis is a second line'"

View File

@ -164,3 +164,11 @@
- result.invocation.module_name == "debug"
- result.msg == "this should be debugged"
- name: test conditional includes
include: test_include_conditional.yml
when: false
- name: assert the nested include from test_include_conditional was not set
assert:
that:
- nested_include_var is undefined

View File

@ -0,0 +1 @@
- include: test_include_nested.yml

View File

@ -0,0 +1,2 @@
- name: set the nested include fact
set_fact: nested_include_var=1

View File

@ -24,6 +24,8 @@ class FakeInventory(object):
self.hosts = {}
def basedir(self):
return "."
def src(self):
return "fakeinventory"
def get_variables(self, host, vault_password=None):
if host in self.hosts:
return self.hosts[host]

View File

@ -705,9 +705,9 @@ class TestUtils(unittest.TestCase):
# jinja2 loop blocks with lots of complexity
_test_combo(
# in memory of neighbors cat
# we only preserve newlines inside of quotes
'a {% if x %} y {%else %} {{meow}} {% endif %} "cookie\nchip"\ndone',
['a', '{% if x %}', 'y', '{%else %}', '{{meow}}', '{% endif %}', '"cookie\nchip"', 'done']
# we preserve line breaks unless a line continuation character preceeds them
'a {% if x %} y {%else %} {{meow}} {% endif %} "cookie\nchip" \\\ndone\nand done',
['a', '{% if x %}', 'y', '{%else %}', '{{meow}}', '{% endif %}', '"cookie\nchip"', 'done', '\nand', 'done']
)
# test space preservation within quotes
@ -729,6 +729,10 @@ class TestUtils(unittest.TestCase):
ansible.utils._clean_data('this string has a {{variable}}', from_remote=True),
'this string has a {#variable#}'
)
self.assertEqual(
ansible.utils._clean_data('this string {{has}} two {{variables}} in it', from_remote=True),
'this string {#has#} two {#variables#} in it'
)
self.assertEqual(
ansible.utils._clean_data('this string has a {{variable with a\nnewline}}', from_remote=True),
'this string has a {#variable with a\nnewline#}'