Compare commits
65 Commits
v2.8.11
...
release1.7
| Author | SHA1 | Date | |
|---|---|---|---|
| d3c28fee87 | |||
| 082c24d968 | |||
| e96fe1f33e | |||
| ed0eeb3d58 | |||
| 39d85a9bf5 | |||
| 6e9d45e2b1 | |||
| f5a6c36dcb | |||
| af269a1524 | |||
| 085ba4ffd8 | |||
| 8d0c1dd2fa | |||
| e110126121 | |||
| 7954be2593 | |||
| b83bce4b88 | |||
| adf06c0b11 | |||
| 506c7b2bc0 | |||
| 268e72318f | |||
| 48b4805d77 | |||
| 7589105c23 | |||
| ef1ae4db86 | |||
| 789196b2b8 | |||
| 9f9e975990 | |||
| d9fb94378c | |||
| 293b11e45d | |||
| 7a68f4e428 | |||
| 4dcbeed7af | |||
| d6266ba72b | |||
| a096240d17 | |||
| dcde900e84 | |||
| d07107ab71 | |||
| 165572fbb8 | |||
| df136769eb | |||
| f1f88f8904 | |||
| 38123579fb | |||
| da08e62017 | |||
| bb1ca99db7 | |||
| 27223b1253 | |||
| 6f8498d0b1 | |||
| 08c968a999 | |||
| f0270bb289 | |||
| 5ae0bb176a | |||
| cd643f0d32 | |||
| 3cf0f27f7b | |||
| 7f5bf63a9b | |||
| ba82d3e423 | |||
| be76296067 | |||
| adcd53c67e | |||
| 4eb5faa1b5 | |||
| a77674145f | |||
| 1dd484cc2d | |||
| 3121008475 | |||
| ce28176fa2 | |||
| 2b9d439b03 | |||
| 29ff0df4b9 | |||
| edb08b56fc | |||
| c783c6754b | |||
| c32e1961c4 | |||
| 67821f0122 | |||
| e2ea7b3297 | |||
| f961082c05 | |||
| 5c00381e26 | |||
| 2912793087 | |||
| 9b5619d741 | |||
| 92382c4181 | |||
| 650e967b30 | |||
| 953a2dfb8a |
33
CHANGELOG.md
33
CHANGELOG.md
@ -1,7 +1,28 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
## 1.7 "Summer Nights" - Active Development
|
||||
## 1.8 "You Really Got Me" - Active Development
|
||||
|
||||
|
||||
## 1.7.2 "Summer Nights" - Sep 24, 2014
|
||||
|
||||
- Fixes a bug in accelerate mode which caused a traceback when trying to use that connection method.
|
||||
- Fixes a bug in vault where the password file option was not being used correctly internally.
|
||||
- Improved multi-line parsing when using YAML literal blocks (using > or |).
|
||||
- Fixed a bug with the file module and the creation of relative symlinks.
|
||||
- Fixed a bug where checkmode was not being honored during the templating of files.
|
||||
- Other various bug fixes.
|
||||
|
||||
## 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 +55,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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -4,8 +4,13 @@ Ansible Releases at a Glance
|
||||
Active Development
|
||||
++++++++++++++++++
|
||||
|
||||
1.7 "Summer Nights" -------- SUMMER 2014
|
||||
1.8 "You Really Got Me" ---- FALL 2014
|
||||
|
||||
1.7.2 "Summer Nights" -------- 09-24-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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.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()
|
||||
|
||||
|
||||
@ -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.2'
|
||||
__author__ = 'Michael DeHaan'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -443,6 +450,10 @@ class Inventory(object):
|
||||
|
||||
vars = {}
|
||||
|
||||
# special case for ungrouped hosts, make sure group_vars/all is loaded
|
||||
if len(host.groups) == 1 and host.groups[0].name == 'ungrouped':
|
||||
vars = self.get_group_variables('all', vault_password=self._vault_password)
|
||||
|
||||
# plugin.run retrieves all vars (also from groups) for host
|
||||
vars_results = [ plugin.run(host, vault_password=vault_password) for plugin in self._vars_plugins if hasattr(plugin, 'run')]
|
||||
for updated in vars_results:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -58,7 +58,7 @@ class Host(object):
|
||||
groups = self.get_groups()
|
||||
for group in sorted(groups, key=lambda g: g.depth):
|
||||
results = utils.combine_vars(results, group.get_variables())
|
||||
results.update(self.vars)
|
||||
results = utils.combine_vars(results, self.vars)
|
||||
results['inventory_hostname'] = self.name
|
||||
results['inventory_hostname_short'] = self.name.split('.')[0]
|
||||
results['group_names'] = sorted([ g.name for g in groups if g.name != 'all'])
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
@ -76,7 +76,7 @@ def split_args(args):
|
||||
do_decode = True
|
||||
except UnicodeDecodeError:
|
||||
do_decode = False
|
||||
items = args.split(' ')
|
||||
items = args.strip().split('\n')
|
||||
|
||||
# iterate over the tokens, and reassemble any that may have been
|
||||
# split on a space inside a jinja2 block.
|
||||
@ -94,21 +94,22 @@ def split_args(args):
|
||||
|
||||
# now we loop over each split chunk, coalescing tokens if the white space
|
||||
# split occurred within quotes or a jinja2 block of some kind
|
||||
for item in items:
|
||||
for itemidx,item in enumerate(items):
|
||||
|
||||
# we split on spaces and newlines separately, so that we
|
||||
# can tell which character we split on for reassembly
|
||||
# inside quotation characters
|
||||
tokens = item.split('\n')
|
||||
tokens = item.strip().split(' ')
|
||||
|
||||
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
|
||||
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
|
||||
@ -129,7 +130,15 @@ def split_args(args):
|
||||
params.append(token)
|
||||
appended = True
|
||||
elif print_depth or block_depth or comment_depth or inside_quotes or was_inside_quotes:
|
||||
params[-1] = "%s%s%s" % (params[-1], spacer, token)
|
||||
if idx == 0 and not inside_quotes and was_inside_quotes:
|
||||
params[-1] = "%s%s" % (params[-1], token)
|
||||
elif len(tokens) > 1:
|
||||
spacer = ''
|
||||
if idx > 0:
|
||||
spacer = ' '
|
||||
params[-1] = "%s%s%s" % (params[-1], spacer, token)
|
||||
else:
|
||||
params[-1] = "%s\n%s" % (params[-1], token)
|
||||
appended = True
|
||||
|
||||
# if the number of paired block tags is not the same, the depth has changed, so we calculate that here
|
||||
@ -157,6 +166,16 @@ def split_args(args):
|
||||
if not (print_depth or block_depth or comment_depth) and not inside_quotes and not appended and token != '':
|
||||
params.append(token)
|
||||
|
||||
# if this was the last token in the list, and we have more than
|
||||
# one item (meaning we split on newlines), add a newline back here
|
||||
# to preserve the original structure
|
||||
if len(items) > 1 and itemidx != len(items) - 1 and not line_continuation:
|
||||
if not params[-1].endswith('\n'):
|
||||
params[-1] += '\n'
|
||||
|
||||
# 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
|
||||
if print_depth or block_depth or comment_depth or inside_quotes:
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'] != '':
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ class ActionModule(object):
|
||||
xfered = self.runner._transfer_str(conn, tmp, 'src', resultant)
|
||||
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root':
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
|
||||
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
|
||||
|
||||
# run the copy module
|
||||
|
||||
@ -227,7 +227,7 @@ class ActionModule(object):
|
||||
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
||||
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root' and not raw:
|
||||
if (self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root') and not raw:
|
||||
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp_path)
|
||||
|
||||
if raw:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -32,6 +32,16 @@ class ActionModule(object):
|
||||
options = {}
|
||||
if complex_args:
|
||||
options.update(complex_args)
|
||||
options.update(utils.parse_kv(module_args))
|
||||
|
||||
# parse the k=v arguments and convert any special boolean
|
||||
# strings into proper booleans (issue #8629)
|
||||
parsed_args = utils.parse_kv(module_args)
|
||||
for k,v in parsed_args.iteritems():
|
||||
# convert certain strings to boolean values
|
||||
if isinstance(v, basestring) and v.lower() in ('true', 'false', 'yes', 'no'):
|
||||
parsed_args[k] = utils.boolean(v)
|
||||
|
||||
# and finally update the options with the parsed/modified args
|
||||
options.update(parsed_args)
|
||||
|
||||
return ReturnData(conn=conn, result=dict(ansible_facts=options))
|
||||
|
||||
@ -113,7 +113,7 @@ class ActionModule(object):
|
||||
xfered = self.runner._transfer_str(conn, tmp, 'source', resultant)
|
||||
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root':
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
|
||||
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
|
||||
|
||||
# run the copy module
|
||||
@ -132,5 +132,12 @@ class ActionModule(object):
|
||||
res.diff = dict(before=dest_contents, after=resultant)
|
||||
return res
|
||||
else:
|
||||
# if we're running in check mode, we still want the file module
|
||||
# to execute, since we can't know if anything would be changed here,
|
||||
# so we inject the check mode param into the module args and rely on
|
||||
# the file module to report its changed status
|
||||
if self.runner.noop_on_check(inject):
|
||||
new_module_args = dict(CHECKMODE=True)
|
||||
module_args = utils.merge_module_args(module_args, new_module_args)
|
||||
return self.runner._execute_module(conn, tmp, 'file', module_args, inject=inject, complex_args=complex_args)
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ class ActionModule(object):
|
||||
# handle check mode client side
|
||||
# fix file permissions when the copy is done as a different user
|
||||
if copy:
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root':
|
||||
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
|
||||
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp)
|
||||
# Build temporary module_args.
|
||||
new_module_args = dict(
|
||||
|
||||
@ -80,6 +80,9 @@ class Connection(object):
|
||||
private_key_file=private_key_file
|
||||
)
|
||||
|
||||
if not getattr(self.ssh, 'shell', None):
|
||||
self.ssh.shell = utils.plugins.shell_loader.get('sh')
|
||||
|
||||
# attempt to work around shared-memory funness
|
||||
if getattr(self.runner, 'aes_keys', None):
|
||||
utils.AES_KEYS = self.runner.aes_keys
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
'''
|
||||
@ -682,6 +669,11 @@ def parse_yaml_from_file(path, vault_password=None):
|
||||
|
||||
vault = VaultLib(password=vault_password)
|
||||
if vault.is_encrypted(data):
|
||||
# if the file is encrypted and no password was specified,
|
||||
# the decrypt call would throw an error, but we check first
|
||||
# since the decrypt function doesn't know the file name
|
||||
if vault_password is None:
|
||||
raise errors.AnsibleError("A vault password must be specified to decrypt %s" % path)
|
||||
data = vault.decrypt(data)
|
||||
show_content = False
|
||||
|
||||
@ -704,7 +696,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 +1249,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:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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!")
|
||||
|
||||
|
||||
@ -237,8 +237,8 @@ def main():
|
||||
else:
|
||||
relpath = os.path.dirname(path)
|
||||
|
||||
absrc = os.path.normpath('%s/%s' % (relpath, os.path.basename(src)))
|
||||
if not os.path.exists(src) and not os.path.exists(absrc) and not force:
|
||||
absrc = os.path.join(relpath, src)
|
||||
if not os.path.exists(absrc) and not force:
|
||||
module.fail_json(path=path, src=src, msg='src file does not exist, use "force=yes" if you really want to create the link: %s' % absrc)
|
||||
|
||||
if state == 'hard':
|
||||
|
||||
@ -363,14 +363,27 @@ 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)
|
||||
|
||||
# always add one layer of quotes
|
||||
line = "'%s'" % 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 = module.safe_eval(pipes.quote(line))
|
||||
line = re.sub(r'(\\[0-9]{1,3})', r'\\\1', line)
|
||||
line = module.safe_eval(line)
|
||||
|
||||
# Now remove quotes around the string, if needed after
|
||||
# removing the layer we added above
|
||||
line = unquote(line)
|
||||
if should_unquote:
|
||||
line = unquote(line)
|
||||
|
||||
present(module, dest, params['regexp'], line,
|
||||
ins_aft, ins_bef, create, backup, backrefs)
|
||||
@ -382,5 +395,6 @@ def main():
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.splitter import *
|
||||
|
||||
main()
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -46,4 +46,12 @@ EXAMPLES = '''
|
||||
- set_fact:
|
||||
one_fact: something
|
||||
other_fact: "{{ local_var * 2 }}"
|
||||
|
||||
# As of 1.8, Ansible will convert boolean strings ('true', 'false', 'yes', 'no')
|
||||
# to proper boolean values when using the key=value syntax, however it is still
|
||||
# recommended that booleans be set using the complex argument style:
|
||||
- set_fact:
|
||||
one_fact: true
|
||||
other_fact: false
|
||||
|
||||
'''
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -1,8 +1,32 @@
|
||||
ansible (1.7.2) unstable; urgency=low
|
||||
|
||||
* 1.7.2 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Wed, 24 Sep 2014 15:00:00 -0500
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -115,6 +115,21 @@ rm -rf %{buildroot}
|
||||
|
||||
%changelog
|
||||
|
||||
* Wed Sep 24 2014 Michael DeHaan <michael@ansible.com> - 1.7.2
|
||||
- Release 1.7.2
|
||||
|
||||
* 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
|
||||
|
||||
|
||||
3
setup.py
3
setup.py
@ -52,6 +52,9 @@ setup(name='ansible',
|
||||
'ansible.callback_plugins',
|
||||
'ansible.module_utils'
|
||||
],
|
||||
package_data={
|
||||
'': ['module_utils/*.ps1'],
|
||||
},
|
||||
scripts=[
|
||||
'bin/ansible',
|
||||
'bin/ansible-playbook',
|
||||
|
||||
@ -17,15 +17,20 @@
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ============================================================
|
||||
- include_vars: '{{ ansible_os_family }}.yml'
|
||||
- include_vars: '{{ item }}'
|
||||
with_first_found:
|
||||
- files:
|
||||
- '{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml'
|
||||
- '{{ ansible_os_family }}.yml'
|
||||
paths: '../vars'
|
||||
|
||||
- name: install mysqldb_test rpm dependencies
|
||||
yum: name={{ item }} state=latest
|
||||
yum: name={{ item }} state=latest
|
||||
with_items: mysql_packages
|
||||
when: ansible_pkg_mgr == 'yum'
|
||||
|
||||
- name: install mysqldb_test debian dependencies
|
||||
apt: name={{ item }} state=latest
|
||||
- name: install mysqldb_test debian dependencies
|
||||
apt: name={{ item }} state=latest
|
||||
with_items: mysql_packages
|
||||
when: ansible_pkg_mgr == 'apt'
|
||||
|
||||
|
||||
6
test/integration/roles/setup_mysql_db/vars/RedHat-7.yml
Normal file
6
test/integration/roles/setup_mysql_db/vars/RedHat-7.yml
Normal file
@ -0,0 +1,6 @@
|
||||
mysql_service: 'mariadb'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb-server
|
||||
- MySQL-python
|
||||
- bzip2
|
||||
@ -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"
|
||||
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'"
|
||||
|
||||
|
||||
@ -193,3 +193,77 @@
|
||||
assert:
|
||||
that:
|
||||
- "result.skipped == true"
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# proper booleanification tests (issue #8629)
|
||||
|
||||
- name: set fact to string 'false'
|
||||
set_fact: bool_test1=false
|
||||
|
||||
- name: set fact to string 'False'
|
||||
set_fact: bool_test2=False
|
||||
|
||||
- name: set fact to a proper boolean using complex args
|
||||
set_fact:
|
||||
bool_test3: false
|
||||
|
||||
- name: "test boolean value 'false' string using 'when: var'"
|
||||
command: echo 'hi'
|
||||
when: bool_test1
|
||||
register: result
|
||||
|
||||
- name: assert that the task did not run for 'false'
|
||||
assert:
|
||||
that:
|
||||
- "result.skipped == true"
|
||||
|
||||
- name: "test boolean value 'false' string using 'when: not var'"
|
||||
command: echo 'hi'
|
||||
when: not bool_test1
|
||||
register: result
|
||||
|
||||
- name: assert that the task DID run for not 'false'
|
||||
assert:
|
||||
that:
|
||||
- "result.changed"
|
||||
|
||||
- name: "test boolean value of 'False' string using 'when: var'"
|
||||
command: echo 'hi'
|
||||
when: bool_test2
|
||||
register: result
|
||||
|
||||
- name: assert that the task did not run for 'False'
|
||||
assert:
|
||||
that:
|
||||
- "result.skipped == true"
|
||||
|
||||
- name: "test boolean value 'False' string using 'when: not var'"
|
||||
command: echo 'hi'
|
||||
when: not bool_test2
|
||||
register: result
|
||||
|
||||
- name: assert that the task DID run for not 'False'
|
||||
assert:
|
||||
that:
|
||||
- "result.changed"
|
||||
|
||||
- name: "test proper boolean value of complex arg using 'when: var'"
|
||||
command: echo 'hi'
|
||||
when: bool_test3
|
||||
register: result
|
||||
|
||||
- name: assert that the task did not run for proper boolean false
|
||||
assert:
|
||||
that:
|
||||
- "result.skipped == true"
|
||||
|
||||
- name: "test proper boolean value of complex arg using 'when: not var'"
|
||||
command: echo 'hi'
|
||||
when: not bool_test3
|
||||
register: result
|
||||
|
||||
- name: assert that the task DID run for not false
|
||||
assert:
|
||||
that:
|
||||
- "result.changed"
|
||||
|
||||
|
||||
@ -228,3 +228,41 @@
|
||||
that:
|
||||
- 'file17_result.failed == true'
|
||||
- 'file17_result.state == "directory"'
|
||||
|
||||
- name: create soft link to directory using absolute path
|
||||
file: src=/ dest={{output_dir}}/root state=link
|
||||
register: file18_result
|
||||
|
||||
- name: verify that the result was marked as changed
|
||||
assert:
|
||||
that:
|
||||
- "file18_result.changed == true"
|
||||
|
||||
- name: create another test sub-directory
|
||||
file: dest={{output_dir}}/sub2 state=directory
|
||||
register: file19_result
|
||||
|
||||
- name: verify that the new directory was created
|
||||
assert:
|
||||
that:
|
||||
- 'file19_result.changed == true'
|
||||
- 'file19_result.state == "directory"'
|
||||
|
||||
- name: create soft link to relative file
|
||||
file: src=../sub1/file1 dest={{output_dir}}/sub2/link1 state=link
|
||||
register: file20_result
|
||||
|
||||
- name: verify that the result was marked as changed
|
||||
assert:
|
||||
that:
|
||||
- "file20_result.changed == true"
|
||||
|
||||
- name: create soft link to relative directory
|
||||
file: src=sub1 dest={{output_dir}}/sub1-link state=link
|
||||
register: file21_result
|
||||
|
||||
- name: verify that the result was marked as changed
|
||||
assert:
|
||||
that:
|
||||
- "file21_result.changed == true"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1 @@
|
||||
- include: test_include_nested.yml
|
||||
@ -0,0 +1,2 @@
|
||||
- name: set the nested include fact
|
||||
set_fact: nested_include_var=1
|
||||
@ -294,3 +294,65 @@
|
||||
that:
|
||||
- "result.stat.md5 == '65f955c2a9722fd43d07103d7756ff9b'"
|
||||
|
||||
###################################################################
|
||||
# issue 8535
|
||||
|
||||
- name: create a new file for testing quoting issues
|
||||
file: dest={{output_dir}}/test_quoting.txt state=touch
|
||||
register: result
|
||||
|
||||
- name: assert the new file was created
|
||||
assert:
|
||||
that:
|
||||
- result.changed
|
||||
|
||||
- name: use with_items to add code-like strings to the quoting txt file
|
||||
lineinfile: >
|
||||
dest={{output_dir}}/test_quoting.txt
|
||||
line="{{ item }}"
|
||||
insertbefore=BOF
|
||||
with_items:
|
||||
- "'foo'"
|
||||
- "dotenv.load();"
|
||||
- "var dotenv = require('dotenv');"
|
||||
register: result
|
||||
|
||||
- name: assert the quote test file was modified correctly
|
||||
assert:
|
||||
that:
|
||||
- result.results|length == 3
|
||||
- result.results[0].changed
|
||||
- result.results[0].item == "'foo'"
|
||||
- result.results[1].changed
|
||||
- result.results[1].item == "dotenv.load();"
|
||||
- result.results[2].changed
|
||||
- result.results[2].item == "var dotenv = require('dotenv');"
|
||||
|
||||
- name: stat the quote test file
|
||||
stat: path={{output_dir}}/test_quoting.txt
|
||||
register: result
|
||||
|
||||
- name: assert test md5 matches after backref line was replaced
|
||||
assert:
|
||||
that:
|
||||
- "result.stat.md5 == '29f349baf1b9c6703beeb346fe8dc669'"
|
||||
|
||||
- name: insert a line into the quoted file with a single quote
|
||||
lineinfile: dest={{output_dir}}/test_quoting.txt line="import g'"
|
||||
register: result
|
||||
|
||||
- name: assert that the quoted file was changed
|
||||
assert:
|
||||
that:
|
||||
- result.changed
|
||||
|
||||
- name: stat the quote test file
|
||||
stat: path={{output_dir}}/test_quoting.txt
|
||||
register: result
|
||||
|
||||
- name: assert test md5 matches after backref line was replaced
|
||||
assert:
|
||||
that:
|
||||
- "result.stat.md5 == 'fbe9c4ba2490f70eb1974ce31ec4a39f'"
|
||||
|
||||
###################################################################
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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\n', 'and', '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#}'
|
||||
|
||||
Reference in New Issue
Block a user