Merge remote branch 'upstream/devel' into devel

This commit is contained in:
Gregory Duchatelet 2012-11-27 08:52:53 +01:00
commit 7c2c6fee3a
8 changed files with 141 additions and 32 deletions

View file

@ -303,7 +303,19 @@ directory.
Lookup Plugins
--------------
Language constructs like "with_fileglob" are implemnted via lookup plugins. Just like other plugin types, you can write your own.
Language constructs like "with_fileglob" and "with_items" are implemented via lookup plugins. Just like other plugin types, you can write your own.
Vars Plugins
------------
Playbook constructs like 'host_vars' and 'group_vars' work via 'vars' plugins. They inject additional variable
data into ansible runs that did not come from an inventory, playbook, or command line. Note that variables
can also be returned from inventory, so in most cases, you won't need to write or understand vars_plugins.
Filter Plugins
--------------
If you want more Jinja2 filters available in a Jinja2 template (filters like to_yaml and to_json are provided by default), they can be extended by writing a filter plugin.
Distributing Plugins
--------------------
@ -317,6 +329,8 @@ to /usr/share/ansible/plugins, in a subfolder for each plugin type::
* lookup_plugins
* callback_plugins
* connection_plugins
* filter_plugins
* vars_plugins
To change this path, edit the ansible configuration file.

View file

@ -226,6 +226,8 @@ also works with ``ansible-playbook``::
$ ansible webservers:dbservers -m command -a "/bin/foo xyz" --limit region
Assuming version 0.9 or later, as with other host patterns, values to limit can be seperated with ";", ":", or ",".
Now let's talk about range selection. Suppose you have 1000 servers in group 'datacenter', but only want to target one at a time. This is also easy::
$ ansible webservers[0-99] -m command -a "/bin/foo xyz"

View file

@ -288,6 +288,47 @@ While `only_if` is a pretty good option for advanced users, it exposes more guts
we can do better. In 0.9, we will be adding `when`, which will be like a syntactic sugar for `only_if` and hide
this level of complexity -- it will numerous built in operators.
Conditional Execution (Simplified)
``````````````````````````````````
In Ansible 0.9, we realized that only_if was a bit syntactically complicated, and exposed too much Python
to the user. As a result, the 'when' set of keywords was added. The 'when' statements do not have
to be quoted or casted to specify types, but you should seperate any variables used with whitespace. In
most cases users will be able to use 'when', but for more complex cases, only_if may still be required.
Here are various examples of 'when' in use. 'when' is incompatible with 'only_if' in the same task::
- name: "do this if my favcolor is blue, and my dog is named fido"
action: shell /bin/false
when_string: $favcolor == 'blue' and $dog == 'fido'
- name: "do this if my favcolor is not blue, and my dog is named fido"
action: shell /bin/true
when_string: $favcolor != 'blue' and $dog == 'fido'
- name: "do this if my SSN is over 9000"
action: shell /bin/true
when_integer: $ssn > 9000
- name: "do this if I have one of these SSNs"
action: shell /bin/true
when_integer: $ssn in [ 8675309, 8675310, 8675311 ]
- name: "do this if a variable named hippo is NOT defined"
action: shell /bin/true
when_unset: $hippo
- name: "do this if a variable named hippo is defined"
action: shell /bin/true
when_set: $hippo
- name: "do this if a variable named hippo is true"
action: shell /bin/true
when_boolean: $hippo
The when_boolean check will look for variables that look to be true as well, such as the string 'True' or
'true', non-zero numbers, and so on.
Conditional Imports
```````````````````
@ -604,7 +645,7 @@ Fireball Mode
.. versionadded:: 0.8
Paramiko's core connection types of 'local', 'paramiko', and 'ssh' are augmented in version 0.8 by a new extra-fast
Ansible's core connection types of 'local', 'paramiko', and 'ssh' are augmented in version 0.8 and later by a new extra-fast
connection type called 'fireball'. It can only be used with playbooks and does require some additional setup
outside the lines of ansible's normal "no bootstrapping" philosophy. You are not required to use fireball mode
to use Ansible, though some users may appreciate it.
@ -654,7 +695,10 @@ any platform. You will also need gcc and zeromq-devel installed from your packa
- PyCrypto
- python-keyczar
For more information about fireball, see the module documentation section.
Fedora and EPEL also have Ansible RPM subpackages available for fireball-dependencies.
Also see the module documentation section.
Understanding Variable Precedence
`````````````````````````````````

View file

@ -283,7 +283,8 @@ def main():
fname = os.path.join(options.module_dir, module)
extra = os.path.join("inc", "%s.tex" % module)
if fname.endswith(".swp"):
# probably could just throw out everything with extensions
if fname.endswith(".swp") or fname.endswith(".orig") or fname.endswith(".rej"):
continue
print " processing module source ---> %s" % fname

View file

@ -308,13 +308,13 @@ class PlayBook(object):
for host, results in results.get('contacted',{}).iteritems():
if results.get('changed', False):
for handler_name in task.notify:
self._flag_handler(play.handlers(), utils.template(play.basedir, handler_name, task.module_vars), host)
self._flag_handler(play, utils.template(play.basedir, handler_name, task.module_vars), host)
return hosts_remaining
# *****************************************************
def _flag_handler(self, handlers, handler_name, host):
def _flag_handler(self, play, handler_name, host):
'''
if a task has any notify elements, flag handlers for run
at end of execution cycle for hosts that have indicated
@ -322,8 +322,8 @@ class PlayBook(object):
'''
found = False
for x in handlers:
if handler_name == x.name:
for x in play.handlers():
if handler_name == utils.template(play.basedir, x.name, x.module_vars):
found = True
self.callbacks.on_notify(host, x.name)
x.notified_by.append(host)

View file

@ -161,7 +161,7 @@ class Play(object):
if type(self.vars) not in [dict, list]:
raise errors.AnsibleError("'vars' section must contain only key/value pairs")
vars = self.playbook.global_vars
vars = {}
# translate a list of vars into a dict
if type(self.vars) == list:
@ -187,12 +187,14 @@ class Play(object):
salt_size = var.get("salt_size", None)
salt = var.get("salt", None)
vars[vname] = self.playbook.callbacks.on_vars_prompt(vname, private, prompt,encrypt, confirm, salt_size, salt)
if vname not in self.playbook.extra_vars:
vars[vname] = self.playbook.callbacks.on_vars_prompt(vname, private, prompt,encrypt, confirm, salt_size, salt)
elif type(self.vars_prompt) == dict:
for (vname, prompt) in self.vars_prompt.iteritems():
prompt_msg = "%s: " % prompt
vars[vname] = self.playbook.callbacks.on_vars_prompt(varname=vname, private=False, prompt=prompt_msg)
if vname not in self.playbook.extra_vars:
vars[vname] = self.playbook.callbacks.on_vars_prompt(varname=vname, private=False, prompt=prompt_msg)
else:
raise errors.AnsibleError("'vars_prompt' section is malformed, see docs")

View file

@ -45,6 +45,8 @@ class Task(object):
# code to allow for saying "modulename: args" versus "action: modulename args"
if x in utils.plugins.module_finder:
if 'action' in ds:
raise errors.AnsibleError("multiple actions specified in task %s" % (ds.get('name', ds['action'])))
ds['action'] = x + " " + ds[x]
ds.pop(x)
@ -149,9 +151,6 @@ class Task(object):
# allow the user to list comma delimited tags
import_tags = import_tags.split(",")
self.name = utils.template(None, self.name, self.module_vars)
self.action = utils.template(None, self.action, self.module_vars)
# handle mutually incompatible options
incompatibles = [ x for x in [ self.first_available_file, self.items_lookup_plugin ] if x is not None ]
if len(incompatibles) > 1:
@ -233,7 +232,7 @@ class Task(object):
tcopy = tokens[1:]
for (i, t) in enumerate(tcopy):
if t.find("$") != -1:
tcopy[i] = "(is_set('''%s''') and '''%s'''.lower() not in ('false', 'none', '0', ''))" % (t, t)
tcopy[i] = "(is_set('''%s''') and '''%s'''.lower() not in ('false', 'no', 'n', 'none', '0', ''))" % (t, t)
return " ".join(tcopy)
else:

View file

@ -72,7 +72,8 @@ examples:
import platform
import os
import re
import tempfile
import shlex
class Service(object):
"""
@ -108,6 +109,9 @@ class Service(object):
self.svc_initctl = None
self.enable_cmd = None
self.arguments = module.params.get('arguments', '')
self.rcconf_file = None
self.rcconf_key = None
self.rcconf_value = None
# select whether we dump additional debug info through syslog
self.syslogging = False
@ -194,6 +198,59 @@ class Service(object):
out = ''
return rc, out, err
def service_enable_rcconf(self):
if self.rcconf_file is None or self.rcconf_key is None or self.rcconf_value is None:
self.module.fail_json(msg="service_enable_rcconf() requires rcconf_file, rcconf_key and rcconf_value")
changed = None
entry = '%s="%s"\n' % (self.rcconf_key, self.rcconf_value)
RCFILE = open(self.rcconf_file, "r")
new_rc_conf = []
# Build a list containing the possibly modified file.
for rcline in RCFILE:
# Parse line removing whitespaces, quotes, etc.
rcarray = shlex.split(rcline, comments=True)
if len(rcarray) >= 1 and '=' in rcarray[0]:
(key, value) = rcarray[0].split("=", 1)
if key == self.rcconf_key:
if value == self.rcconf_value:
# Since the proper entry already exists we can stop iterating.
changed = False
break
else:
# We found the key but the value is wrong, replace with new entry.
rcline = entry
changed = True
# Add line to the list.
new_rc_conf.append(rcline)
# We are done with reading the current rc.conf, close it.
RCFILE.close()
# If we did not see any trace of our entry we need to add it.
if changed is None:
new_rc_conf.append(entry)
changed = True
if changed is True:
# Create a temporary file next to the current rc.conf (so we stay on the same filesystem).
# This way the replacement operation is atomic.
rcconf_dir = os.path.dirname(self.rcconf_file)
rcconf_base = os.path.basename(self.rcconf_file)
(TMP_RCCONF, tmp_rcconf_file) = tempfile.mkstemp(dir=rcconf_dir, prefix="%s-" % rcconf_base)
# Write out the contents of the list into our temporary file.
for rcline in new_rc_conf:
os.write(TMP_RCCONF, rcline)
# Close temporary file.
os.close(TMP_RCCONF)
# Replace previous rc.conf.
self.module.atomic_replace(tmp_rcconf_file, self.rcconf_file)
# ===========================================
# Subclass: Linux
@ -368,28 +425,18 @@ class FreeBsdService(Service):
def service_enable(self):
if self.enable:
rc = "YES"
self.rcconf_value = "YES"
else:
rc = "NO"
self.rcconf_value = "NO"
rcfiles = [ '/etc/rc.conf','/usr/local/etc/rc.conf' ]
for rcfile in rcfiles:
if os.path.isfile(rcfile):
rcconf = rcfile
self.rcconf_file = rcfile
self.rcconf_key = "%s_enable" % self.name
entry = "%s_enable" % self.name
full_entry = '%s="%s"' % (entry,rc)
rc = open(rcconf,"r+")
rctext = rc.read()
if re.search("^%s" % full_entry,rctext,re.M) is None:
if re.search("^%s" % entry,rctext,re.M) is None:
rctext += "\n%s" % full_entry
else:
rctext = re.sub("^%s.*" % entry,full_entry,rctext,1,re.M)
rc.truncate(0)
rc.seek(0)
rc.write(rctext)
rc.close()
return self.service_enable_rcconf()
def service_control(self):
if self.action is "start":