Compare commits
67 Commits
threading_
...
release1.6
| Author | SHA1 | Date | |
|---|---|---|---|
| cc4129f6a1 | |||
| c35bbdaa21 | |||
| 84c40fc111 | |||
| 2c6c1b99c7 | |||
| 2aa7ce006f | |||
| b8b96b2109 | |||
| dffea7f877 | |||
| 7059528059 | |||
| fadf02d0c6 | |||
| 3ebecdd1ed | |||
| 58a0bcbe51 | |||
| 10074d3622 | |||
| 2625196f5d | |||
| e85a9abe75 | |||
| b99ea2723a | |||
| 7cb7fdc2a7 | |||
| 936e1de18b | |||
| c4b6c0ab84 | |||
| 4dd5eeb1c4 | |||
| d8c8c0823a | |||
| 3801be693b | |||
| 75adb3978b | |||
| 4e4ffa669a | |||
| ce80c0b8af | |||
| b50a64b0a1 | |||
| adfbcb8760 | |||
| 5128bced6b | |||
| 549f67c0d8 | |||
| d98b6ae8ab | |||
| ce4883bcc5 | |||
| a75a84b44a | |||
| f132e99b2a | |||
| 4d0ce5bd41 | |||
| b01da1b780 | |||
| 1248d2ce8a | |||
| d72cd70cc8 | |||
| 362626d07c | |||
| a54b8764fa | |||
| 6b4584d004 | |||
| cb0842ab49 | |||
| 2432b45eb1 | |||
| d6214c5d56 | |||
| c5b6d23c63 | |||
| e83f14200c | |||
| 7c0431b1e4 | |||
| 1c5055a401 | |||
| 24d885e368 | |||
| 8851ceaa9e | |||
| 854bf14222 | |||
| d8526a6c80 | |||
| e0fe6ca5d8 | |||
| ca2a12aa5b | |||
| 29018cf2a7 | |||
| 9d8eee633d | |||
| 8cc71a6eec | |||
| 3565249471 | |||
| 595813a1fe | |||
| 0de306574e | |||
| d33486d52a | |||
| 0dabb37337 | |||
| 9a930fb481 | |||
| d4e11c5c69 | |||
| 08f4984d92 | |||
| 7daaabdedb | |||
| 9943c50863 | |||
| ce15813e70 | |||
| f05d457560 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,6 +35,7 @@ docsite/searchindex.js
|
||||
docsite/htmlout
|
||||
# deb building stuff...
|
||||
debian/
|
||||
deb-build
|
||||
# Vim swap files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@ -1,7 +1,39 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
## 1.6 "And the Cradle Will Rock" - Active Development
|
||||
## 1.6.4 "And the Cradle Will Rock" - Jun 25, 2014
|
||||
|
||||
- Security update to prevent local operations from executing as the result of specifically crafted untrusted data
|
||||
|
||||
## 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:
|
||||
|
||||
|
||||
87
Makefile
87
Makefile
@ -5,7 +5,9 @@
|
||||
#
|
||||
# useful targets:
|
||||
# make sdist ---------------- produce a tarball
|
||||
# make srpm ----------------- produce a SRPM
|
||||
# make rpm ----------------- produce RPMs
|
||||
# make deb-src -------------- produce a DEB source
|
||||
# make deb ------------------ produce a DEB
|
||||
# make docs ----------------- rebuild the manpages (results are checked in)
|
||||
# make tests ---------------- run the tests
|
||||
@ -14,7 +16,7 @@
|
||||
########################################################
|
||||
# variable section
|
||||
|
||||
NAME = "ansible"
|
||||
NAME = ansible
|
||||
OS = $(shell uname -s)
|
||||
|
||||
# Manpages are currently built with asciidoc -- would like to move to markdown
|
||||
@ -45,6 +47,29 @@ else
|
||||
DATE := $(shell date --utc --date="$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
endif
|
||||
|
||||
# DEB build parameters
|
||||
DEBUILD_BIN ?= debuild
|
||||
DEBUILD_OPTS = --source-option="-I"
|
||||
DPUT_BIN ?= dput
|
||||
DPUT_OPTS ?=
|
||||
ifeq ($(OFFICIAL),yes)
|
||||
DEB_RELEASE = 1ppa
|
||||
# Sign OFFICIAL builds using 'DEBSIGN_KEYID'
|
||||
# DEBSIGN_KEYID is required when signing
|
||||
ifneq ($(DEBSIGN_KEYID),)
|
||||
DEBUILD_OPTS += -k$(DEBSIGN_KEYID)
|
||||
endif
|
||||
else
|
||||
DEB_RELEASE = 0.git$(DATE)
|
||||
# Do not sign unofficial builds
|
||||
DEBUILD_OPTS += -uc -us
|
||||
DPUT_OPTS += -u
|
||||
endif
|
||||
DEBUILD = $(DEBUILD_BIN) $(DEBUILD_OPTS)
|
||||
DEB_PPA ?= ppa
|
||||
# Choose the desired Ubuntu release: lucid precise saucy trusty
|
||||
DEB_DIST ?= unstable
|
||||
|
||||
# RPM build parameters
|
||||
RPMSPECDIR= packaging/rpm
|
||||
RPMSPEC = $(RPMSPECDIR)/ansible.spec
|
||||
@ -55,6 +80,10 @@ ifeq ($(OFFICIAL),)
|
||||
endif
|
||||
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
|
||||
|
||||
# MOCK build parameters
|
||||
MOCK_BIN ?= mock
|
||||
MOCK_CFG ?=
|
||||
|
||||
NOSETESTS ?= nosetests
|
||||
|
||||
########################################################
|
||||
@ -129,6 +158,20 @@ rpmcommon: $(MANPAGES) sdist
|
||||
@cp dist/*.gz rpm-build/
|
||||
@sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}#' $(RPMSPEC) >rpm-build/$(NAME).spec
|
||||
|
||||
mock-srpm: /etc/mock/$(MOCK_CFG).cfg rpmcommon
|
||||
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build/ --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build/
|
||||
@echo "#############################################"
|
||||
@echo "Ansible SRPM is built:"
|
||||
@echo rpm-build/*.src.rpm
|
||||
@echo "#############################################"
|
||||
|
||||
mock-rpm: /etc/mock/$(MOCK_CFG).cfg mock-srpm
|
||||
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build/ --rebuild rpm-build/$(NAME)-*.src.rpm
|
||||
@echo "#############################################"
|
||||
@echo "Ansible RPM is built:"
|
||||
@echo rpm-build/*.noarch.rpm
|
||||
@echo "#############################################"
|
||||
|
||||
srpm: rpmcommon
|
||||
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
|
||||
--define "_builddir %{_topdir}" \
|
||||
@ -160,12 +203,44 @@ rpm: rpmcommon
|
||||
@echo "#############################################"
|
||||
|
||||
debian: sdist
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
mkdir -p deb-build/$${DIST} ; \
|
||||
tar -C deb-build/$${DIST} -xvf dist/$(NAME)-$(VERSION).tar.gz ; \
|
||||
cp -a packaging/debian deb-build/$${DIST}/$(NAME)-$(VERSION)/ ; \
|
||||
sed -ie "s#^$(NAME) (\([^)]*\)) \([^;]*\);#ansible (\1-$(DEB_RELEASE)~$${DIST}) $${DIST};#" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
|
||||
done
|
||||
|
||||
deb: debian
|
||||
cp -r packaging/debian ./
|
||||
chmod 755 debian/rules
|
||||
fakeroot debian/rules clean
|
||||
fakeroot dh_install
|
||||
fakeroot debian/rules binary
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -b) ; \
|
||||
done
|
||||
@echo "#############################################"
|
||||
@echo "Ansible DEB artifacts:"
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
echo deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \
|
||||
done
|
||||
@echo "#############################################"
|
||||
|
||||
deb-src: debian
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -S) ; \
|
||||
done
|
||||
@echo "#############################################"
|
||||
@echo "Ansible DEB artifacts:"
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
echo deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_source.changes ; \
|
||||
done
|
||||
@echo "#############################################"
|
||||
|
||||
deb-upload: deb
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \
|
||||
done
|
||||
|
||||
deb-src-upload: deb-src
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_source.changes ; \
|
||||
done
|
||||
|
||||
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
|
||||
|
||||
|
||||
17
RELEASES.txt
17
RELEASES.txt
@ -1,21 +1,16 @@
|
||||
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.4 "The Cradle Will Rock" - 06-25-2014
|
||||
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
|
||||
|
||||
@ -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.4'
|
||||
__author__ = 'Michael DeHaan'
|
||||
|
||||
@ -31,7 +31,7 @@ def mk_boolean(value):
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False):
|
||||
def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False):
|
||||
''' return a configuration variable with casting '''
|
||||
value = _get_config(p, section, key, env_var, default)
|
||||
if boolean:
|
||||
@ -40,6 +40,8 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False,
|
||||
return int(value)
|
||||
if value and floating:
|
||||
return float(value)
|
||||
if value and islist:
|
||||
return [x.strip() for x in value.split(',')]
|
||||
return value
|
||||
|
||||
def _get_config(p, section, key, env_var, default):
|
||||
@ -129,12 +131,12 @@ DEFAULT_SUDO_FLAGS = get_config(p, DEFAULTS, 'sudo_flags', 'ANSIBLE_SUDO_
|
||||
DEFAULT_HASH_BEHAVIOUR = get_config(p, DEFAULTS, 'hash_behaviour', 'ANSIBLE_HASH_BEHAVIOUR', 'replace')
|
||||
DEFAULT_JINJA2_EXTENSIONS = get_config(p, DEFAULTS, 'jinja2_extensions', 'ANSIBLE_JINJA2_EXTENSIONS', None)
|
||||
DEFAULT_EXECUTABLE = get_config(p, DEFAULTS, 'executable', 'ANSIBLE_EXECUTABLE', '/bin/sh')
|
||||
DEFAULT_SU_EXE = get_config(p, DEFAULTS, 'su_exe', 'ANSIBLE_SU_EXE', 'su')
|
||||
DEFAULT_SU = get_config(p, DEFAULTS, 'su', 'ANSIBLE_SU', False, boolean=True)
|
||||
DEFAULT_SU_FLAGS = get_config(p, DEFAULTS, 'su_flags', 'ANSIBLE_SU_FLAGS', '')
|
||||
DEFAULT_SU_USER = get_config(p, DEFAULTS, 'su_user', 'ANSIBLE_SU_USER', 'root')
|
||||
DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_SU_PASS', False, boolean=True)
|
||||
DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower()
|
||||
DEFAULT_SU_EXE = get_config(p, DEFAULTS, 'su_exe', 'ANSIBLE_SU_EXE', 'su')
|
||||
DEFAULT_SU = get_config(p, DEFAULTS, 'su', 'ANSIBLE_SU', False, boolean=True)
|
||||
DEFAULT_SU_FLAGS = get_config(p, DEFAULTS, 'su_flags', 'ANSIBLE_SU_FLAGS', '')
|
||||
DEFAULT_SU_USER = get_config(p, DEFAULTS, 'su_user', 'ANSIBLE_SU_USER', 'root')
|
||||
DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_SU_PASS', False, boolean=True)
|
||||
DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower()
|
||||
|
||||
DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '/usr/share/ansible_plugins/action_plugins')
|
||||
DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '/usr/share/ansible_plugins/callback_plugins')
|
||||
@ -152,6 +154,7 @@ DEFAULT_UNDEFINED_VAR_BEHAVIOR = get_config(p, DEFAULTS, 'error_on_undefined_var
|
||||
HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', 'ANSIBLE_HOST_KEY_CHECKING', True, boolean=True)
|
||||
SYSTEM_WARNINGS = get_config(p, DEFAULTS, 'system_warnings', 'ANSIBLE_SYSTEM_WARNINGS', True, boolean=True)
|
||||
DEPRECATION_WARNINGS = get_config(p, DEFAULTS, 'deprecation_warnings', 'ANSIBLE_DEPRECATION_WARNINGS', True, boolean=True)
|
||||
DEFAULT_CALLABLE_WHITELIST = get_config(p, DEFAULTS, 'callable_whitelist', 'ANSIBLE_CALLABLE_WHITELIST', [], islist=True)
|
||||
|
||||
# CONNECTION RELATED
|
||||
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
|
||||
|
||||
@ -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('./'):
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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={},
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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] = []
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 ]
|
||||
|
||||
@ -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:
|
||||
@ -1050,21 +1067,31 @@ def safe_eval(expr, locals={}, include_exceptions=False):
|
||||
)
|
||||
)
|
||||
|
||||
# builtin functions that are not safe to call
|
||||
INVALID_CALLS = (
|
||||
'classmethod', 'compile', 'delattr', 'eval', 'execfile', 'file',
|
||||
'filter', 'help', 'input', 'object', 'open', 'raw_input', 'reduce',
|
||||
'reload', 'repr', 'setattr', 'staticmethod', 'super', 'type',
|
||||
)
|
||||
# builtin functions that are safe to call
|
||||
BUILTIN_WHITELIST = [
|
||||
'abs', 'all', 'any', 'basestring', 'bin', 'bool', 'buffer', 'bytearray',
|
||||
'bytes', 'callable', 'chr', 'cmp', 'coerce', 'complex', 'copyright', 'credits',
|
||||
'dict', 'dir', 'divmod', 'enumerate', 'exit', 'float', 'format', 'frozenset',
|
||||
'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'int', 'intern',
|
||||
'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long',
|
||||
'map', 'max', 'memoryview', 'min', 'next', 'oct', 'ord', 'pow', 'print',
|
||||
'property', 'quit', 'range', 'reversed', 'round', 'set', 'slice', 'sorted',
|
||||
'str', 'sum', 'tuple', 'unichr', 'unicode', 'vars', 'xrange', 'zip',
|
||||
]
|
||||
|
||||
filter_list = []
|
||||
for filter in filter_loader.all():
|
||||
filter_list.extend(filter.filters().keys())
|
||||
|
||||
CALL_WHITELIST = BUILTIN_WHITELIST + filter_list + C.DEFAULT_CALLABLE_WHITELIST
|
||||
|
||||
class CleansingNodeVisitor(ast.NodeVisitor):
|
||||
def generic_visit(self, node):
|
||||
if type(node) not in SAFE_NODES:
|
||||
#raise Exception("invalid expression (%s) type=%s" % (expr, type(node)))
|
||||
raise Exception("invalid expression (%s)" % expr)
|
||||
super(CleansingNodeVisitor, self).generic_visit(node)
|
||||
def visit_Call(self, call):
|
||||
if call.func.id in INVALID_CALLS:
|
||||
if call.func.id not in CALL_WHITELIST:
|
||||
raise Exception("invalid function: %s" % call.func.id)
|
||||
|
||||
if not isinstance(expr, basestring):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 = []
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -1,32 +1,56 @@
|
||||
ansible (1.6.4) unstable; urgency=low
|
||||
|
||||
* 1.6.4 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Wed, 25 Jun 2014 17:30:00 -0500
|
||||
|
||||
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
|
||||
|
||||
* 1.5.5 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Fri, 18 April 2014 15:00:00 -0500
|
||||
-- Michael DeHaan <michael@ansible.com> Fri, 18 April 2014 15:00:00 -0500
|
||||
|
||||
ansible (1.5.4) unstable; urgency=low
|
||||
|
||||
* 1.5.4 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Tue, 01 April 2014 08:46:00 -0500
|
||||
-- Michael DeHaan <michael@ansible.com> Tue, 01 April 2014 08:46:00 -0500
|
||||
|
||||
ansible (1.5.3) unstable; urgency=low
|
||||
|
||||
* 1.5.3 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Thu, 13 March 2014 08:46:00 -0500
|
||||
-- Michael DeHaan <michael@ansible.com> Thu, 13 March 2014 08:46:00 -0500
|
||||
|
||||
ansible (1.5.2) unstable; urgency=low
|
||||
|
||||
* 1.5.2 release
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Tue, 11 March 2014 08:46:00 -0500
|
||||
-- Michael DeHaan <michael@ansible.com> Tue, 11 March 2014 08:46:00 -0500
|
||||
|
||||
ansible (1.5.1) unstable; urgency=low
|
||||
|
||||
@ -37,7 +61,7 @@ ansible (1.5.1) unstable; urgency=low
|
||||
ansible (1.5) unstable; urgency=low
|
||||
|
||||
* 1.5 release
|
||||
|
||||
|
||||
-- Michael DeHaan <michael@ansible.com> Fri, 28 February 2014 00:00:00 -0500
|
||||
|
||||
ansible (1.4.5) unstable; urgency=low
|
||||
@ -72,8 +96,8 @@ ansible (1.4.1) unstable; urgency=low
|
||||
|
||||
ansible (1.4) unstable; urgency=low
|
||||
|
||||
* 1.4 release
|
||||
|
||||
* 1.4 release
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 21 November 2013 15:00:01 -0500
|
||||
|
||||
ansible (1.3) unstable; urgency=low
|
||||
@ -83,15 +107,15 @@ ansible (1.3) unstable; urgency=low
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Fri, 13 September 2013 18:00:00 -0500
|
||||
|
||||
ansible (1.2.2) unstable; urgency=low
|
||||
|
||||
|
||||
* 1.2.2 release
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 04 July 2013 21:45:01 -0500
|
||||
|
||||
ansible (1.2.1) unstable; urgency=low
|
||||
|
||||
* 1.2.1 release
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 04 July 2013 21:45:00 -0500
|
||||
|
||||
ansible (1.2) unstable; urgency=low
|
||||
@ -107,33 +131,33 @@ ansible (1.1) unstable; urgency=low
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Tue, 02 Mar 2013 20:38:00 -0500
|
||||
|
||||
ansible (1.0) unstable; urgency=low
|
||||
|
||||
* 1.0 release
|
||||
|
||||
|
||||
* 1.0 release
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Fri, 01 Feb 2013 21:34:34 -0500
|
||||
|
||||
ansible (0.9) unstable; urgency=low
|
||||
|
||||
* 0.9 release
|
||||
|
||||
|
||||
* 0.9 release
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Fri, 30 Nov 2012 22:00:00 -0400
|
||||
|
||||
ansible (0.8) unstable; urgency=low
|
||||
|
||||
|
||||
* 0.8 update
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Fri, 19 Oct 2012 20:50:00 -0400
|
||||
|
||||
ansible (0.7) unstable; urgency=low
|
||||
|
||||
|
||||
* 0.7 update
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 06 Aug 2012 18:50:00 -0400
|
||||
|
||||
ansible (0.6) unstable; urgency=low
|
||||
|
||||
|
||||
* 0.6 update
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Mon, 06 Aug 2012 19:50:00 -0400
|
||||
|
||||
ansible (0.5) unstable; urgency=low
|
||||
@ -143,9 +167,9 @@ ansible (0.5) unstable; urgency=low
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Wed, 04 Jul 2012 13:40:00 -0400
|
||||
|
||||
ansible (0.4) unstable; urgency=low
|
||||
|
||||
|
||||
* 0.4 update
|
||||
|
||||
|
||||
-- Michael DeHaan <michael.dehaan@gmail.com> Wed, 23 May 2012 19:40:00 -0400
|
||||
|
||||
ansible (0.3) unstable; urgency=low
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
Source: ansible
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Henry Graham (hzgraham) <Henry.Graham@mail.wvu.edu>
|
||||
Build-Depends: cdbs, debhelper (>= 5.0.0), asciidoc
|
||||
Standards-Version: 3.9.1
|
||||
Standards-Version: 3.9.3
|
||||
Maintainer: Michael DeHaan <michael.dehaan@gmail.com>
|
||||
Build-Depends: cdbs, debhelper (>= 5.0.0), asciidoc, python, python-support, python-setuptools
|
||||
Homepage: http://ansible.github.com/
|
||||
|
||||
Package: ansible
|
||||
Architecture: all
|
||||
Depends: python, python-support (>= 0.90), python-jinja2, python-yaml, python-paramiko, python-httplib2, sshpass
|
||||
Description: Ansible Application
|
||||
Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH executing commands, running "modules", or executing larger 'playbooks' that can serve as a configuration management or deployment system.
|
||||
Depends: python, python-support (>= 0.90), python-jinja2, python-yaml, python-paramiko, python-httplib2, sshpass, ${misc:Depends}
|
||||
Description: A radically simple IT automation platform
|
||||
A radically simple IT automation platform that makes your applications and
|
||||
systems easier to deploy. Avoid writing scripts or custom code to deploy and
|
||||
update your applications— automate in a language that approaches plain English,
|
||||
using SSH, with no agents to install on remote systems.
|
||||
|
||||
@ -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,18 @@ rm -rf %{buildroot}
|
||||
|
||||
%changelog
|
||||
|
||||
* Wed Jun 25 2014 Michael DeHaan <michael@ansible.com> - 1.6.4
|
||||
- Release 1.6.4
|
||||
|
||||
* 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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
6
test/integration/inventory.group_by
Normal file
6
test/integration/inventory.group_by
Normal file
@ -0,0 +1,6 @@
|
||||
[lamini]
|
||||
alpaca genus=vicugna
|
||||
llama genus=lama
|
||||
|
||||
[lamini:vars]
|
||||
ansible_connection=local
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 == ""'
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
dependencies:
|
||||
- prepare_tests
|
||||
dependencies: []
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
111
test/integration/test_group_by.yml
Normal file
111
test/integration/test_group_by.yml
Normal 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"] }
|
||||
@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user