Compare commits

...

61 Commits

Author SHA1 Message Date
dffea7f877 Version bump and CHANGELOG/RELEASES update for 1.6.3 2014-06-09 14:30:07 -05:00
7059528059 Cleaning up svr4pkg commit fix for #7645 2014-06-09 14:14:13 -05:00
fadf02d0c6 fix for svr4pkg module failure reporting, issue #7645 2014-06-09 14:14:09 -05:00
3ebecdd1ed Fixing mysql master replication boolean
Fixes #7528
2014-06-09 14:14:06 -05:00
58a0bcbe51 Fix issue with symlink path detection
Fixes #7627
Fixes #7664
2014-06-09 14:14:02 -05:00
10074d3622 Fix linking issue to directories in file module
The os.path.isdir() returns True for symlinks that point to directories,
so we also need to check to make sure it's not a symlink too.

Fixes #7657
2014-06-09 14:13:55 -05:00
2625196f5d Fix error passing play vars to an included playbook with empty vars
Fixes #7658
2014-06-09 14:13:50 -05:00
e85a9abe75 Fix order of merging variables for the combined cache
Fixes #7598
2014-06-09 14:13:18 -05:00
b99ea2723a Removing cruft leftover from the deprecation of include+with_items
Also cleaning up integration tests that had checks for the empty item
attribute in certain results.

Fixes #7535
2014-06-09 14:11:21 -05:00
7cb7fdc2a7 fixing problem where ansible would error out because Amazon was saying the group didnt exist yet (needed to wait a few more seconds for amazon to propagate correctly) 2014-06-09 14:11:12 -05:00
936e1de18b Fix error when using os.getlogin() without a tty 2014-06-09 14:11:08 -05:00
c4b6c0ab84 Fix typo in CHANGE MASTER TO syntax 2014-06-09 14:11:05 -05:00
4dd5eeb1c4 Ensure there are no duplicates in the merged/intersected lists 2014-06-09 14:11:01 -05:00
d8c8c0823a Merge and intersect lists without using sets.
Using sets for these operations is dangerous because sets cannot contain
certain object types (such as lists) and their iteration order is
undefined.

Fixes #7596
2014-06-09 14:10:57 -05:00
3801be693b dont mutate inventory vars 2014-06-09 14:10:53 -05:00
75adb3978b Check groupnames for None as well as an empty string in add_host
Fixes #7585
2014-06-09 14:10:48 -05:00
4e4ffa669a Correctly examine path for relative links in file module
Fixes #7515
Fixes #7537
2014-06-09 14:10:44 -05:00
ce80c0b8af Support checking for su user to atomic_move
Fixes #7553
2014-06-09 14:10:40 -05:00
b50a64b0a1 Correct issue of handlers running on all hosts incorrectly
Also adds an integration test to catch this bug in the future.

Fixes #7559
2014-06-09 14:10:35 -05:00
adfbcb8760 now module searches for update-alternatives vs hardcoding it 2014-06-09 14:10:29 -05:00
5128bced6b Don't use listify_ function, when all we want to do is template variables
This was causing a bug in the env module, due to the fact that we now
pass variables for the module through the templating engine combined
with the fact that we split-up the hostvars and setup variables. As a
result, if a variable in the env lookup had the same name as the variable
in Ansible, it would try and template itself over and over again until
the recursion limit would be hit, at which time an empty string was
returned.

Fixes #7396
2014-06-09 14:10:24 -05:00
549f67c0d8 Merge pull request #7527 from hlieberman/release1.6.2
Remove colon which breaks installation of 1.6.2.
2014-05-25 13:06:39 -04:00
d98b6ae8ab Remove colon which breaks installation of 1.6.2.
Rework the description so that it doesn't require the colon, rather
than just wrapping it in quotes.
2014-05-23 20:15:18 -04:00
ce4883bcc5 Version bump for 1.6.2 2014-05-23 15:37:57 -05:00
a75a84b44a Don't re-query inventory when trimming failed/dark hosts
Since the filter list contains hosts, passing that back to list_hosts()
in the inventoy causes issues when the hostname is an IPv6 address
(with :'s interpreted as group unions). Since we already have the list
of hosts, we should not need to pass that back through inventory a
second time.

Fixes #7446
2014-05-23 15:28:42 -05:00
f132e99b2a Adding the capability to proxy the ssl cert check
The ssl cert check will now respect the http and https proxy
environment settings. The url may also have the username/password
embedded, in which case basic auth will be used to connect to the
proxy server.

Fixes #7413
2014-05-23 15:27:05 -05:00
4d0ce5bd41 Make sure dwim'd relative path ends in a "/" if the original does
Fixes #7296
2014-05-23 15:17:43 -05:00
b01da1b780 Ensure the validation string has %s in it 2014-05-21 16:16:48 -05:00
1248d2ce8a Fixing absolute path calculation for file module when linking
Fixes #7403
2014-05-21 15:00:21 -05:00
d72cd70cc8 Also set the environment variables when resetting the locale to C 2014-05-21 15:00:05 -05:00
362626d07c Default use_proxy to True for fetch_url()
Also added some error handling to the fetch_url() call in the
apt_repository module, so that failures to look up the PPA info
are properly handled.

Fixes #7322
2014-05-21 14:59:58 -05:00
a54b8764fa Reset locale to 'C' if the specified one is invalid
Fixes #7448
2014-05-21 14:59:51 -05:00
6b4584d004 Remove erroneous set of status_code causing a traceback in module uri
Fixes #7416
2014-05-21 14:59:43 -05:00
cb0842ab49 docker: fix targetting images by name 2014-05-21 14:59:32 -05:00
2432b45eb1 Fixed extension filtering in InventoryDirectory 2014-05-21 14:59:29 -05:00
d6214c5d56 Check module_path permissions when creating ssh_wrapper for git
If the module directory is not writable/executable to the current user
(most likely because of a sudo to a non-root user), the ssh_wrapper
will be created in the default location for mkstemp() calls. To facilitate
the deletion of these new files, a new mechanism for cleaning up files
created by the module was also added.

Fixes #7375
2014-05-21 14:59:20 -05:00
c5b6d23c63 During vsphere config check attempt cast before marking values missing
Fixes #7385
2014-05-21 14:59:13 -05:00
e83f14200c Fix env splitting in docker module
ENV variable value could contain '=' (for example mysql://host/db?pool=10)
2014-05-21 14:59:05 -05:00
7c0431b1e4 Add missing basedir to Runner in _do_setup_step 2014-05-21 14:58:58 -05:00
1c5055a401 Properly merge role conditionals in with pre-existing conditionals
Fixes #7387
2014-05-21 14:58:49 -05:00
24d885e368 Fix to unescape * was discarded by unescape of @
`decoded_name` was created twice, each from `rset.name`

So, the second call to `.replace(r'\100', '@')` overwrites decoded_name, discarding the result of the call to `.replace(r'\052', '*')`

I had a problem with wildcard domains that was fixed by this patch.
2014-05-21 14:57:23 -05:00
8851ceaa9e Use the existing module_vars during the templating of module_vars
Since some of the vars contained in there may be used in the templating
of other variables. This also reverts e83a494 which originally fixed
issue #6979 but broke some other variable related things.

Fixes #6979
Fixes #7321
Fixes #7332
2014-05-21 14:56:59 -05:00
854bf14222 Evaluate changed_when only if task is not skipped
Fixes #7405
2014-05-21 14:55:45 -05:00
d8526a6c80 Only chown on atomic move if the uid/gid don't match the src/tmp file
This is a corner case for remote file systems that don't support
chown() and where the source and destination for the atomic_move
are on that remote file system.

Fixes #7372
2014-05-21 14:55:39 -05:00
e0fe6ca5d8 Use utils.combine_vars on vars_files data
Fixes #7345
2014-05-21 14:55:35 -05:00
ca2a12aa5b Pass vars_files on to included playbooks too
Fixes #7384
2014-05-21 14:55:29 -05:00
29018cf2a7 Correctly initialize combined_cache for hosts if setup is skipped
Fixes #7364
2014-05-21 14:55:24 -05:00
9d8eee633d Make sure the docker data has a "Names" field before using it
Fixes #7363
2014-05-21 14:55:20 -05:00
8cc71a6eec Do not base synchronize source on the inventory directory 2014-05-21 14:55:01 -05:00
3565249471 Fix: Remove check for Ghost state
This fixed the just committed change handling non existent Ghost state.
2014-05-21 14:53:39 -05:00
595813a1fe Remove check for Ghost state
This doesn't exist anymore and causes ansible to throw a KeyError
2014-05-21 14:53:34 -05:00
0de306574e rds module: Fix undeclared reference to "params" when setting password in
modify mode.
2014-05-21 14:53:28 -05:00
d33486d52a ec2: Fix bug with running/stopped state and wait=yes.
If `get_all_instances` returns multiple reservations, the old wait loop only
dealt with the first reservation. Thus, the wait loop may end before all
instances get to be running/stopped.

Also clean up the code a little.
2014-05-21 14:53:23 -05:00
0dabb37337 Make sure the default ec2_group egress rule is not removed
Upon a second run, the default egress rule will be removed when a
vpc is specified but no other egress rules were set. This patch
corrects that behavior by removing the default egress rule from the
list of unmatched outbound rules.

Fixes #7309
2014-05-21 14:53:18 -05:00
9a930fb481 Add pyc/pyo to ignore list for inventory directories
Fixes #7308
2014-05-21 14:53:09 -05:00
d4e11c5c69 Version bump for 1.6.1 release 2014-05-07 12:33:07 -05:00
08f4984d92 Add integration tests for group_by module. Fixed bug introduced by ansible/ansible#7273 while also fixing issue described in ansible/ansible#6953 2014-05-07 09:12:30 -05:00
7daaabdedb Close fds when running commands from the accelerate daemon
Fixes #7307
2014-05-07 08:26:06 -05:00
9943c50863 Make sure distro variable is set if python-apt is installed
Fixes #7295
2014-05-07 08:25:58 -05:00
ce15813e70 made enabled/disabled checks of apache2_module workable 2014-05-07 08:25:50 -05:00
f05d457560 Updating changlog and releases for 1.6 2014-05-05 16:46:31 -05:00
45 changed files with 618 additions and 213 deletions

View File

@ -1,7 +1,35 @@
Ansible Changes By Release
==========================
## 1.6 "And the Cradle Will Rock" - Active Development
## 1.6.3 "And the Cradle Will Rock" - Jun 09, 2014
- Corrects a regression where handlers were run across all hosts, not just those that triggered the handler.
- Fixed a bug in which modules did not support properly moving a file atomically when su was in use.
- Fixed two bugs related to symlinks with directories when using the file module.
- Fixed a bug related to MySQL master replication syntax.
- Corrects a regression in the order of variable merging done by the internal runner code.
- Various other minor bug fixes.
## 1.6.2 "And the Cradle Will Rock" - May 23, 2014
- If an improper locale is specified, core modules will now automatically revert to using the 'C' locale.
- Modules using the fetch_url utility will now obey proxy environment variables.
- The SSL validation step in fetch_url will likewise obey proxy settings, however only proxies using the http protocol are supported.
- Fixed multiple bugs in docker module related to version changes upstream.
- Fixed a bug in the ec2_group module where egress rules were lost when a VPC was specified.
- Fixed two bugs in the synchronize module:
* a trailing slash might be lost when calculating relative paths, resulting in an incorrect destination.
* the sync might use the inventory directory incorrectly instead of the playbook or role directory.
- Files will now only be chown'd on an atomic move if the src/dest uid/gid do not match.
## 1.6.1 "And the Cradle Will Rock" - May 7, 2014
- Fixed a bug in group_by, where systems were being grouped incorrectly.
- Fixed a bug where file descriptors may leak to a child process when using accelerate.
- Fixed a bug in apt_repository triggered when python-apt not being installed/available.
- Fixed a bug in the apache2_module module, where modules were not being disabled correctly.
## 1.6 "And the Cradle Will Rock" - May 5, 2014
Major features/changes:

View File

@ -1,21 +1,15 @@
Ansible Releases at a Glance
============================
Future
++++++
1.7 "Summer Nights" -------- SUMMER 2014
Active Development
++++++++++++++++++
1.6 "The Cradle Will Rock" - CURRENT DEVEL BRANCH
1.7 "Summer Nights" -------- CURRENT DEVEL BRANCH
Previous
++++++++
=======
1.6 "The Cradle Will Rock" - NEXT
1.6.3 "The Cradle Will Rock" - 06-09-2014
1.6.2 "The Cradle Will Rock" - 05-23-2014
1.6.1 "The Cradle Will Rock" - 05-07-2014
1.6 "The Cradle Will Rock" - 05-05-2014
1.5.5 "Love Walks In" -------- 04-18-2014
1.5.4 "Love Walks In" -------- 04-01-2014
1.5.3 "Love Walks In" -------- 03-13-2014

View File

@ -1 +1 @@
1.6
1.6.3

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.6'
__version__ = '1.6.3'
__author__ = 'Michael DeHaan'

View File

@ -36,17 +36,11 @@ class InventoryDirectory(object):
self.parsers = []
self.hosts = {}
self.groups = {}
for i in self.names:
if i.endswith("~") or i.endswith(".orig") or i.endswith(".bak"):
continue
if i.endswith(".ini"):
# configuration file for an inventory script
continue
if i.endswith(".retry"):
# this file is generated on a failed playbook and should only be
# used when run specifically
# Skip files that end with certain extensions or characters
if any(i.endswith(ext) for ext in ("~", ".orig", ".bak", ".ini", ".retry", ".pyc", ".pyo")):
continue
# Skip hidden files
if i.startswith('.') and not i.startswith('./'):

View File

@ -43,6 +43,7 @@ BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE
# of an ansible module. The source of this common code lives
# in lib/ansible/module_common.py
import locale
import os
import re
import pipes
@ -182,6 +183,7 @@ class AnsibleModule(object):
self.supports_check_mode = supports_check_mode
self.check_mode = False
self.no_log = no_log
self.cleanup_files = []
self.aliases = {}
@ -190,6 +192,10 @@ class AnsibleModule(object):
if k not in self.argument_spec:
self.argument_spec[k] = v
# check the locale as set by the current environment, and
# reset to LANG=C if it's an invalid/unavailable locale
self._check_locale()
(self.params, self.args) = self._load_params()
self._legal_inputs = ['CHECKMODE', 'NO_LOG']
@ -560,6 +566,24 @@ class AnsibleModule(object):
kwargs['state'] = 'absent'
return kwargs
def _check_locale(self):
'''
Uses the locale module to test the currently set locale
(per the LANG and LC_CTYPE environment settings)
'''
try:
# setting the locale to '' uses the default locale
# as it would be returned by locale.getdefaultlocale()
locale.setlocale(locale.LC_ALL, '')
except locale.Error, e:
# fallback to the 'C' locale, which may cause unicode
# issues but is preferable to simply failing because
# of an unknown locale
locale.setlocale(locale.LC_ALL, 'C')
os.environ['LANG'] = 'C'
os.environ['LC_CTYPE'] = 'C'
except Exception, e:
self.fail_json(msg="An unknown error was encountered while attempting to validate the locale: %s" % e)
def _handle_aliases(self):
aliases_results = {} #alias:canon
@ -898,11 +922,20 @@ class AnsibleModule(object):
def from_json(self, data):
return json.loads(data)
def add_cleanup_file(self, path):
if path not in self.cleanup_files:
self.cleanup_files.append(path)
def do_cleanup_files(self):
for path in self.cleanup_files:
self.cleanup(path)
def exit_json(self, **kwargs):
''' return from the module, without error '''
self.add_path_info(kwargs)
if not 'changed' in kwargs:
kwargs['changed'] = False
self.do_cleanup_files()
print self.jsonify(kwargs)
sys.exit(0)
@ -911,6 +944,7 @@ class AnsibleModule(object):
self.add_path_info(kwargs)
assert 'msg' in kwargs, "implementation error -- msg to explain the error is required"
kwargs['failed'] = True
self.do_cleanup_files()
print self.jsonify(kwargs)
sys.exit(1)
@ -958,7 +992,7 @@ class AnsibleModule(object):
self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, e))
return backupdest
def cleanup(self,tmpfile):
def cleanup(self, tmpfile):
if os.path.exists(tmpfile):
try:
os.unlink(tmpfile)
@ -987,6 +1021,18 @@ class AnsibleModule(object):
creating = not os.path.exists(dest)
try:
login_name = os.getlogin()
except OSError:
# not having a tty can cause the above to fail, so
# just get the LOGNAME environment variable instead
login_name = os.environ.get('LOGNAME', None)
# if the original login_name doesn't match the currently
# logged-in user, or if the SUDO_USER environment variable
# is set, then this user has switched their credentials
switched_user = login_name and login_name != pwd.getpwuid(os.getuid())[0] or os.environ.get('SUDO_USER')
try:
# Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic.
os.rename(src, dest)
@ -1001,7 +1047,7 @@ class AnsibleModule(object):
prefix=".ansible_tmp", dir=dest_dir, suffix=dest_file)
try: # leaves tmp file behind when sudo and not root
if os.getenv("SUDO_USER") and os.getuid() != 0:
if switched_user and os.getuid() != 0:
# cleanup will happen by 'rm' of tempdir
# copy2 will preserve some metadata
shutil.copy2(src, tmp_dest.name)
@ -1010,7 +1056,8 @@ class AnsibleModule(object):
if self.selinux_enabled():
self.set_context_if_different(
tmp_dest.name, context, False)
if dest_stat:
tmp_stat = os.stat(tmp_dest.name)
if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
os.chown(tmp_dest.name, dest_stat.st_uid, dest_stat.st_gid)
os.rename(tmp_dest.name, dest)
except (shutil.Error, OSError, IOError), e:
@ -1023,7 +1070,7 @@ class AnsibleModule(object):
umask = os.umask(0)
os.umask(umask)
os.chmod(dest, 0666 ^ umask)
if os.getenv("SUDO_USER"):
if switched_user:
os.chown(dest, os.getuid(), os.getgid())
if self.selinux_enabled():

View File

@ -50,6 +50,8 @@ try:
except:
HAS_SSL=False
import os
import re
import socket
import tempfile
@ -77,6 +79,58 @@ zKPZsZ2miVGclicJHzm5q080b1p/sZtuKIEZk6vZqEg=
-----END CERTIFICATE-----
"""
def generic_urlparse(parts):
'''
Returns a dictionary of url parts as parsed by urlparse,
but accounts for the fact that older versions of that
library do not support named attributes (ie. .netloc)
'''
generic_parts = dict()
if hasattr(parts, 'netloc'):
# urlparse is newer, just read the fields straight
# from the parts object
generic_parts['scheme'] = parts.scheme
generic_parts['netloc'] = parts.netloc
generic_parts['path'] = parts.path
generic_parts['params'] = parts.params
generic_parts['query'] = parts.query
generic_parts['fragment'] = parts.fragment
generic_parts['username'] = parts.username
generic_parts['password'] = parts.password
generic_parts['hostname'] = parts.hostname
generic_parts['port'] = parts.port
else:
# we have to use indexes, and then parse out
# the other parts not supported by indexing
generic_parts['scheme'] = parts[0]
generic_parts['netloc'] = parts[1]
generic_parts['path'] = parts[2]
generic_parts['params'] = parts[3]
generic_parts['query'] = parts[4]
generic_parts['fragment'] = parts[5]
# get the username, password, etc.
try:
netloc_re = re.compile(r'^((?:\w)+(?::(?:\w)+)?@)?([A-Za-z0-9.-]+)(:\d+)?$')
(auth, hostname, port) = netloc_re.match(parts[1])
if port:
# the capture group for the port will include the ':',
# so remove it and convert the port to an integer
port = int(port[1:])
if auth:
# the capture group above inclues the @, so remove it
# and then split it up based on the first ':' found
auth = auth[:-1]
username, password = auth.split(':', 1)
generic_parts['username'] = username
generic_parts['password'] = password
generic_parts['hostname'] = hostnme
generic_parts['port'] = port
except:
generic_parts['username'] = None
generic_parts['password'] = None
generic_parts['hostname'] = None
generic_parts['port'] = None
return generic_parts
class RequestWithMethod(urllib2.Request):
'''
@ -103,6 +157,7 @@ class SSLValidationHandler(urllib2.BaseHandler):
http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-python
http://techknack.net/python-urllib2-handlers/
'''
CONNECT_COMMAND = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n"
def __init__(self, module, hostname, port):
self.module = module
@ -161,13 +216,42 @@ class SSLValidationHandler(urllib2.BaseHandler):
return (tmp_path, paths_checked)
def validate_proxy_response(self, response, valid_codes=[200]):
'''
make sure we get back a valid code from the proxy
'''
try:
(http_version, resp_code, msg) = re.match(r'(HTTP/\d\.\d) (\d\d\d) (.*)', response).groups()
if int(resp_code) not in valid_codes:
raise Exception
except:
self.module.fail_json(msg='Connection to proxy failed')
def http_request(self, req):
tmp_ca_cert_path, paths_checked = self.get_ca_certs()
https_proxy = os.environ.get('https_proxy')
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED)
ssl_s.connect((self.hostname, self.port))
ssl_s.close()
if https_proxy:
proxy_parts = generic_urlparse(urlparse.urlparse(https_proxy))
s.connect((proxy_parts.get('hostname'), proxy_parts.get('port')))
if proxy_parts.get('scheme') == 'http':
s.sendall(self.CONNECT_COMMAND % (self.hostname, self.port))
if proxy_parts.get('username'):
credentials = "%s:%s" % (proxy_parts.get('username',''), proxy_parts.get('password',''))
s.sendall('Proxy-Authorization: Basic %s\r\n' % credentials.encode('base64').strip())
s.sendall('\r\n')
connect_result = s.recv(4096)
self.validate_proxy_response(connect_result)
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED)
else:
self.module.fail_json(msg='Unsupported proxy scheme: %s. Currently ansible only supports HTTP proxies.' % proxy_parts.get('scheme'))
else:
s.connect((self.hostname, self.port))
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED)
# close the ssl connection
#ssl_s.unwrap()
s.close()
except (ssl.SSLError, socket.error), e:
# fail if we tried all of the certs but none worked
if 'connection refused' in str(e).lower():
@ -207,7 +291,7 @@ def url_argument_spec():
def fetch_url(module, url, data=None, headers=None, method=None,
use_proxy=False, force=False, last_mod_time=None, timeout=10):
use_proxy=True, force=False, last_mod_time=None, timeout=10):
'''
Fetches a file from an HTTP/FTP server using urllib2
'''
@ -227,6 +311,8 @@ def fetch_url(module, url, data=None, headers=None, method=None,
# Get validate_certs from the module params
validate_certs = module.params.get('validate_certs', True)
# FIXME: change the following to use the generic_urlparse function
# to remove the indexed references for 'parsed'
parsed = urlparse.urlparse(url)
if parsed[0] == 'https':
if not HAS_SSL and validate_certs:

View File

@ -179,7 +179,46 @@ class PlayBook(object):
# *****************************************************
def _load_playbook_from_file(self, path, vars={}):
def _get_playbook_vars(self, play_ds, existing_vars):
'''
Gets the vars specified with the play and blends them
with any existing vars that have already been read in
'''
new_vars = existing_vars.copy()
if 'vars' in play_ds:
if isinstance(play_ds['vars'], dict):
new_vars.update(play_ds['vars'])
elif isinstance(play_ds['vars'], list):
for v in play_ds['vars']:
new_vars.update(v)
return new_vars
# *****************************************************
def _get_include_info(self, play_ds, basedir, existing_vars={}):
'''
Gets any key=value pairs specified with the included file
name and returns the merged vars along with the path
'''
new_vars = existing_vars.copy()
tokens = shlex.split(play_ds.get('include', ''))
for t in tokens[1:]:
(k,v) = t.split("=", 1)
new_vars[k] = template(basedir, v, new_vars)
return (new_vars, tokens[0])
# *****************************************************
def _get_playbook_vars_files(self, play_ds, existing_vars_files):
new_vars_files = list(existing_vars_files)
if 'vars_files' in play_ds:
new_vars_files = utils.list_union(new_vars_files, play_ds['vars_files'])
return new_vars_files
# *****************************************************
def _load_playbook_from_file(self, path, vars={}, vars_files=[]):
'''
run top level error checking on playbooks and allow them to include other playbooks.
'''
@ -201,36 +240,29 @@ class PlayBook(object):
# a playbook (list of plays) decided to include some other list of plays
# from another file. The result is a flat list of plays in the end.
tokens = shlex.split(play['include'])
play_vars = self._get_playbook_vars(play, vars)
play_vars_files = self._get_playbook_vars_files(play, vars_files)
inc_vars, inc_path = self._get_include_info(play, basedir, play_vars)
play_vars.update(inc_vars)
incvars = vars.copy()
if 'vars' in play:
if isinstance(play['vars'], dict):
incvars.update(play['vars'])
elif isinstance(play['vars'], list):
for v in play['vars']:
incvars.update(v)
# allow key=value parameters to be specified on the include line
# to set variables
for t in tokens[1:]:
(k,v) = t.split("=", 1)
incvars[k] = template(basedir, v, incvars)
included_path = utils.path_dwim(basedir, template(basedir, tokens[0], incvars))
(plays, basedirs) = self._load_playbook_from_file(included_path, incvars)
included_path = utils.path_dwim(basedir, template(basedir, inc_path, play_vars))
(plays, basedirs) = self._load_playbook_from_file(included_path, vars=play_vars, vars_files=play_vars_files)
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(incvars)
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 incvars.iteritems()])
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()
# now add in the vars_files
p['vars_files'] = utils.list_union(p.get('vars_files', []), play_vars_files)
accumulated_plays.extend(plays)
play_basedirs.extend(basedirs)
@ -320,14 +352,14 @@ class PlayBook(object):
def _trim_unavailable_hosts(self, hostlist=[]):
''' returns a list of hosts that haven't failed and aren't dark '''
return [ h for h in self.inventory.list_hosts(hostlist) if (h not in self.stats.failures) and (h not in self.stats.dark)]
return [ h for h in hostlist if (h not in self.stats.failures) and (h not in self.stats.dark)]
# *****************************************************
def _run_task_internal(self, task):
''' run a particular module step in a playbook '''
hosts = self._trim_unavailable_hosts(task.play._play_hosts)
hosts = self._trim_unavailable_hosts(self.inventory.list_hosts(task.play._play_hosts))
self.inventory.restrict_to(hosts)
runner = ansible.runner.Runner(
@ -505,6 +537,7 @@ class PlayBook(object):
# push any variables down to the system
setup_results = ansible.runner.Runner(
basedir=self.basedir,
pattern=play.hosts,
module_name='setup',
module_args={},

View File

@ -294,26 +294,24 @@ class Play(object):
else:
self.included_roles.append(dep)
def _merge_conditional(cur_conditionals, new_conditionals):
if isinstance(new_conditionals, (basestring, bool)):
cur_conditionals.append(new_conditionals)
elif isinstance(new_conditionals, list):
cur_conditionals.extend(new_conditionals)
# pass along conditionals from roles to dep roles
if type(role) is dict:
if 'when' in passed_vars:
if 'when' in dep_vars:
tmpcond = []
passed_when = passed_vars.get('when')
role_when = role_vars.get('when')
dep_when = dep_vars.get('when')
if type(passed_vars['when']) is str:
tmpcond.append(passed_vars['when'])
elif type(passed_vars['when']) is list:
tmpcond += passed_vars['when']
tmpcond = []
_merge_conditional(tmpcond, passed_when)
_merge_conditional(tmpcond, role_when)
_merge_conditional(tmpcond, dep_when)
if type(dep_vars['when']) is str:
tmpcond.append(dep_vars['when'])
elif type(dep_vars['when']) is list:
tmpcond += dep_vars['when']
if len(tmpcond) > 0:
dep_vars['when'] = tmpcond
else:
dep_vars['when'] = passed_vars['when']
if len(tmpcond) > 0:
dep_vars['when'] = tmpcond
self._build_role_dependencies([dep], dep_stack, passed_vars=dep_vars, level=level+1)
dep_stack.append([dep,dep_path,dep_vars,dep_defaults_data])
@ -528,7 +526,6 @@ class Play(object):
if 'include' in x:
tokens = shlex.split(str(x['include']))
items = ['']
included_additional_conditions = list(additional_conditions)
include_vars = {}
for k in x:
@ -565,30 +562,31 @@ class Play(object):
task_vars = utils.combine_vars(task_vars, x['vars'])
if 'when' in x:
included_additional_conditions.append(x['when'])
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']
for item in items:
mv = task_vars.copy()
mv['item'] = item
for t in tokens[1:]:
(k,v) = t.split("=", 1)
mv[k] = template(self.basedir, v, mv)
dirname = self.basedir
if original_file:
dirname = os.path.dirname(original_file)
include_file = template(dirname, tokens[0], mv)
include_filename = utils.path_dwim(dirname, include_file)
data = utils.parse_yaml_from_file(include_filename, vault_password=self.vault_password)
if 'role_name' in x and data is not None:
for y in data:
if isinstance(y, dict) and 'include' in y:
y['role_name'] = new_role
loaded = self._load_tasks(data, mv, default_vars, included_sudo_vars, list(included_additional_conditions), original_file=include_filename, role_name=new_role)
results += loaded
mv = task_vars.copy()
for t in tokens[1:]:
(k,v) = t.split("=", 1)
mv[k] = template(self.basedir, v, mv)
dirname = self.basedir
if original_file:
dirname = os.path.dirname(original_file)
include_file = template(dirname, tokens[0], mv)
include_filename = utils.path_dwim(dirname, include_file)
data = utils.parse_yaml_from_file(include_filename, vault_password=self.vault_password)
if 'role_name' in x and data is not None:
for y in data:
if isinstance(y, dict) and 'include' in y:
y['role_name'] = new_role
loaded = self._load_tasks(data, mv, default_vars, included_sudo_vars, list(included_additional_conditions), original_file=include_filename, role_name=new_role)
results += loaded
elif type(x) == dict:
task = Task(
self, x,
@ -785,7 +783,7 @@ class Play(object):
""" update a host's varscache with new var data """
data = utils.combine_vars(inject, data)
self.playbook.VARS_CACHE[host].update(data)
self.playbook.VARS_CACHE[host] = utils.combine_vars(self.playbook.VARS_CACHE.get(host, {}), data)
self.playbook.callbacks.on_import_for_host(host, filename4)
def process_files(filename, filename2, filename3, filename4, host=None):
@ -855,5 +853,4 @@ class Play(object):
# finally, update the VARS_CACHE for the host, if it is set
if host is not None:
self.playbook.VARS_CACHE[host].update(self.vars)
self.playbook.VARS_CACHE[host].update(self.playbook.extra_vars)

View File

@ -92,7 +92,7 @@ class HostVars(dict):
def __getitem__(self, host):
if host not in self.lookup:
result = self.inventory.get_variables(host, vault_password=self.vault_password)
result = self.inventory.get_variables(host, vault_password=self.vault_password).copy()
result.update(self.vars_cache.get(host, {}))
self.lookup[host] = result
return self.lookup[host]
@ -566,11 +566,14 @@ class Runner(object):
# merge the VARS and SETUP caches for this host
combined_cache = self.setup_cache.copy()
combined_cache.get(host, {}).update(self.vars_cache.get(host, {}))
combined_cache.setdefault(host, {}).update(self.vars_cache.get(host, {}))
hostvars = HostVars(combined_cache, self.inventory, vault_password=self.vault_pass)
# use combined_cache and host_variables to template the module_vars
module_vars_inject = utils.combine_vars(combined_cache.get(host, {}), host_variables)
# we update the inject variables with the data we're about to template
# since some of the variables we'll be replacing may be contained there too
module_vars_inject = utils.combine_vars(host_variables, combined_cache.get(host, {}))
module_vars_inject = utils.combine_vars(self.module_vars, module_vars_inject)
module_vars = template.template(self.basedir, self.module_vars, module_vars_inject)
inject = {}
@ -890,7 +893,7 @@ class Runner(object):
if (module_name == 'async_status' and "finished" in data) or module_name != 'async_status':
if changed_when is not None and 'skipped' not in data:
data['changed'] = utils.check_conditional(changed_when, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars)
if failed_when is not None:
if failed_when is not None and 'skipped' not in data:
data['failed_when_result'] = data['failed'] = utils.check_conditional(failed_when, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars)
if is_chained:

View File

@ -77,7 +77,7 @@ class ActionModule(object):
groupnames = args.get('groupname', args.get('groups', args.get('group', '')))
# add it to the group if that was specified
if groupnames != '':
if groupnames:
for group_name in groupnames.split(","):
group_name = group_name.strip()
if not inventory.get_group(group_name):

View File

@ -38,6 +38,8 @@ class ActionModule(object):
# the group_by module does not need to pay attention to check mode.
# it always runs.
# module_args and complex_args have already been templated for the first host.
# Use them here only to check that a key argument is provided.
args = {}
if complex_args:
args.update(complex_args)
@ -68,7 +70,16 @@ class ActionModule(object):
break
if next_host:
continue
group_name = template.template(self.runner.basedir, args['key'], data)
# Template original module_args and complex_args from runner for each host.
host_module_args = template.template(self.runner.basedir, self.runner.module_args, data)
host_complex_args = template.template(self.runner.basedir, self.runner.complex_args, data)
host_args = {}
if host_complex_args:
host_args.update(host_complex_args)
host_args.update(parse_kv(host_module_args))
group_name = host_args['key']
group_name = group_name.replace(' ','-')
if group_name not in groups:
groups[group_name] = []

View File

@ -32,11 +32,12 @@ class ActionModule(object):
if 'vars' in self.inject:
if '_original_file' in self.inject['vars']:
# roles
original_path = path
path = utils.path_dwim_relative(self.inject['_original_file'], 'files', path, self.runner.basedir)
elif 'inventory_dir' in self.inject['vars']:
# non-roles
abs_dir = os.path.abspath(self.inject['vars']['inventory_dir'])
path = os.path.join(abs_dir, path)
if original_path and original_path[-1] == '/' and path[-1] != '/':
# make sure the dwim'd path ends in a trailing "/"
# if the original path did
path += '/'
return path

View File

@ -16,6 +16,7 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible import utils, errors
from ansible.utils import template
import os
class LookupModule(object):
@ -25,7 +26,10 @@ class LookupModule(object):
def run(self, terms, inject=None, **kwargs):
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
try:
terms = template.template(self.basedir, terms, inject)
except Exception, e:
pass
if isinstance(terms, basestring):
terms = [ terms ]

View File

@ -1005,6 +1005,23 @@ def is_list_of_strings(items):
return False
return True
def list_union(a, b):
result = []
for x in a:
if x not in result:
result.append(x)
for x in b:
if x not in result:
result.append(x)
return result
def list_intersection(a, b):
result = []
for x in a:
if x in b and x not in result:
result.append(x)
return result
def safe_eval(expr, locals={}, include_exceptions=False):
'''
this is intended for allowing things like:

View File

@ -384,7 +384,7 @@ class DockerManager:
self.env = None
if self.module.params.get('env'):
self.env = dict(map(lambda x: x.split("="), self.module.params.get('env')))
self.env = dict(map(lambda x: x.split("=", 1), self.module.params.get('env')))
# connect to docker server
docker_url = urlparse(module.params.get('docker_url'))
@ -507,14 +507,16 @@ class DockerManager:
running_image, running_tag = self.get_split_image_tag(i['Image'])
running_command = i['Command'].strip()
name_matches = (name and name in i['Names'])
name_matches = False
if i["Names"]:
name_matches = (name and name in i['Names'])
image_matches = (running_image == image)
tag_matches = (not tag or running_tag == tag)
# if a container has an entrypoint, `command` will actually equal
# '{} {}'.format(entrypoint, command)
command_matches = (not command or running_command.endswith(command))
if name_matches or (image_matches and tag_matches and command_matches):
if name_matches or (name is None and image_matches and tag_matches and command_matches):
details = self.client.inspect_container(i['Id'])
details = _docker_id_quirk(details)
deployed.append(details)
@ -524,7 +526,7 @@ class DockerManager:
def get_running_containers(self):
running = []
for i in self.get_deployed_containers():
if i['State']['Running'] == True and i['State']['Ghost'] == False:
if i['State']['Running'] == True and i['State'].get('Ghost', False) == False:
running.append(i)
return running

View File

@ -175,7 +175,7 @@ options:
instance_ids:
version_added: "1.3"
description:
- list of instance ids, currently only used when state='absent'
- list of instance ids, currently used for the states 'absent', 'running', and 'stopped'
required: false
default: null
aliases: []
@ -1025,7 +1025,7 @@ def terminate_instances(module, ec2, instance_ids):
return (changed, instance_dict_array, terminated_instance_ids)
def startstop_instances(module, ec2, instance_ids):
def startstop_instances(module, ec2, instance_ids, state):
"""
Starts or stops a list of existing instances
@ -1033,9 +1033,10 @@ def startstop_instances(module, ec2, instance_ids):
ec2: authenticated ec2 connection object
instance_ids: The list of instances to start in the form of
[ {id: <inst-id>}, ..]
state: Intended state ("running" or "stopped")
Returns a dictionary of instance information
about the instances started.
about the instances started/stopped.
If the instance was not able to change state,
"changed" will be set to False.
@ -1050,18 +1051,15 @@ def startstop_instances(module, ec2, instance_ids):
if not isinstance(instance_ids, list) or len(instance_ids) < 1:
module.fail_json(msg='instance_ids should be a list of instances, aborting')
dest_state = module.params.get('state')
dest_state_ec2 = 'stopped' if dest_state == 'stopped' else 'running'
# Check that our instances are not in the state we want to take them to
# and change them to our desired state
running_instances_array = []
for res in ec2.get_all_instances(instance_ids):
for inst in res.instances:
if not inst.state == dest_state_ec2:
if inst.state != state:
instance_dict_array.append(get_instance_info(inst))
try:
if dest_state == 'running':
if state == 'running':
inst.start()
else:
inst.stop()
@ -1070,21 +1068,14 @@ def startstop_instances(module, ec2, instance_ids):
changed = True
## Wait for all the instances to finish starting or stopping
instids = [ i.id for i in res.instances ]
this_res = []
num_running = 0
wait_timeout = time.time() + wait_timeout
while wait_timeout > time.time() and num_running < len(instids):
res_list = res.connection.get_all_instances(instids)
if len(res_list) > 0:
this_res = res_list[0]
num_running = len([ i for i in this_res.instances if i.state == dest_state_ec2 ])
else:
# got a bad response of some sort, possibly due to
# stale/cached data. Wait a second and then try again
time.sleep(1)
continue
if wait and num_running < len(instids):
while wait and wait_timeout > time.time():
matched_instances = []
for res in ec2.get_all_instances(instance_ids):
for i in res.instances:
if i.state == state:
matched_instances.append(i)
if len(matched_instances) < len(instance_ids):
time.sleep(5)
else:
break
@ -1093,10 +1084,7 @@ def startstop_instances(module, ec2, instance_ids):
# waiting took too long
module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
for inst in this_res.instances:
running_instances_array.append(inst.id)
return (changed, instance_dict_array, running_instances_array)
return (changed, instance_dict_array, instance_ids)
def main():
@ -1147,21 +1135,23 @@ def main():
tagged_instances = []
if module.params.get('state') == 'absent':
state = module.params.get('state')
if state == 'absent':
instance_ids = module.params.get('instance_ids')
if not isinstance(instance_ids, list):
module.fail_json(msg='termination_list needs to be a list of instances to terminate')
(changed, instance_dict_array, new_instance_ids) = terminate_instances(module, ec2, instance_ids)
elif module.params.get('state') == 'running' or module.params.get('state') == 'stopped':
elif state in ('running', 'stopped'):
instance_ids = module.params.get('instance_ids')
if not isinstance(instance_ids, list):
module.fail_json(msg='running list needs to be a list of instances to run: %s' % instance_ids)
(changed, instance_dict_array, new_instance_ids) = startstop_instances(module, ec2, instance_ids)
(changed, instance_dict_array, new_instance_ids) = startstop_instances(module, ec2, instance_ids, state)
elif module.params.get('state') == 'present':
elif state == 'present':
# Changed is always set to true when provisioning new instances
if not module.params.get('image'):
module.fail_json(msg='image parameter is required for new instance')

View File

@ -230,6 +230,10 @@ def main():
# to 0.0.0.0/0 is added automatically but it's not
# reflected in the object returned by the AWS API
# call. We re-read the group for getting an updated object
# amazon sometimes takes a couple seconds to update the security group so wait till it exists
while len(ec2.get_all_security_groups(filters={ 'group_id': group.id, })) == 0:
time.sleep(0.1)
group = ec2.get_all_security_groups(group_ids=(group.id,))[0]
changed = True
else:
@ -317,7 +321,8 @@ def main():
# when using a vpc, but no egress rules are specified,
# we add in a default allow all out rule, which was the
# default behavior before egress rules were added
if 'out--1-None-None-None-0.0.0.0/0' not in groupRules:
default_egress_rule = 'out--1-None-None-None-0.0.0.0/0'
if default_egress_rule not in groupRules:
ec2.authorize_security_group_egress(
group_id=group.id,
ip_protocol=-1,
@ -327,6 +332,9 @@ def main():
cidr_ip='0.0.0.0/0'
)
changed = True
else:
# make sure the default egress rule is not removed
del groupRules[default_egress_rule]
# Finally, remove anything left in the groupRules -- these will be defunct rules
for rule in groupRules.itervalues():

View File

@ -370,6 +370,9 @@ def main():
else:
return 'vpc_security_groups'
# Package up the optional parameters
params = {}
# Validate parameters for each command
if command == 'create':
required_vars = [ 'instance_name', 'db_engine', 'size', 'instance_type', 'username', 'password' ]
@ -413,9 +416,6 @@ def main():
if module.params.get(v):
module.fail_json(msg = str("Parameter %s invalid for %s command" % (v, command)))
# Package up the optional parameters
params = {}
if db_engine:
params["engine"] = db_engine

View File

@ -223,7 +223,7 @@ def main():
# Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round
# tripping of things like * and @.
decoded_name = rset.name.replace(r'\052', '*')
decoded_name = rset.name.replace(r'\100', '@')
decoded_name = decoded_name.replace(r'\100', '@')
if rset.type == type_in and decoded_name == record_in:
found_record = True

View File

@ -976,7 +976,15 @@ class DefaultVMConfig(object):
for k, v in value.items():
if k in self.check_dict[key]:
if not isinstance(self.check_dict[key][k], v):
self.recursive_missing.append((k, v))
try:
if v == int:
self.check_dict[key][k] = int(self.check_dict[key][k])
elif v == basestring:
self.check_dict[key][k] = str(self.check_dict[key][k])
else:
raise ValueError
except ValueError:
self.recursive_missing.append((k, v))
else:
self.recursive_missing.append((k, v))

View File

@ -240,7 +240,7 @@ def main():
master_log_pos=dict(default=None),
relay_log_file=dict(default=None),
relay_log_pos=dict(default=None),
master_ssl=dict(default=None, choices=[0,1]),
master_ssl=dict(default=False, type='bool'),
master_ssl_ca=dict(default=None),
master_ssl_capath=dict(default=None),
master_ssl_cert=dict(default=None),
@ -337,7 +337,7 @@ def main():
if relay_log_pos:
chm.append("RELAY_LOG_POS=" + relay_log_pos)
if master_ssl:
chm.append("MASTER_SSL=" + master_ssl)
chm.append("MASTER_SSL=1")
if master_ssl_ca:
chm.append("MASTER_SSL_CA='" + master_ssl_ca + "'")
if master_ssl_capath:
@ -347,7 +347,7 @@ def main():
if master_ssl_key:
chm.append("MASTER_SSL_KEY='" + master_ssl_key + "'")
if master_ssl_cipher:
chm.append("MASTER_SSL_CIPTHER='" + master_ssl_cipher + "'")
chm.append("MASTER_SSL_CIPHER='" + master_ssl_cipher + "'")
changemaster(cursor,chm)
module.exit_json(changed=True)
elif mode in "startslave":

View File

@ -204,6 +204,8 @@ def main():
os.unlink(dest)
open(dest, 'w').close()
if validate:
if "%s" not in validate:
module.fail_json(msg="validate must contain %%s: %s" % (validate))
(rc,out,err) = module.run_command(validate % src)
if rc != 0:
module.fail_json(msg="failed to validate: rc:%s error:%s" % (rc,err))

View File

@ -182,11 +182,13 @@ def main():
elif state in ['link','hard']:
absrc = src
if not os.path.isabs(absrc):
absrc = os.path.normpath('%s/%s' % (os.path.dirname(path), absrc))
if os.path.isdir(path) and not os.path.islink(path):
relpath = path
else:
relpath = os.path.dirname(path)
if not os.path.exists(absrc) and not force:
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:
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':

View File

@ -154,6 +154,8 @@ def write_changes(module,lines,dest):
validate = module.params.get('validate', None)
valid = not validate
if validate:
if "%s" not in validate:
module.fail_json(msg="validate must contain %%s: %s" % (validate))
(rc, out, err) = module.run_command(validate % tmpfile)
valid = rc == 0
if rc != 0:

View File

@ -90,6 +90,8 @@ def write_changes(module,contents,dest):
validate = module.params.get('validate', None)
valid = not validate
if validate:
if "%s" not in validate:
module.fail_json(msg="validate must contain %%s: %s" % (validate))
(rc, out, err) = module.run_command(validate % tmpfile)
valid = rc == 0
if rc != 0:

View File

@ -406,7 +406,6 @@ def main():
# Write the file out if requested
if dest is not None:
if resp['status'] == 304:
status_code = 304
changed = False
else:
write_file(module, url, dest, content)

View File

@ -83,10 +83,11 @@ import tempfile
try:
import apt
import apt_pkg
import aptsources.distro
distro = aptsources.distro.get_distro()
import aptsources.distro as aptsources_distro
distro = aptsources_distro.get_distro()
HAVE_PYTHON_APT = True
except ImportError:
distro = None
HAVE_PYTHON_APT = False
@ -97,11 +98,16 @@ def install_python_apt(module):
if not module.check_mode:
apt_get_path = module.get_bin_path('apt-get')
if apt_get_path:
rc, so, se = module.run_command('%s update && %s install python-apt -y -q' % (apt_get_path, apt_get_path))
rc, so, se = module.run_command('%s update && %s install python-apt -y -q' % (apt_get_path, apt_get_path), use_unsafe_shell=True)
if rc == 0:
global apt, apt_pkg
global apt, apt_pkg, aptsources_distro, distro, HAVE_PYTHON_APT
import apt
import apt_pkg
import aptsources.distro as aptsources_distro
distro = aptsources_distro.get_distro()
HAVE_PYTHON_APT = True
else:
module.fail_json(msg="Failed to auto-install python-apt. Error was: '%s'" % se.strip())
class InvalidSource(Exception):
pass
@ -314,6 +320,8 @@ class UbuntuSourcesList(SourcesList):
headers = dict(Accept='application/json')
response, info = fetch_url(self.module, lp_api, headers=headers)
if info['status'] != 200:
self.module.fail_json(msg="failed to fetch PPA information, error was: %s" % info['msg'])
return json.load(response)
def _expand_ppa(self, path):
@ -382,14 +390,16 @@ def main():
update_cache = module.params['update_cache']
sourceslist = None
if isinstance(distro, aptsources.distro.UbuntuDistribution):
sourceslist = UbuntuSourcesList(module,
add_ppa_signing_keys_callback=get_add_ppa_signing_key_callback(module))
elif isinstance(distro, aptsources.distro.DebianDistribution) or \
isinstance(distro, aptsources.distro.Distribution):
sourceslist = SourcesList()
if HAVE_PYTHON_APT:
if isinstance(distro, aptsources_distro.UbuntuDistribution):
sourceslist = UbuntuSourcesList(module,
add_ppa_signing_keys_callback=get_add_ppa_signing_key_callback(module))
elif HAVE_PYTHON_APT and \
isinstance(distro, aptsources_distro.DebianDistribution) or isinstance(distro, aptsources_distro.Distribution):
sourceslist = SourcesList()
else:
module.fail_json(msg='Module apt_repository supports only Debian and Ubuntu.')
module.fail_json(msg='Module apt_repository supports only Debian and Ubuntu. ' + \
'You may be seeing this because python-apt is not installed, but you requested that it not be auto-installed')
sources_before = sourceslist.dump()

View File

@ -152,7 +152,7 @@ def package_install(module, name, src, proxy, response_file, zone, category):
os.unlink(adminfile)
return (rc, out, err)
def package_uninstall(module, name, src):
def package_uninstall(module, name, src, category):
adminfile = create_admin_file()
if category:
cmd = [ 'pkgrm', '-na', adminfile, '-Y', name ]
@ -209,10 +209,18 @@ def main():
(rc, out, err) = package_uninstall(module, name, src, category)
out = out[:75]
if rc is None:
result['changed'] = False
else:
# Success, Warning, Interruption, Reboot all, Reboot this return codes
if rc in (0, 2, 3, 10, 20):
result['changed'] = True
# no install nor uninstall, or failed
else:
result['changed'] = False
# Fatal error, Administration, Administration Interaction return codes
if rc in (1, 4 , 5):
result['failed'] = True
else:
result['failed'] = False
if out:
result['stdout'] = out

View File

@ -181,7 +181,15 @@ def get_submodule_update_params(module, git_path, cwd):
def write_ssh_wrapper():
module_dir = get_module_path()
fd, wrapper_path = tempfile.mkstemp(prefix=module_dir + '/')
try:
# make sure we have full permission to the module_dir, which
# may not be the case if we're sudo'ing to a non-root user
if os.access(module_dir, os.W_OK|os.R_OK|os.X_OK):
fd, wrapper_path = tempfile.mkstemp(prefix=module_dir + '/')
else:
raise OSError
except (IOError, OSError):
fd, wrapper_path = tempfile.mkstemp()
fh = os.fdopen(fd, 'w+b')
template = """#!/bin/sh
if [ -z "$GIT_SSH_OPTS" ]; then
@ -502,6 +510,7 @@ def main():
if key_file or ssh_opts:
ssh_wrapper = write_ssh_wrapper()
set_git_ssh(ssh_wrapper, key_file, ssh_opts)
module.add_cleanup_file(path=ssh_wrapper)
# add the git repo's hostkey
if module.params['ssh_opts'] is not None:

View File

@ -53,7 +53,6 @@ EXAMPLES = '''
alternatives: name=hadoop-conf link=/etc/hadoop/conf path=/etc/hadoop/conf.ansible
'''
UPDATE_ALTERNATIVES = '/usr/sbin/update-alternatives'
DEFAULT_LINK_PRIORITY = 50
def main():
@ -71,6 +70,8 @@ def main():
path = params['path']
link = params['link']
UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True)
current_path = None
all_alternatives = []

View File

@ -479,7 +479,7 @@ class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
if executable:
use_unsafe_shell = True
rc, stdout, stderr = self.server.module.run_command(data['cmd'], executable=executable, use_unsafe_shell=use_unsafe_shell)
rc, stdout, stderr = self.server.module.run_command(data['cmd'], executable=executable, use_unsafe_shell=use_unsafe_shell, close_fds=True)
if stdout is None:
stdout = ''
if stderr is None:

View File

@ -1,7 +1,7 @@
#!/usr/bin/python
#coding: utf-8 -*-
# (c) 2013, Christian Berendt <berendt@b1-systems.de>
# (c) 2013-2014, Christian Berendt <berendt@b1-systems.de>
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -44,39 +44,31 @@ EXAMPLES = '''
- apache2_module: state=absent name=wsgi
'''
def _module_is_enabled(module):
name = module.params['name']
a2enmod_binary = module.get_bin_path("a2enmod")
result, stdout, stderr = module.run_command("%s -q %s" % (a2enmod_binary, name))
return result == 0
def _module_is_disabled(module):
return _module_is_enabled(module) == False
import re
def _disable_module(module):
name = module.params['name']
a2dismod_binary = module.get_bin_path("a2dismod")
result, stdout, stderr = module.run_command("%s %s" % (a2dismod_binary, name))
if _module_is_disabled(module):
if re.match(r'.*already disabled.*', stdout):
module.exit_json(changed = False, result = "Success")
result, stdout, stderr = module.run_command("a2dismod %s" % name)
if result != 0:
elif result != 0:
module.fail_json(msg="Failed to disable module %s: %s" % (name, stdout))
module.exit_json(changed = True, result = "Disabled")
else:
module.exit_json(changed = True, result = "Disabled")
def _enable_module(module):
name = module.params['name']
if _module_is_enabled(module):
module.exit_json(changed = False, result = "Success")
a2enmod_binary = module.get_bin_path("a2enmod")
result, stdout, stderr = module.run_command("%s %s" % (a2enmod_binary, name))
if result != 0:
module.fail_json(msg="Failed to enable module %s: %s" % (name, stdout))
module.exit_json(changed = True, result = "Enabled")
if re.match(r'.*already enabled.*', stdout):
module.exit_json(changed = False, result = "Success")
elif result != 0:
module.fail_json(msg="Failed to enable module %s: %s" % (name, stdout))
else:
module.exit_json(changed = True, result = "Enabled")
def main():
module = AnsibleModule(
@ -95,4 +87,3 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@ -1,8 +1,26 @@
ansible (1.6.3) unstable; urgency=low
* 1.6.3 release
-- Michael DeHaan <michael@ansible.com> Mon, 09 Jun 2014 16:30:00 -0500
ansible (1.6.2) unstable; urgency=low
* 1.6.2 release
-- Michael DeHaan <michael@ansible.com> Fri, 23 May 2014 17:30:00 -0500
ansible (1.6.1) unstable; urgency=low
* 1.6.1 release
-- Michael DeHaan <michael@ansible.com> Wed, 07 May 2014 13:30:00 -0500
ansible (1.6) unstable; urgency=low
* 1.6.0 release
-- Michael DeHaan <michael.dehaan@gmail.com> Mon, 05 May 2014 17:30:03 -0500
-- Michael DeHaan <michael@ansible.com> Mon, 05 May 2014 17:30:03 -0500
ansible (1.5.5) unstable; urgency=low

View File

@ -1,11 +1,12 @@
%define name ansible
%define ansible_version $VERSION
%if 0%{?rhel} == 5
%define __python /usr/bin/python26
%endif
Name: %{name}
Version: 1.6
Version: %{ansible_version}
Release: 1%{?dist}
Url: http://www.ansible.com
Summary: SSH-based application deployment, configuration management, and IT orchestration platform
@ -114,6 +115,15 @@ rm -rf %{buildroot}
%changelog
* Mon Jun 09 2014 Michael DeHaan <michael@ansible.com> - 1.6.3
- Release 1.6.3
* Fri May 23 2014 Michael DeHaan <michael@ansible.com> - 1.6.2
- Release 1.6.2
* Wed May 07 2014 Michael DeHaan <michael@ansible.com> - 1.6.1
- Release 1.6.1
* Mon May 05 2014 Michael DeHaan <michael@ansible.com> - 1.6.0
- Release 1.6.0

View File

@ -14,7 +14,7 @@ else
CREDENTIALS_ARG =
endif
all: non_destructive destructive check_mode test_hash test_handlers
all: non_destructive destructive check_mode test_hash test_handlers test_group_by
non_destructive:
ansible-playbook non_destructive.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
@ -25,6 +25,9 @@ destructive:
check_mode:
ansible-playbook check_mode.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v --check $(TEST_FLAGS)
test_group_by:
ansible-playbook test_group_by.yml -i inventory.group_by -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)
test_handlers:
ansible-playbook test_handlers.yml -i inventory.handlers -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS)

View File

@ -0,0 +1,6 @@
[lamini]
alpaca genus=vicugna
llama genus=lama
[lamini:vars]
ansible_connection=local

View File

@ -63,7 +63,6 @@
that:
- "'invocation' in apt_result"
- "'changed' in apt_result"
- "'item' in apt_result"
- "'stderr' in apt_result"
- "'stdout' in apt_result"
- "'stdout_lines' in apt_result"

View File

@ -35,7 +35,6 @@
- "'end' in async_result"
- "'finished' in async_result"
- "'invocation' in async_result"
- "'item' in async_result"
- "'rc' in async_result"
- "'start' in async_result"
- "'stderr' in async_result"

View File

@ -191,7 +191,6 @@
that:
- 'result.changed'
- 'result.group_id.startswith("sg-")'
- 'result.item == ""'
# ============================================================
- name: test state=present (expected changed=false)
@ -209,7 +208,6 @@
that:
- 'not result.changed'
- 'result.group_id.startswith("sg-")'
- 'result.item == ""'
# ============================================================
- name: test state=absent (expected changed=true)
@ -228,7 +226,6 @@
that:
- 'result.changed'
- 'not result.group_id'
- 'result.item == ""'
# ============================================================
- name: test state=absent (expected changed=false)
@ -247,4 +244,3 @@
that:
- 'not result.changed'
- 'not result.group_id'
- 'result.item == ""'

View File

@ -1,3 +1,2 @@
dependencies:
- prepare_tests
dependencies: []

View File

@ -16,8 +16,23 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: notify the handler
- name: reset handler_called variable to false for all hosts
set_fact:
handler_called: False
- name: notify the handler for host A only
shell: echo
notify:
- set handler fact
when: inventory_hostname == 'A'
- name: force handler execution now
meta: "flush_handlers"
- debug: var=handler_called
- name: validate the handler only ran on one host
assert:
that:
- "inventory_hostname == 'A' and handler_called == True or handler_called == False"

View File

@ -69,7 +69,6 @@
that:
- "'invocation' in yum_result"
- "'changed' in yum_result"
- "'item' in yum_result"
- "'msg' in yum_result"
- "'rc' in yum_result"
- "'results' in yum_result"

View File

@ -0,0 +1,111 @@
# test code for the group_by module
# (c) 2014, Chris Church <cchurch@ansible.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- hosts: lamini
gather_facts: false
tasks:
- name: group by genus
group_by: key={{ genus }}
- name: group by first three letters of genus with key in quotes
group_by: key="{{ genus | truncate(3, true, '') }}"
- name: group by first two letters of genus with key not in quotes
group_by: key={{ genus | truncate(2, true, '') }}
- name: group by genus in uppercase using complex args
group_by: { key: "{{ genus | upper() }}" }
- hosts: vicugna
gather_facts: false
tasks:
- name: verify that only the alpaca is in this group
assert: { that: "inventory_hostname == 'alpaca'" }
- name: set a fact to check that we ran this play
set_fact: genus_vicugna=true
- hosts: lama
gather_facts: false
tasks:
- name: verify that only the llama is in this group
assert: { that: "inventory_hostname == 'llama'" }
- name: set a fact to check that we ran this play
set_fact: genus_lama=true
- hosts: vic
gather_facts: false
tasks:
- name: verify that only the alpaca is in this group
assert: { that: "inventory_hostname == 'alpaca'" }
- name: set a fact to check that we ran this play
set_fact: genus_vic=true
- hosts: lam
gather_facts: false
tasks:
- name: verify that only the llama is in this group
assert: { that: "inventory_hostname == 'llama'" }
- name: set a fact to check that we ran this play
set_fact: genus_lam=true
- hosts: vi
gather_facts: false
tasks:
- name: verify that only the alpaca is in this group
assert: { that: "inventory_hostname == 'alpaca'" }
- name: set a fact to check that we ran this play
set_fact: genus_vi=true
- hosts: la
gather_facts: false
tasks:
- name: verify that only the llama is in this group
assert: { that: "inventory_hostname == 'llama'" }
- name: set a fact to check that we ran this play
set_fact: genus_la=true
- hosts: VICUGNA
gather_facts: false
tasks:
- name: verify that only the alpaca is in this group
assert: { that: "inventory_hostname == 'alpaca'" }
- name: set a fact to check that we ran this play
set_fact: genus_VICUGNA=true
- hosts: LAMA
gather_facts: false
tasks:
- name: verify that only the llama is in this group
assert: { that: "inventory_hostname == 'llama'" }
- name: set a fact to check that we ran this play
set_fact: genus_LAMA=true
- hosts: '{{genus'
gather_facts: false
tasks:
- name: no hosts should match this group
fail: msg="should never get here"
- hosts: alpaca
gather_facts: false
tasks:
- name: check that alpaca matched all four groups
assert: { that: ["genus_vicugna", "genus_vic", "genus_vi", "genus_VICUGNA"] }
- hosts: llama
gather_facts: false
tasks:
- name: check that llama matched all four groups
assert: { that: ["genus_lama", "genus_lam", "genus_la", "genus_LAMA"] }

View File

@ -17,8 +17,9 @@
- "not hostvars[inventory_hostname]['handler1_called']"
- "'handler2_called' in hostvars[inventory_hostname]"
#- hosts: testgroup
# gather_facts: False
# connection: local
# roles:
# - { role: test_handlers_meta }
- name: test handlers
hosts: testgroup
gather_facts: False
connection: local
roles:
- { role: test_handlers }