Merge remote branch 'upstream/devel' into devel

pull/1044/head
Aleksej Romanov 12 years ago
commit 61e9b27df2

@ -1,7 +1,24 @@
Ansible Changes By Release Ansible Changes By Release
========================== ==========================
0.7 "Panama" -- release pending -- I can barely see the roadmap from the heat coming off of it. 0.8 "Cathedral" -- release pending
Misc:
* is_set is available for use inside of an only_if expression: is_set('ansible_eth0') # etc
* removes= exists on command just like creates=
* postgresql modules now take an optional port= parameter
* /proc/cmdline info is now available in Linux facts
* public host key detection for OS X
* to_yaml and from_yaml are available as Jinja2 filters
* server side action code (template, etc) are now fully pluggable
* lineinfile module now uses 'search' not exact 'match' in regexes, making it much more intuitive and not needing regex syntax most of the time
* $group and $group_names are now accessible in with_items
* playbooks can import playbooks in other directories and then be able to import tasks relative to them
* ansible config file can also go in '.ansible.cfg' in cwd in addition to ~/.ansible.cfg and /etc/ansible/ansible.cfg
* fix for inventory hosts at API level when hosts spec is a list and not a colon delimited string
0.7 "Panama" -- Sept 6 2012
Module changes: Module changes:
@ -11,7 +28,7 @@ Module changes:
* misc yum module fixes * misc yum module fixes
* better changed=True/False detection in user module on older Linux distros * better changed=True/False detection in user module on older Linux distros
* nicer errors from modules when arguments are not key=value * nicer errors from modules when arguments are not key=value
* backup option on copy (backup=yes) * backup option on copy (backup=yes), as well as template, assemble, and lineinfile
* file module will not recurse on directory properties * file module will not recurse on directory properties
* yum module now workable without having repoquery installed, but doesn't support comparisons or list= if so * yum module now workable without having repoquery installed, but doesn't support comparisons or list= if so
* setup module now detects interfaces with aliases * setup module now detects interfaces with aliases

@ -1,7 +1,8 @@
Ansible Releases at a Glance Ansible Releases at a Glance
============================ ============================
0.7 "Panama" ---------- X-XX-2012 0.8 "Cathedral" ------- pending
0.7 "Panama" ---------- 9-06-2012
0.6 "Cabo" ------------ 8-06-2012 0.6 "Cabo" ------------ 8-06-2012
0.5 "Amsterdam" ------- 7-04-2012 0.5 "Amsterdam" ------- 7-04-2012
0.4 "Unchained" ------- 5-23-2012 0.4 "Unchained" ------- 5-23-2012

@ -1 +1 @@
0.7 0.8

@ -1,22 +1,13 @@
'\" t '\" t
.\" Title: ansible-playbook .\" Title: ansible-playbook
.\" Author: [see the "AUTHOR" section] .\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/> .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 08/15/2012 .\" Date: 09/06/2012
.\" Manual: System administration commands .\" Manual: System administration commands
.\" Source: Ansible 0.7 .\" Source: Ansible 0.8
.\" Language: English .\" Language: English
.\" .\"
.TH "ANSIBLE\-PLAYBOOK" "1" "08/15/2012" "Ansible 0\&.7" "System administration commands" .TH "ANSIBLE\-PLAYBOOK" "1" "09/06/2012" "Ansible 0\&.8" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
.\" * set default formatting .\" * set default formatting
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
@ -117,6 +108,11 @@ Connection type to use\&. Possible options are
\fIlocal\fR \fIlocal\fR
is mostly useful for crontab or kickstarts\&. is mostly useful for crontab or kickstarts\&.
.RE .RE
.PP
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
.RS 4
Further limits the selected host/group patterns\&.
.RE
.SH "ENVIRONMENT" .SH "ENVIRONMENT"
.sp .sp
The following environment variables may specified\&. The following environment variables may specified\&.

@ -93,6 +93,9 @@ user name to run as.
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh', Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
and 'local'. 'local' is mostly useful for crontab or kickstarts. and 'local'. 'local' is mostly useful for crontab or kickstarts.
*-l* 'SUBSET', *--limit=*'SUBSET'::
Further limits the selected host/group patterns.
ENVIRONMENT ENVIRONMENT

@ -1,22 +1,13 @@
'\" t '\" t
.\" Title: ansible .\" Title: ansible
.\" Author: [see the "AUTHOR" section] .\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/> .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 08/14/2012 .\" Date: 09/06/2012
.\" Manual: System administration commands .\" Manual: System administration commands
.\" Source: Ansible 0.7 .\" Source: Ansible 0.8
.\" Language: English .\" Language: English
.\" .\"
.TH "ANSIBLE" "1" "08/14/2012" "Ansible 0\&.7" "System administration commands" .TH "ANSIBLE" "1" "09/06/2012" "Ansible 0\&.8" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
.\" * set default formatting .\" * set default formatting
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
@ -34,7 +25,7 @@ ansible-pull \- set up a remote copy of ansible on each managed node
ansible \-d DEST \-U URL [ \-C CHECKOUT ] ansible \-d DEST \-U URL [ \-C CHECKOUT ]
.SH "DESCRIPTION" .SH "DESCRIPTION"
.sp .sp
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&. \fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
.sp .sp
Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via git\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&. Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via git\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&.
.sp .sp

@ -1,22 +1,13 @@
'\" t '\" t
.\" Title: ansible .\" Title: ansible
.\" Author: [see the "AUTHOR" section] .\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/> .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 08/15/2012 .\" Date: 09/06/2012
.\" Manual: System administration commands .\" Manual: System administration commands
.\" Source: Ansible 0.7 .\" Source: Ansible 0.8
.\" Language: English .\" Language: English
.\" .\"
.TH "ANSIBLE" "1" "08/15/2012" "Ansible 0\&.7" "System administration commands" .TH "ANSIBLE" "1" "09/06/2012" "Ansible 0\&.8" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
.\" * set default formatting .\" * set default formatting
.\" ----------------------------------------------------------------- .\" -----------------------------------------------------------------
@ -34,7 +25,7 @@ ansible \- run a command somewhere else
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args] ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
.SH "DESCRIPTION" .SH "DESCRIPTION"
.sp .sp
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&. \fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
.SH "ARGUMENTS" .SH "ARGUMENTS"
.PP .PP
\fBhost\-pattern\fR \fBhost\-pattern\fR
@ -82,7 +73,7 @@ to load modules from\&. The default is
\fI/usr/share/ansible\fR\&. \fI/usr/share/ansible\fR\&.
.RE .RE
.PP .PP
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq \fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\'
.RS 4 .RS 4
The The
\fIARGUMENTS\fR \fIARGUMENTS\fR
@ -165,6 +156,11 @@ Connection type to use\&. Possible options are
\fIlocal\fR \fIlocal\fR
is mostly useful for crontab or kickstarts\&. is mostly useful for crontab or kickstarts\&.
.RE .RE
.PP
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
.RS 4
Further limits the selected host/group patterns\&.
.RE
.SH "INVENTORY" .SH "INVENTORY"
.sp .sp
Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets that start the line\&. Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets that start the line\&.

@ -116,6 +116,9 @@ Use this file to authenticate the connection.
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh', Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
and 'local'. 'local' is mostly useful for crontab or kickstarts. and 'local'. 'local' is mostly useful for crontab or kickstarts.
*-l* 'SUBSET', *--limit=*'SUBSET'::
Further limits the selected host/group patterns.
INVENTORY INVENTORY
--------- ---------

@ -62,7 +62,7 @@ remote_port=22
#remote_user=root #remote_user=root
# if set, always use this private key file for authentication, same as if passing # if set, always use this private key file for authentication, same as if passing
# --private-key-file to ansible or ansible-playbook # --private-key to ansible or ansible-playbook
#private_key_file=/path/to/file #private_key_file=/path/to/file

@ -14,5 +14,5 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
__version__ = '0.7' __version__ = '0.8'
__author__ = 'Michael DeHaan' __author__ = 'Michael DeHaan'

@ -25,7 +25,7 @@ from ansible.color import stringc
dirname = os.path.dirname(__file__) dirname = os.path.dirname(__file__)
callbacks = utils.import_plugins(os.path.join(dirname, 'callback_plugins')) callbacks = utils.import_plugins(os.path.join(dirname, 'callback_plugins'))
callbacks = [ c.CallbackModule() for c in callbacks.values() if c.__name__ != '__init__' ] callbacks = [ c.CallbackModule() for c in callbacks.values() ]
cowsay = None cowsay = None
if os.path.exists("/usr/bin/cowsay"): if os.path.exists("/usr/bin/cowsay"):
@ -76,11 +76,12 @@ class AggregateStats(object):
prev = (getattr(self, what)).get(host, 0) prev = (getattr(self, what)).get(host, 0)
getattr(self, what)[host] = prev+1 getattr(self, what)[host] = prev+1
def compute(self, runner_results, setup=False, poll=False): def compute(self, runner_results, setup=False, poll=False, ignore_errors=False):
''' walk through all results and increment stats ''' ''' walk through all results and increment stats '''
for (host, value) in runner_results.get('contacted', {}).iteritems(): for (host, value) in runner_results.get('contacted', {}).iteritems():
if ('failed' in value and bool(value['failed'])) or ('rc' in value and value['rc'] != 0): if not ignore_errors and (('failed' in value and bool(value['failed'])) or
('rc' in value and value['rc'] != 0)):
self._increment('failures', host) self._increment('failures', host)
elif 'skipped' in value and bool(value['skipped']): elif 'skipped' in value and bool(value['skipped']):
self._increment('skipped', host) self._increment('skipped', host)
@ -235,8 +236,8 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
) )
super(CliRunnerCallbacks, self).on_unreachable(host, res) super(CliRunnerCallbacks, self).on_unreachable(host, res)
def on_skipped(self, host): def on_skipped(self, host, item=None):
super(CliRunnerCallbacks, self).on_skipped(host, res) super(CliRunnerCallbacks, self).on_skipped(host, item)
def on_error(self, host, err): def on_error(self, host, err):
print >>sys.stderr, "err: [%s] => %s\n" % (host, err) print >>sys.stderr, "err: [%s] => %s\n" % (host, err)
@ -265,9 +266,9 @@ class CliRunnerCallbacks(DefaultRunnerCallbacks):
def _on_any(self, host, result): def _on_any(self, host, result):
result2 = result.copy() result2 = result.copy()
result2.pop('invocation', None) result2.pop('invocation', None)
print host_report_msg(host, self.options.module_name, result, self.options.one_line) print host_report_msg(host, self.options.module_name, result2, self.options.one_line)
if self.options.tree: if self.options.tree:
utils.write_tree_file(self.options.tree, host, utils.jsonify(result,format=True)) utils.write_tree_file(self.options.tree, host, utils.jsonify(result2,format=True))
######################################################################## ########################################################################
@ -291,14 +292,14 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
def on_failed(self, host, results, ignore_errors=False): def on_failed(self, host, results, ignore_errors=False):
results = results.copy() results2 = results.copy()
results.pop('invocation', None) results2.pop('invocation', None)
item = results.get('item', None) item = results2.get('item', None)
if item: if item:
msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results)) msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results2))
else: else:
msg = "failed: [%s] => %s" % (host, utils.jsonify(results)) msg = "failed: [%s] => %s" % (host, utils.jsonify(results2))
print stringc(msg, 'red') print stringc(msg, 'red')
if ignore_errors: if ignore_errors:
print stringc("...ignoring", 'yellow') print stringc("...ignoring", 'yellow')
@ -307,12 +308,12 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
def on_ok(self, host, host_result): def on_ok(self, host, host_result):
item = host_result.get('item', None) item = host_result.get('item', None)
host_result = host_result.copy() host_result2 = host_result.copy()
host_result.pop('invocation', None) host_result2.pop('invocation', None)
# show verbose output for non-setup module results if --verbose is used # show verbose output for non-setup module results if --verbose is used
msg = '' msg = ''
if not self.verbose or host_result.get("verbose_override",None) is not None: if not self.verbose or host_result2.get("verbose_override",None) is not None:
if item: if item:
msg = "ok: [%s] => (item=%s)" % (host,item) msg = "ok: [%s] => (item=%s)" % (host,item)
else: else:
@ -321,13 +322,13 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
else: else:
# verbose ... # verbose ...
if item: if item:
msg = "ok: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(host_result)) msg = "ok: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(host_result2))
else: else:
if 'ansible_job_id' not in host_result or 'finished' in host_result: if 'ansible_job_id' not in host_result or 'finished' in host_result2:
msg = "ok: [%s] => %s" % (host, utils.jsonify(host_result)) msg = "ok: [%s] => %s" % (host, utils.jsonify(host_result2))
if msg != '': if msg != '':
if not 'changed' in host_result or not host_result['changed']: if not 'changed' in host_result2 or not host_result['changed']:
print stringc(msg, 'green') print stringc(msg, 'green')
else: else:
print stringc(msg, 'yellow') print stringc(msg, 'yellow')
@ -353,7 +354,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
else: else:
msg = "skipping: [%s]" % host msg = "skipping: [%s]" % host
print stringc(msg, 'yellow') print stringc(msg, 'yellow')
super(PlaybookRunnerCallbacks, self).on_skipped(host, item=None) super(PlaybookRunnerCallbacks, self).on_skipped(host, item)
def on_no_hosts(self): def on_no_hosts(self):
print stringc("no hosts matched or remaining\n", 'red') print stringc("no hosts matched or remaining\n", 'red')

@ -35,13 +35,16 @@ def get_config(p, section, key, env_var, default):
def load_config_file(): def load_config_file():
p = ConfigParser.ConfigParser() p = ConfigParser.ConfigParser()
path1 = os.path.expanduser( path1 = os.getcwd() + "/ansible.cfg"
os.environ.get('ANSIBLE_CONFIG', "~/.ansible.cfg")) path2 = os.path.expanduser(os.environ.get('ANSIBLE_CONFIG', "~/.ansible.cfg"))
path2 = "/etc/ansible/ansible.cfg" path3 = "/etc/ansible/ansible.cfg"
if os.path.exists(path1): if os.path.exists(path1):
p.read(path1) p.read(path1)
elif os.path.exists(path2): elif os.path.exists(path2):
p.read(path2) p.read(path2)
elif os.path.exists(path3):
p.read(path3)
else: else:
return None return None
return p return p

@ -35,7 +35,7 @@ class Inventory(object):
""" """
__slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script', __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script',
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache' ] 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list' ]
def __init__(self, host_list=C.DEFAULT_HOST_LIST): def __init__(self, host_list=C.DEFAULT_HOST_LIST):
@ -49,6 +49,7 @@ class Inventory(object):
self._vars_per_host = {} self._vars_per_host = {}
self._vars_per_group = {} self._vars_per_group = {}
self._hosts_cache = {} self._hosts_cache = {}
self._groups_list = {}
# the inventory object holds a list of groups # the inventory object holds a list of groups
self.groups = [] self.groups = []
@ -97,6 +98,8 @@ class Inventory(object):
""" """
# process patterns # process patterns
if isinstance(pattern, list):
pattern = ';'.join(pattern)
patterns = pattern.replace(";",":").split(":") patterns = pattern.replace(";",":").split(":")
positive_patterns = [ p for p in patterns if not p.startswith("!") ] positive_patterns = [ p for p in patterns if not p.startswith("!") ]
negative_patterns = [ p for p in patterns if p.startswith("!") ] negative_patterns = [ p for p in patterns if p.startswith("!") ]
@ -213,6 +216,17 @@ class Inventory(object):
continue continue
return results return results
def groups_list(self):
if not self._groups_list:
groups = {}
for g in self.groups:
groups[g.name] = [h.name for h in g.get_hosts()]
ancestors = g.get_ancestors()
for a in ancestors:
groups[a.name] = [h.name for h in a.get_hosts()]
self._groups_list = groups
return self._groups_list
def get_groups(self): def get_groups(self):
return self.groups return self.groups

@ -47,12 +47,21 @@ import subprocess
import sys import sys
import syslog import syslog
import types import types
import time
import shutil
try: try:
from hashlib import md5 as _md5 from hashlib import md5 as _md5
except ImportError: except ImportError:
from md5 import md5 as _md5 from md5 import md5 as _md5
try:
from systemd import journal
has_journal = True
except ImportError:
import syslog
has_journal = False
class AnsibleModule(object): class AnsibleModule(object):
def __init__(self, argument_spec, bypass_checks=False, no_log=False, def __init__(self, argument_spec, bypass_checks=False, no_log=False,
@ -196,11 +205,26 @@ class AnsibleModule(object):
def _log_invocation(self): def _log_invocation(self):
''' log that ansible ran the module ''' ''' log that ansible ran the module '''
syslog.openlog('ansible-%s' % os.path.basename(__file__)) # Sanitize possible password argument when logging.
# Sanitize possible password argument when logging log_args = dict()
log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", self.args) passwd_keys = ['password', 'login_password']
log_args = re.sub(r'login_password=.+ (.*)', r"login_password=NOT_LOGGING_PASSWORD \1", log_args) for param in self.params:
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args) if param in passwd_keys:
log_args[param] = 'NOT_LOGGING_PASSWORD'
else:
log_args[param] = self.params[param]
if (has_journal):
journal_args = ["MESSAGE=Ansible module invoked", "MODULE=%s" % os.path.basename(__file__)]
for arg in log_args:
journal_args.append(arg.upper() + "=" + str(log_args[arg]))
journal.sendv(*journal_args)
else:
msg = ''
syslog.openlog('ansible-%s' % os.path.basename(__file__))
for arg in log_args:
msg = msg + arg + '=' + str(log_args[arg]) + ' '
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % msg)
def get_bin_path(self, arg, required=False, opt_dirs=[]): def get_bin_path(self, arg, required=False, opt_dirs=[]):
''' '''
@ -274,6 +298,18 @@ class AnsibleModule(object):
infile.close() infile.close()
return digest.hexdigest() return digest.hexdigest()
def backup_local(self, fn):
'''make a date-marked backup of the specified file, return True or False on success or failure'''
# backups named basename-YYYY-MM-DD@HH:MM~
ext = time.strftime("%Y-%m-%d@%H:%M~", time.localtime(time.time()))
backupdest = '%s.%s' % (fn, ext)
try:
shutil.copy2(fn, backupdest)
except shutil.Error, e:
self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, e))
return backupdest
# == END DYNAMICALLY INSERTED CODE === # == END DYNAMICALLY INSERTED CODE ===

@ -112,7 +112,7 @@ class PlayBook(object):
self.global_vars.update(self.inventory.get_group_variables('all')) self.global_vars.update(self.inventory.get_group_variables('all'))
self.basedir = os.path.dirname(playbook) self.basedir = os.path.dirname(playbook)
self.playbook = self._load_playbook_from_file(playbook) (self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook)
self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library") self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library")
# ***************************************************** # *****************************************************
@ -124,6 +124,7 @@ class PlayBook(object):
playbook_data = utils.parse_yaml_from_file(path) playbook_data = utils.parse_yaml_from_file(path)
accumulated_plays = [] accumulated_plays = []
play_basedirs = []
if type(playbook_data) != list: if type(playbook_data) != list:
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list") raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list")
@ -134,13 +135,17 @@ class PlayBook(object):
if 'include' in play: if 'include' in play:
if len(play.keys()) == 1: if len(play.keys()) == 1:
included_path = utils.path_dwim(self.basedir, play['include']) included_path = utils.path_dwim(self.basedir, play['include'])
accumulated_plays.extend(self._load_playbook_from_file(included_path)) (plays, basedirs) = self._load_playbook_from_file(included_path)
accumulated_plays.extend(plays)
play_basedirs.extend(basedirs)
else: else:
raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play) raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play)
else: else:
accumulated_plays.append(play) accumulated_plays.append(play)
play_basedirs.append(os.path.dirname(path))
return accumulated_plays return (accumulated_plays, play_basedirs)
# ***************************************************** # *****************************************************
@ -152,8 +157,8 @@ class PlayBook(object):
# loop through all patterns and run them # loop through all patterns and run them
self.callbacks.on_start() self.callbacks.on_start()
for play_ds in self.playbook: for (play_ds, play_basedir) in zip(self.playbook, self.play_basedirs):
play = Play(self,play_ds) play = Play(self, play_ds, play_basedir)
matched_tags, unmatched_tags = play.compare_tags(self.only_tags) matched_tags, unmatched_tags = play.compare_tags(self.only_tags)
matched_tags_all = matched_tags_all | matched_tags matched_tags_all = matched_tags_all | matched_tags
unmatched_tags_all = unmatched_tags_all | unmatched_tags unmatched_tags_all = unmatched_tags_all | unmatched_tags
@ -215,7 +220,7 @@ class PlayBook(object):
timeout=self.timeout, remote_user=task.play.remote_user, timeout=self.timeout, remote_user=task.play.remote_user,
remote_port=task.play.remote_port, module_vars=task.module_vars, remote_port=task.play.remote_port, module_vars=task.module_vars,
private_key_file=self.private_key_file, private_key_file=self.private_key_file,
setup_cache=self.SETUP_CACHE, basedir=self.basedir, setup_cache=self.SETUP_CACHE, basedir=task.play.basedir,
conditional=task.only_if, callbacks=self.runner_callbacks, conditional=task.only_if, callbacks=self.runner_callbacks,
sudo=task.play.sudo, sudo_user=task.play.sudo_user, sudo=task.play.sudo, sudo_user=task.play.sudo_user,
transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True
@ -246,7 +251,7 @@ class PlayBook(object):
if results is None: if results is None:
results = {} results = {}
self.stats.compute(results) self.stats.compute(results, ignore_errors=task.ignore_errors)
# add facts to the global setup cache # add facts to the global setup cache
for host, result in results['contacted'].iteritems(): for host, result in results['contacted'].iteritems():

@ -29,7 +29,8 @@ class Play(object):
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files', 'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
'handlers', 'remote_user', 'remote_port', 'handlers', 'remote_user', 'remote_port',
'sudo', 'sudo_user', 'transport', 'playbook', 'sudo', 'sudo_user', 'transport', 'playbook',
'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks' 'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks',
'basedir'
] ]
# to catch typos and so forth -- these are userland names # to catch typos and so forth -- these are userland names
@ -42,7 +43,7 @@ class Play(object):
# ************************************************* # *************************************************
def __init__(self, playbook, ds): def __init__(self, playbook, ds, basedir):
''' constructor loads from a play datastructure ''' ''' constructor loads from a play datastructure '''
for x in ds.keys(): for x in ds.keys():
@ -57,15 +58,15 @@ class Play(object):
elif isinstance(hosts, list): elif isinstance(hosts, list):
hosts = ';'.join(hosts) hosts = ';'.join(hosts)
hosts = utils.template(hosts, playbook.extra_vars) hosts = utils.template(hosts, playbook.extra_vars)
self._ds = ds self._ds = ds
self.playbook = playbook self.playbook = playbook
self.basedir = basedir
self.hosts = hosts self.hosts = hosts
self.name = ds.get('name', self.hosts) self.name = ds.get('name', self.hosts)
self.vars = ds.get('vars', {}) self.vars = ds.get('vars', {})
self.vars_files = ds.get('vars_files', []) self.vars_files = ds.get('vars_files', [])
self.vars_prompt = ds.get('vars_prompt', {}) self.vars_prompt = ds.get('vars_prompt', {})
self.vars = self._get_vars(self.playbook.basedir) self.vars = self._get_vars()
self._tasks = ds.get('tasks', []) self._tasks = ds.get('tasks', [])
self._handlers = ds.get('handlers', []) self._handlers = ds.get('handlers', [])
self.remote_user = utils.template(ds.get('user', self.playbook.remote_user), playbook.extra_vars) self.remote_user = utils.template(ds.get('user', self.playbook.remote_user), playbook.extra_vars)
@ -107,7 +108,7 @@ class Play(object):
(k,v) = t.split("=", 1) (k,v) = t.split("=", 1)
task_vars[k] = utils.template(v, task_vars) task_vars[k] = utils.template(v, task_vars)
include_file = utils.template(tokens[0], task_vars) include_file = utils.template(tokens[0], task_vars)
data = utils.parse_yaml_from_file(utils.path_dwim(self.playbook.basedir, include_file)) data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file))
elif type(x) == dict: elif type(x) == dict:
data = [x] data = [x]
else: else:
@ -135,7 +136,7 @@ class Play(object):
# ************************************************* # *************************************************
def _get_vars(self, dirname): def _get_vars(self):
''' load the vars section from a play, accounting for all sorts of variable features ''' load the vars section from a play, accounting for all sorts of variable features
including loading from yaml files, prompting, and conditional includes of the first including loading from yaml files, prompting, and conditional includes of the first
file found in a list. ''' file found in a list. '''
@ -264,7 +265,7 @@ class Play(object):
filename3 = filename2 filename3 = filename2
if host is not None: if host is not None:
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host]) filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
filename4 = utils.path_dwim(self.playbook.basedir, filename3) filename4 = utils.path_dwim(self.basedir, filename3)
sequence.append(filename4) sequence.append(filename4)
if os.path.exists(filename4): if os.path.exists(filename4):
found = True found = True
@ -297,7 +298,7 @@ class Play(object):
filename3 = filename2 filename3 = filename2
if host is not None: if host is not None:
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host]) filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
filename4 = utils.path_dwim(self.playbook.basedir, filename3) filename4 = utils.path_dwim(self.basedir, filename3)
if self._has_vars_in(filename4): if self._has_vars_in(filename4):
return return
new_vars = utils.parse_yaml_from_file(filename4) new_vars = utils.parse_yaml_from_file(filename4)

@ -43,6 +43,9 @@ try:
except ImportError: except ImportError:
HAS_ATFORK=False HAS_ATFORK=False
dirname = os.path.dirname(__file__)
action_plugins = utils.import_plugins(os.path.join(dirname, 'action_plugins'))
################################################ ################################################
def _executor_hook(job_queue, result_queue): def _executor_hook(job_queue, result_queue):
@ -136,6 +139,11 @@ class Runner(object):
# ensure we are using unique tmp paths # ensure we are using unique tmp paths
random.seed() random.seed()
# instantiate plugin classes
self.action_plugins = {}
for (k,v) in action_plugins.iteritems():
self.action_plugins[k] = v.ActionModule(self)
# ***************************************************** # *****************************************************
def _delete_remote_files(self, conn, files): def _delete_remote_files(self, conn, files):
@ -209,217 +217,6 @@ class Runner(object):
# ***************************************************** # *****************************************************
def _execute_raw(self, conn, tmp, inject=None):
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
return ReturnData(conn=conn, result=dict(
stdout=self._low_level_exec_command(conn, self.module_args.encode('utf-8'), tmp, sudoable = True)
))
# ***************************************************
def _execute_normal_module(self, conn, tmp, module_name, inject=None):
''' transfer & execute a module that is not 'copy' or 'template' '''
# shell and command are the same module
if module_name == 'shell':
module_name = 'command'
self.module_args += " #USE_SHELL"
vv("REMOTE_MODULE %s %s" % (module_name, self.module_args), host=conn.host)
exec_rc = self._execute_module(conn, tmp, module_name, self.module_args, inject=inject)
return exec_rc
# *****************************************************
def _execute_async_module(self, conn, tmp, module_name, inject=None):
''' transfer the given module name, plus the async module, then run it '''
# shell and command module are the same
module_args = self.module_args
if module_name == 'shell':
module_name = 'command'
module_args += " #USE_SHELL"
(module_path, is_new_style) = self._copy_module(conn, tmp, module_name, inject)
self._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp)
return self._execute_module(conn, tmp, 'async_wrapper', module_args,
async_module=module_path,
async_jid=self.generated_jid,
async_limit=self.background,
inject=inject
)
# *****************************************************
def _execute_copy(self, conn, tmp, inject=None):
''' handler for file transfer operations '''
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and not 'first_available_file' in inject) or dest is None:
result=dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in inject.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
results=dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(conn=conn, results=results)
source = utils.template(source, inject)
source = utils.path_dwim(self.basedir, source)
local_md5 = utils.md5(source)
if local_md5 is None:
result=dict(failed=True, msg="could not find src=%s" % source)
return ReturnData(conn=conn, result=result)
remote_md5 = self._remote_md5(conn, tmp, dest)
exec_rc = None
if local_md5 != remote_md5:
# transfer the file to a remote tmp location
tmp_src = tmp + os.path.basename(source)
conn.put_file(source, tmp_src)
# fix file permissions when the copy is done as a different user
if self.sudo and self.sudo_user != 'root':
self._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp)
# run the copy module
self.module_args = "%s src=%s" % (self.module_args, tmp_src)
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
else:
# no need to transfer the file, already correct md5
result = dict(changed=False, md5sum=remote_md5, transferred=False)
return ReturnData(conn=conn, result=result).daisychain('file')
# *****************************************************
def _execute_fetch(self, conn, tmp, inject=None):
''' handler for fetch operations '''
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if source is None or dest is None:
results = dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, result=results)
# apply templating to source argument
source = utils.template(source, inject)
# apply templating to dest argument
dest = utils.template(dest, inject)
# files are saved in dest dir, with a subdir for each host, then the filename
dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), conn.host, source)
dest = dest.replace("//","/")
# calculate md5 sum for the remote file
remote_md5 = self._remote_md5(conn, tmp, source)
# these don't fail because you may want to transfer a log file that possibly MAY exist
# but keep going to fetch other log files
if remote_md5 == '0':
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
return ReturnData(conn=conn, result=result)
if remote_md5 == '1':
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
return ReturnData(conn=conn, result=result)
if remote_md5 == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
return ReturnData(conn=conn, result=result)
# calculate md5 sum for the local file
local_md5 = utils.md5(dest)
if remote_md5 != local_md5:
# create the containing directories, if needed
if not os.path.isdir(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest))
# fetch the file and check for changes
conn.fetch_file(source, dest)
new_md5 = utils.md5(dest)
if new_md5 != remote_md5:
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
return ReturnData(conn=conn, result=result)
result = dict(changed=True, md5sum=new_md5)
return ReturnData(conn=conn, result=result)
else:
result = dict(changed=False, md5sum=local_md5, file=source)
return ReturnData(conn=conn, result=result)
# *****************************************************
def _execute_template(self, conn, tmp, inject=None):
''' handler for template operations '''
if not self.is_playbook:
raise errors.AnsibleError("in current versions of ansible, templates are only usable in playbooks")
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and 'first_available_file' not in inject) or dest is None:
result = dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, comm_ok=False, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in self.module_vars.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
result = dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(conn=conn, comm_ok=False, result=result)
source = utils.template(source, inject)
# template the source data locally & transfer
try:
resultant = utils.template_from_file(self.basedir, source, inject)
except Exception, e:
result = dict(failed=True, msg=str(e))
return ReturnData(conn=conn, comm_ok=False, result=result)
xfered = self._transfer_str(conn, tmp, 'source', resultant)
# run the copy module, queue the file module
self.module_args = "%s src=%s dest=%s" % (self.module_args, xfered, dest)
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
# *****************************************************
def _execute_assemble(self, conn, tmp, inject=None):
''' handler for assemble operations '''
# FIXME: once assemble is ported over to the use the new common logic, this method
# will be unneccessary as it can decide to daisychain via it's own module returns.
# and this function can be deleted.
return self._execute_module(conn, tmp, 'assemble', self.module_args, inject=inject).daisychain('file')
# *****************************************************
def _executor(self, host): def _executor(self, host):
''' handler for multiprocessing library ''' ''' handler for multiprocessing library '''
@ -453,17 +250,15 @@ class Runner(object):
inject.update(self.module_vars) inject.update(self.module_vars)
inject.update(self.setup_cache[host]) inject.update(self.setup_cache[host])
inject['hostvars'] = self.setup_cache inject['hostvars'] = self.setup_cache
inject['group_names'] = host_variables.get('group_names', [])
inject['groups'] = self.inventory.groups_list()
# allow with_items to work in playbooks... # allow with_items to work in playbooks...
# apt and yum are converted into a single call, others run in a loop # apt and yum are converted into a single call, others run in a loop
items = self.module_vars.get('items', []) items = self.module_vars.get('items', [])
if isinstance(items, basestring) and items.startswith("$"): if isinstance(items, basestring) and items.startswith("$"):
items = items.replace("$","") items = utils.varLookup(items, inject)
if items in inject:
items = inject[items]
else:
raise errors.AnsibleError("unbound variable in with_items: %s" % items)
if type(items) != list: if type(items) != list:
raise errors.AnsibleError("with_items only takes a list: %s" % items) raise errors.AnsibleError("with_items only takes a list: %s" % items)
@ -524,11 +319,7 @@ class Runner(object):
# 'hostvars' variable contains variables for each host name # 'hostvars' variable contains variables for each host name
# ... and is set elsewhere # ... and is set elsewhere
# 'inventory_hostname' is also set elsewhere # 'inventory_hostname' is also set elsewhere
group_hosts = {} inject['groups'] = self.inventory.groups_list()
for g in self.inventory.groups:
group_hosts[g.name] = [ h.name for h in g.hosts ]
inject['groups'] = group_hosts
# allow module args to work as a dictionary # allow module args to work as a dictionary
# though it is usually a string # though it is usually a string
new_args = "" new_args = ""
@ -538,8 +329,12 @@ class Runner(object):
self.module_args = new_args self.module_args = new_args
self.module_args = utils.template(self.module_args, inject) self.module_args = utils.template(self.module_args, inject)
def _check_conditional(conditional):
def is_set(var):
return not var.startswith("$")
return eval(conditional)
conditional = utils.template(self.conditional, inject) conditional = utils.template(self.conditional, inject)
if not eval(conditional): if not _check_conditional(conditional):
result = utils.jsonify(dict(skipped=True)) result = utils.jsonify(dict(skipped=True))
self.callbacks.on_skipped(host, inject.get('item',None)) self.callbacks.on_skipped(host, inject.get('item',None))
return ReturnData(host=host, result=result) return ReturnData(host=host, result=result)
@ -564,14 +359,14 @@ class Runner(object):
tmp = self._make_tmp_path(conn) tmp = self._make_tmp_path(conn)
result = None result = None
handler = getattr(self, "_execute_%s" % self.module_name, None) handler = self.action_plugins.get(self.module_name, None)
if handler: if handler:
result = handler(conn, tmp, inject=inject) result = handler.run(conn, tmp, module_name, inject)
else: else:
if self.background == 0: if self.background == 0:
result = self._execute_normal_module(conn, tmp, module_name, inject=inject) result = self.action_plugins['normal'].run(conn, tmp, module_name, inject)
else: else:
result = self._execute_async_module(conn, tmp, module_name, inject=inject) result = self.action_plugins['async'].run(conn, tmp, module_name, inject)
if result.is_successful() and 'daisychain' in result.result: if result.is_successful() and 'daisychain' in result.result:
self.module_name = result.result['daisychain'] self.module_name = result.result['daisychain']
@ -616,11 +411,6 @@ class Runner(object):
elif not result.is_successful(): elif not result.is_successful():
ignore_errors = self.module_vars.get('ignore_errors', False) ignore_errors = self.module_vars.get('ignore_errors', False)
self.callbacks.on_failed(host, data, ignore_errors) self.callbacks.on_failed(host, data, ignore_errors)
if ignore_errors:
if 'failed' in result.result:
result.result['failed'] = False
if 'rc' in result.result:
result.result['rc'] = 0
else: else:
self.callbacks.on_ok(host, data) self.callbacks.on_ok(host, data)
return result return result

@ -0,0 +1,43 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject=None):
''' handler for assemble operations '''
# FIXME: since assemble is ported over to the use the new common logic, this method
# is actually unneccessary as it can decide to daisychain via it's own module returns.
# make assemble return daisychain_args and this will go away.
return self.runner._execute_module(conn, tmp, 'assemble', self.runner.module_args, inject=inject).daisychain('file')

@ -0,0 +1,53 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
''' transfer the given module name, plus the async module, then run it '''
# shell and command module are the same
module_args = self.runner.module_args
if module_name == 'shell':
module_name = 'command'
module_args += " #USE_SHELL"
(module_path, is_new_style) = self.runner._copy_module(conn, tmp, module_name, inject)
self.runner._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp)
return self.runner._execute_module(conn, tmp, 'async_wrapper', module_args,
async_module=module_path,
async_jid=self.runner.generated_jid,
async_limit=self.runner.background,
inject=inject
)

@ -0,0 +1,87 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
''' handler for file transfer operations '''
# load up options
options = utils.parse_kv(self.runner.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and not 'first_available_file' in inject) or dest is None:
result=dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in inject.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
results=dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(conn=conn, results=results)
source = utils.template(source, inject)
source = utils.path_dwim(self.runner.basedir, source)
local_md5 = utils.md5(source)
if local_md5 is None:
result=dict(failed=True, msg="could not find src=%s" % source)
return ReturnData(conn=conn, result=result)
remote_md5 = self.runner._remote_md5(conn, tmp, dest)
exec_rc = None
if local_md5 != remote_md5:
# transfer the file to a remote tmp location
tmp_src = tmp + os.path.basename(source)
conn.put_file(source, tmp_src)
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root':
self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp)
# run the copy module
self.runner.module_args = "%s src=%s" % (self.runner.module_args, tmp_src)
return self.runner._execute_module(conn, tmp, 'copy', self.runner.module_args, inject=inject).daisychain('file')
else:
# no need to transfer the file, already correct md5
result = dict(changed=False, md5sum=remote_md5, transferred=False)
return ReturnData(conn=conn, result=result).daisychain('file')

@ -0,0 +1,89 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
''' handler for fetch operations '''
# load up options
options = utils.parse_kv(self.runner.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if source is None or dest is None:
results = dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, result=results)
# apply templating to source argument
source = utils.template(source, inject)
# apply templating to dest argument
dest = utils.template(dest, inject)
# files are saved in dest dir, with a subdir for each host, then the filename
dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source)
dest = dest.replace("//","/")
# calculate md5 sum for the remote file
remote_md5 = self.runner._remote_md5(conn, tmp, source)
# these don't fail because you may want to transfer a log file that possibly MAY exist
# but keep going to fetch other log files
if remote_md5 == '0':
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
return ReturnData(conn=conn, result=result)
if remote_md5 == '1':
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
return ReturnData(conn=conn, result=result)
if remote_md5 == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
return ReturnData(conn=conn, result=result)
# calculate md5 sum for the local file
local_md5 = utils.md5(dest)
if remote_md5 != local_md5:
# create the containing directories, if needed
if not os.path.isdir(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest))
# fetch the file and check for changes
conn.fetch_file(source, dest)
new_md5 = utils.md5(dest)
if new_md5 != remote_md5:
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
return ReturnData(conn=conn, result=result)
result = dict(changed=True, md5sum=new_md5)
return ReturnData(conn=conn, result=result)
else:
result = dict(changed=False, md5sum=local_md5, file=source)
return ReturnData(conn=conn, result=result)

@ -0,0 +1,47 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
from ansible.callbacks import vv, vvv
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
''' transfer & execute a module that is not 'copy' or 'template' '''
# shell and command are the same module
if module_name == 'shell':
module_name = 'command'
self.runner.module_args += " #USE_SHELL"
vv("REMOTE_MODULE %s %s" % (module_name, self.runner.module_args), host=conn.host)
return self.runner._execute_module(conn, tmp, module_name, self.runner.module_args, inject=inject)

@ -0,0 +1,39 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
return ReturnData(conn=conn, result=dict(
stdout=self.runner._low_level_exec_command(conn, self.runner.module_args.encode('utf-8'), tmp, sudoable=True)
))

@ -0,0 +1,81 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.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/>.
import os
import pwd
import random
import traceback
import tempfile
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import module_common
from ansible.runner.return_data import ReturnData
class ActionModule(object):
def __init__(self, runner):
self.runner = runner
def run(self, conn, tmp, module_name, inject):
''' handler for template operations '''
if not self.runner.is_playbook:
raise errors.AnsibleError("in current versions of ansible, templates are only usable in playbooks")
# load up options
options = utils.parse_kv(self.runner.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and 'first_available_file' not in inject) or dest is None:
result = dict(failed=True, msg="src and dest are required")
return ReturnData(conn=conn, comm_ok=False, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in self.runner.module_vars.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
result = dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(conn=conn, comm_ok=False, result=result)
source = utils.template(source, inject)
# template the source data locally & transfer
try:
resultant = utils.template_from_file(self.runner.basedir, source, inject)
except Exception, e:
result = dict(failed=True, msg=str(e))
return ReturnData(conn=conn, comm_ok=False, result=result)
xfered = self.runner._transfer_str(conn, tmp, 'source', resultant)
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root':
self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered,
tmp)
# run the copy module, queue the file module
self.runner.module_args = "%s src=%s dest=%s" % (self.runner.module_args, xfered, dest)
return self.runner._execute_module(conn, tmp, 'copy', self.runner.module_args, inject=inject).daisychain('file')

@ -243,6 +243,8 @@ def template_from_file(basedir, path, vars):
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(basedir), trim_blocks=False) environment = jinja2.Environment(loader=jinja2.FileSystemLoader(basedir), trim_blocks=False)
environment.filters['to_json'] = json.dumps environment.filters['to_json'] = json.dumps
environment.filters['from_json'] = json.loads environment.filters['from_json'] = json.loads
environment.filters['to_yaml'] = yaml.dump
environment.filters['from_yaml'] = yaml.load
data = codecs.open(path_dwim(basedir, path), encoding="utf8").read() data = codecs.open(path_dwim(basedir, path), encoding="utf8").read()
t = environment.from_string(data) t = environment.from_string(data)
vars = vars.copy() vars = vars.copy()
@ -323,7 +325,19 @@ def _gitinfo():
''' returns a string containing git branch, commit id and commit date ''' ''' returns a string containing git branch, commit id and commit date '''
result = None result = None
repo_path = os.path.join(os.path.dirname(__file__), '..', '..', '.git') repo_path = os.path.join(os.path.dirname(__file__), '..', '..', '.git')
if os.path.exists(repo_path): if os.path.exists(repo_path):
# Check if the .git is a file. If it is a file, it means that we are in a submodule structure.
if os.path.isfile(repo_path):
try:
gitdir = yaml.load(open(repo_path)).get('gitdir')
# There is a posibility the .git file to have an absolute path.
if os.path.isabs(gitdir):
repo_path = gitdir
else:
repo_path = os.path.join(repo_path.split('.git')[0], gitdir)
except (IOError, AttributeError):
return ''
f = open(os.path.join(repo_path, "HEAD")) f = open(os.path.join(repo_path, "HEAD"))
branch = f.readline().split('/')[-1].rstrip("\n") branch = f.readline().split('/')[-1].rstrip("\n")
f.close() f.close()
@ -340,7 +354,7 @@ def _gitinfo():
result = "({0} {1}) last updated {2} (GMT {3:+04d})".format(branch, commit, result = "({0} {1}) last updated {2} (GMT {3:+04d})".format(branch, commit,
time.strftime("%Y/%m/%d %H:%M:%S", date), offset / -36) time.strftime("%Y/%m/%d %H:%M:%S", date), offset / -36)
else: else:
result = 'n/a' result = ''
return result return result
def version(prog): def version(prog):
@ -380,7 +394,7 @@ def base_parser(constants=C, usage="", output_opts=False, runas_opts=False,
default=constants.DEFAULT_HOST_LIST) default=constants.DEFAULT_HOST_LIST)
parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true', parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true',
help='ask for SSH password') help='ask for SSH password')
parser.add_option('--private-key', default=None, dest='private_key_file', parser.add_option('--private-key', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file',
help='use this file to authenticate the connection') help='use this file to authenticate the connection')
parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true', parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true',
help='ask for sudo password') help='ask for sudo password')
@ -473,8 +487,11 @@ def filter_leading_non_json_lines(buf):
def import_plugins(directory): def import_plugins(directory):
modules = {} modules = {}
for path in glob.glob(os.path.join(directory, '*.py')): for path in glob.glob(os.path.join(directory, '*.py')):
if path.startswith("_"):
continue
name, ext = os.path.splitext(os.path.basename(path)) name, ext = os.path.splitext(os.path.basename(path))
modules[name] = imp.load_source(name, path) if not name.startswith("_"):
modules[name] = imp.load_source(name, path)
return modules return modules

@ -50,6 +50,7 @@ def main():
argument_spec = dict( argument_spec = dict(
src = dict(required=True), src = dict(required=True),
dest = dict(required=True), dest = dict(required=True),
backup=dict(default=False, choices=BOOLEANS),
) )
) )
@ -58,6 +59,7 @@ def main():
destmd5 = None destmd5 = None
src = os.path.expanduser(module.params['src']) src = os.path.expanduser(module.params['src'])
dest = os.path.expanduser(module.params['dest']) dest = os.path.expanduser(module.params['dest'])
backup = module.boolean(module.params.get('backup', False))
if not os.path.exists(src): if not os.path.exists(src):
module.fail_json(msg="Source (%s) does not exist" % src) module.fail_json(msg="Source (%s) does not exist" % src)
@ -72,6 +74,8 @@ def main():
destmd5 = module.md5(dest) destmd5 = module.md5(dest)
if pathmd5 != destmd5: if pathmd5 != destmd5:
if backup and destmd5 is not None:
module.backup_local(dest)
shutil.copy(path, dest) shutil.copy(path, dest)
changed = True changed = True

@ -39,7 +39,7 @@ def main():
module.fail_json(msg="no command given") module.fail_json(msg="no command given")
if chdir: if chdir:
os.chdir(chdir) os.chdir(os.path.expanduser(chdir))
if not shell: if not shell:
args = shlex.split(args) args = shlex.split(args)
@ -114,8 +114,24 @@ class CommandModule(AnsibleModule):
rc=0 rc=0
) )
args = args.replace(x,'') args = args.replace(x,'')
elif x.startswith("removes="):
# do not run the command if the line contains removes=filename
# and the filename do not exists. This allows idempotence
# of command executions.
(k,v) = x.split("=",1)
if not os.path.exists(v):
self.exit_json(
cmd=args,
stdout="skipped, since %s do not exists" % v,
skipped=True,
changed=False,
stderr=False,
rc=0
)
args = args.replace(x,'')
elif x.startswith("chdir="): elif x.startswith("chdir="):
(k,v) = x.split("=", 1) (k,v) = x.split("=", 1)
v = os.path.expanduser(v)
if not (os.path.exists(v) and os.path.isdir(v)): if not (os.path.exists(v) and os.path.isdir(v)):
self.fail_json(msg="cannot change to directory '%s': path does not exist" % v) self.fail_json(msg="cannot change to directory '%s': path does not exist" % v)
elif v[0] != '/': elif v[0] != '/':

@ -20,19 +20,6 @@
import os import os
import shutil import shutil
import time
def backuplocal(fn):
"""make a date-marked backup of the specified file, return True or False on success or failure"""
# backups named basename-YYYY-MM-DD@HH:MM~
ext = time.strftime("%Y-%m-%d@%H:%M~", time.localtime(time.time()))
backupdest = '%s.%s' % (fn, ext)
try:
shutil.copy2(fn, backupdest)
except shutil.Error, e:
return False, 'Could not make backup of %s to %s: %s' % (fn, backupdest, e)
return True, backupdest
def main(): def main():
@ -76,11 +63,7 @@ def main():
try: try:
if backup: if backup:
if os.path.exists(dest): if os.path.exists(dest):
success, msg = backuplocal(dest) backup_file = module.backup_local(dest)
if not success:
module.fail_jason(msg=msg)
else:
backup_file = msg
shutil.copyfile(src, dest) shutil.copyfile(src, dest)
except shutil.Error: except shutil.Error:
module.fail_json(msg="failed to copy: %s and %s are the same" % (src, dest)) module.fail_json(msg="failed to copy: %s and %s are the same" % (src, dest))

@ -21,14 +21,14 @@
import re import re
import os import os
def present(module, name, regexp, line, insertafter): def present(module, name, regexp, line, insertafter, backup):
f = open(name, 'rb') f = open(name, 'rb')
lines = f.readlines() lines = f.readlines()
f.close() f.close()
mre = re.compile(regexp) mre = re.compile(regexp)
if not mre.match(line): if not mre.search(line):
module.fail_json(msg="line= doesn't match regexp=") module.fail_json(msg="usage error: line= doesn't match regexp (%s)" % regexp)
if insertafter in ('BOF', 'EOF'): if insertafter in ('BOF', 'EOF'):
iare = None iare = None
@ -37,9 +37,9 @@ def present(module, name, regexp, line, insertafter):
index = [-1, -1] index = [-1, -1]
for lineno in range(0, len(lines)): for lineno in range(0, len(lines)):
if mre.match(lines[lineno]): if mre.search(lines[lineno]):
index[0] = lineno index[0] = lineno
elif iare is not None and iare.match(lines[lineno]): elif iare is not None and iare.search(lines[lineno]):
# + 1 for the next line # + 1 for the next line
index[1] = lineno + 1 index[1] = lineno + 1
@ -69,20 +69,22 @@ def present(module, name, regexp, line, insertafter):
changed = True changed = True
if changed: if changed:
if backup:
module.backup_local(name)
f = open(name, 'wb') f = open(name, 'wb')
f.writelines(lines) f.writelines(lines)
f.close() f.close()
module.exit_json(changed=changed, msg=msg) module.exit_json(changed=changed, msg=msg)
def absent(module, name, regexp): def absent(module, name, regexp, backup):
f = open(name, 'rb') f = open(name, 'rb')
lines = f.readlines() lines = f.readlines()
f.close() f.close()
cre = re.compile(regexp) cre = re.compile(regexp)
found = [] found = []
def matcher(line): def matcher(line):
if cre.match(line): if cre.search(line):
found.append(line) found.append(line)
return False return False
else: else:
@ -90,6 +92,8 @@ def absent(module, name, regexp):
lines = filter(matcher, lines) lines = filter(matcher, lines)
changed = len(found) > 0 changed = len(found) > 0
if changed: if changed:
if backup:
module.backup_local(name)
f = open(name, 'wb') f = open(name, 'wb')
f.writelines(lines) f.writelines(lines)
f.close() f.close()
@ -103,18 +107,20 @@ def main():
regexp=dict(required=True), regexp=dict(required=True),
line=dict(aliases=['value']), line=dict(aliases=['value']),
insertafter=dict(default='EOF'), insertafter=dict(default='EOF'),
backup=dict(default=False, choices=BOOLEANS),
), ),
) )
params = module.params params = module.params
backup = module.boolean(module.params.get('backup', False))
if params['state'] == 'present': if params['state'] == 'present':
if 'line' not in params: if 'line' not in params:
module.fail_json(msg='line= is required with state=present') module.fail_json(msg='line= is required with state=present')
present(module, params['name'], params['regexp'], params['line'], present(module, params['name'], params['regexp'], params['line'],
params['insertafter']) params['insertafter'], backup)
else: else:
absent(module, params['name'], params['regexp']) absent(module, params['name'], params['regexp'], backup)
# this is magic, see lib/ansible/module_common.py # this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>> #<<INCLUDE_ANSIBLE_MODULE_COMMON>>

@ -77,6 +77,7 @@ def main():
login_user=dict(default="postgres"), login_user=dict(default="postgres"),
login_password=dict(default=""), login_password=dict(default=""),
login_host=dict(default=""), login_host=dict(default=""),
port=dict(default="5432"),
db=dict(required=True, aliases=['name']), db=dict(required=True, aliases=['name']),
owner=dict(default=""), owner=dict(default=""),
template=dict(default=""), template=dict(default=""),
@ -89,6 +90,7 @@ def main():
module.fail_json(msg="the python psycopg2 module is required") module.fail_json(msg="the python psycopg2 module is required")
db = module.params["db"] db = module.params["db"]
port = module.params["port"]
owner = module.params["owner"] owner = module.params["owner"]
template = module.params["template"] template = module.params["template"]
encoding = module.params["encoding"] encoding = module.params["encoding"]
@ -101,7 +103,8 @@ def main():
params_map = { params_map = {
"login_host":"host", "login_host":"host",
"login_user":"user", "login_user":"user",
"login_password":"password" "login_password":"password",
"port":"port"
} }
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems() kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
if k in params_map and v != '' ) if k in params_map and v != '' )

@ -115,6 +115,8 @@ def get_database_privileges(cursor, user, db):
query = 'SELECT datacl FROM pg_database WHERE datname = %s' query = 'SELECT datacl FROM pg_database WHERE datname = %s'
cursor.execute(query, (db,)) cursor.execute(query, (db,))
datacl = cursor.fetchone()[0] datacl = cursor.fetchone()[0]
if datacl is None:
return []
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl) r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
if r is None: if r is None:
return [] return []
@ -224,6 +226,7 @@ def main():
state=dict(default="present", choices=["absent", "present"]), state=dict(default="present", choices=["absent", "present"]),
priv=dict(default=None), priv=dict(default=None),
db=dict(default=''), db=dict(default=''),
port=dict(default='5432'),
fail_on_user=dict(default='yes') fail_on_user=dict(default='yes')
) )
) )
@ -235,6 +238,7 @@ def main():
if db == '' and module.params["priv"] is not None: if db == '' and module.params["priv"] is not None:
module.fail_json(msg="privileges require a database to be specified") module.fail_json(msg="privileges require a database to be specified")
privs = parse_privs(module.params["priv"], db) privs = parse_privs(module.params["priv"], db)
port = module.params["port"]
if not postgresqldb_found: if not postgresqldb_found:
module.fail_json(msg="the python psycopg2 module is required") module.fail_json(msg="the python psycopg2 module is required")
@ -246,6 +250,7 @@ def main():
"login_host":"host", "login_host":"host",
"login_user":"user", "login_user":"user",
"login_password":"password", "login_password":"password",
"port":"port",
"db":"database" "db":"database"
} }
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems() kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()

@ -58,6 +58,7 @@ class Facts(object):
self.facts = {} self.facts = {}
self.get_platform_facts() self.get_platform_facts()
self.get_distribution_facts() self.get_distribution_facts()
self.get_cmdline()
self.get_public_ssh_host_keys() self.get_public_ssh_host_keys()
self.get_selinux_facts() self.get_selinux_facts()
@ -103,9 +104,26 @@ class Facts(object):
else: else:
self.facts['distribution'] = name self.facts['distribution'] = name
def get_cmdline(self):
data = get_file_content('/proc/cmdline')
if data:
self.facts['cmdline'] = {}
for piece in shlex.split(data):
item = piece.split('=', 1)
if len(item) == 1:
self.facts['cmdline'][item[0]] = True
else:
self.facts['cmdline'][item[0]] = item[1]
def get_public_ssh_host_keys(self): def get_public_ssh_host_keys(self):
dsa = get_file_content('/etc/ssh/ssh_host_dsa_key.pub') dsa_filename = '/etc/ssh/ssh_host_dsa_key.pub'
rsa = get_file_content('/etc/ssh/ssh_host_rsa_key.pub') rsa_filename = '/etc/ssh/ssh_host_rsa_key.pub'
if self.facts['system'] == 'Darwin':
dsa_filename = '/etc/ssh_host_dsa_key.pub'
rsa_filename = '/etc/ssh_host_rsa_key.pub'
dsa = get_file_content(dsa_filename)
rsa = get_file_content(rsa_filename)
if dsa is None: if dsa is None:
dsa = 'NA' dsa = 'NA'
else: else:
@ -198,7 +216,7 @@ class LinuxHardware(Hardware):
MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree'] MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree']
# DMI bits # DMI bits
DMI_DICT = dict( DMI_DICT = dict(
form_factor = '/sys/devices/virtual/dmi/id/chassis_type', form_factor = '/sys/devices/virtual/dmi/id/chassis_type',
product_name = '/sys/devices/virtual/dmi/id/product_name', product_name = '/sys/devices/virtual/dmi/id/product_name',
product_serial = '/sys/devices/virtual/dmi/id/product_serial', product_serial = '/sys/devices/virtual/dmi/id/product_serial',
product_uuid = '/sys/devices/virtual/dmi/id/product_uuid', product_uuid = '/sys/devices/virtual/dmi/id/product_uuid',
@ -461,7 +479,9 @@ class LinuxNetwork(Network):
) )
interface = dict(v4 = {}, v6 = {}) interface = dict(v4 = {}, v6 = {})
for v in 'v4', 'v6': for v in 'v4', 'v6':
output = subprocess.Popen(command[v], stdout=subprocess.PIPE).communicate()[0] if v == 'v6' and not socket.has_ipv6:
continue
output = subprocess.Popen(command[v], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
if not output: if not output:
# v6 routing may result in # v6 routing may result in
# RTNETLINK answers: Invalid argument # RTNETLINK answers: Invalid argument
@ -484,7 +504,7 @@ class LinuxNetwork(Network):
all_ipv4_addresses = [], all_ipv4_addresses = [],
all_ipv6_addresses = [], all_ipv6_addresses = [],
) )
output = subprocess.Popen([ip_path, 'addr', 'show'], stdout=subprocess.PIPE).communicate()[0] output = subprocess.Popen([ip_path, 'addr', 'show'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
for line in output.split('\n'): for line in output.split('\n'):
if line: if line:
words = line.split() words = line.split()
@ -517,9 +537,9 @@ class LinuxNetwork(Network):
# interface name for each address # interface name for each address
if iface in interfaces: if iface in interfaces:
i = 0 i = 0
while '{0}_{1}'.format(iface, i) in interfaces: while str(iface) + "_" + str(i) in interfaces:
i += 1 i += 1
iface = '{0}_{1}'.format(iface, i) iface = str(iface) + "_" + str(i)
interfaces[iface] = {} interfaces[iface] = {}
interfaces[iface]['macaddress'] = macaddress interfaces[iface]['macaddress'] = macaddress

@ -99,7 +99,7 @@ def main():
) )
) )
dest = module.params['dest'] dest = os.path.expanduser(module.params['dest'])
repo = module.params['repo'] repo = module.params['repo']
revision = module.params['revision'] revision = module.params['revision']
force = module.boolean(module.params['force']) force = module.boolean(module.params['force'])

@ -19,10 +19,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# #
def _is_present(name): def _is_present(name, supervisorctl):
rc, out, err = _run('%s status' % _find_supervisorctl()) rc, out, err = _run('%s status' % supervisorctl)
return name in out return name in out
def _is_running(name, supervisorctl): def _is_running(name, supervisorctl):
rc, out, err = _run('%s status %s' % (supervisorctl, name)) rc, out, err = _run('%s status %s' % (supervisorctl, name))
return 'RUNNING' in out return 'RUNNING' in out
@ -49,10 +50,7 @@ def main():
SUPERVISORCTL = module.get_bin_path('supervisorctl', True) SUPERVISORCTL = module.get_bin_path('supervisorctl', True)
if SUPERVISORCTL is None: present = _is_present(name, SUPERVISORCTL)
module.fail_json(msg='supervisorctl is not installed')
present = _is_present(name)
if state == 'present': if state == 'present':
if not present: if not present:

@ -19,6 +19,8 @@ VIRT_FAILED = 1
VIRT_SUCCESS = 0 VIRT_SUCCESS = 0
VIRT_UNAVAILABLE=2 VIRT_UNAVAILABLE=2
import sys
try: try:
import libvirt import libvirt
except ImportError: except ImportError:

@ -291,7 +291,7 @@ def install(module, items, repoq, yum_basecmd, conf_file):
nvra = local_nvra(spec) nvra = local_nvra(spec)
# look for them in the rpmdb # look for them in the rpmdb
if is_installed(repoq, nvra, conf_file): if is_installed(module, repoq, nvra, conf_file):
# if they are there, skip it # if they are there, skip it
continue continue
pkg = spec pkg = spec

@ -1,12 +1,18 @@
ansible (0.8) unstable; urgency=low
* 0.8 release pending
-- Michael DeHaan <michael.dehaan@gmail.com> Thu, 06 Aug 2012 18:50:01 -0400
ansible (0.7) unstable; urgency=low ansible (0.7) unstable; urgency=low
* 0.6 not released yet * 0.7 update
-- Michael DeHaan <michael.dehaan@gmail.com> Mon, 06 Aug 2012 19:50:01 -0400 -- Michael DeHaan <michael.dehaan@gmail.com> Thu, 06 Aug 2012 18:50:00 -0400
ansible (0.6) unstable; urgency=low ansible (0.6) unstable; urgency=low
* 0.5 update * 0.6 update
-- Michael DeHaan <michael.dehaan@gmail.com> Mon, 06 Aug 2012 19:50:00 -0400 -- Michael DeHaan <michael.dehaan@gmail.com> Mon, 06 Aug 2012 19:50:00 -0400

@ -1,7 +1,7 @@
# $FreeBSD$ # $FreeBSD$
PORTNAME= ansible PORTNAME= ansible
PORTVERSION= 0.6 PORTVERSION= 0.8
CATEGORIES= devel www textproc python CATEGORIES= devel www textproc python
MASTER_SITES= https://github.com/downloads/ansible/ansible/ MASTER_SITES= https://github.com/downloads/ansible/ansible/

@ -6,7 +6,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Name: ansible Name: ansible
Release: 1%{?dist} Release: 1%{?dist}
Summary: SSH-based configuration management, deployment, and task execution system Summary: SSH-based configuration management, deployment, and task execution system
Version: 0.7 Version: 0.8
Group: Development/Libraries Group: Development/Libraries
License: GPLv3 License: GPLv3
@ -59,8 +59,11 @@ rm -rf $RPM_BUILD_ROOT
%doc examples/playbooks %doc examples/playbooks
%changelog %changelog
* Mon Aug 6 2012 Michael DeHaan <michael.dehaan@gmail.com> - 0.7-0 * Thu Aug 7 2012 Michael DeHaan <michael.dehaan@gmail.com> - 0.8-0
- not released yet - pending
* Thu Aug 6 2012 Michael DeHaan <michael.dehaan@gmail.com> - 0.7-0
- Release of 0.7
* Mon Aug 6 2012 Michael DeHaan <michael.dehaan@gmail.com> - 0.6-0 * Mon Aug 6 2012 Michael DeHaan <michael.dehaan@gmail.com> - 0.6-0
- Release of 0.6 - Release of 0.6

@ -28,7 +28,9 @@ setup(name='ansible',
'ansible.inventory', 'ansible.inventory',
'ansible.playbook', 'ansible.playbook',
'ansible.runner', 'ansible.runner',
'ansible.runner.action_plugins',
'ansible.runner.connection_plugins', 'ansible.runner.connection_plugins',
'ansible.runner.action_plugins',
'ansible.callback_plugins', 'ansible.callback_plugins',
], ],
scripts=[ scripts=[

@ -238,3 +238,11 @@ class TestInventory(unittest.TestCase):
'group_names': ['norse'], 'group_names': ['norse'],
'inventory_hostname': 'thor', 'inventory_hostname': 'thor',
'inventory_hostname_short': 'thor'} 'inventory_hostname_short': 'thor'}
def test_hosts_list(self):
"""Test the case when playbook 'hosts' var is a list."""
inventory = self.script_inventory()
host_names = sorted(['thor', 'loki', 'odin']) # Not sure if sorting is in the contract or not
actual_hosts = inventory.get_hosts(host_names)
actual_host_names = [host.name for host in actual_hosts]
assert host_names == actual_host_names

@ -179,5 +179,5 @@ class TestPlaybook(unittest.TestCase):
callbacks=test_callbacks, callbacks=test_callbacks,
runner_callbacks=test_callbacks runner_callbacks=test_callbacks
) )
play = ansible.playbook.Play(playbook, playbook.playbook[0]) play = ansible.playbook.Play(playbook, playbook.playbook[0], os.getcwd())
assert play.hosts == ';'.join(('host1', 'host2', 'host3')) assert play.hosts == ';'.join(('host1', 'host2', 'host3'))

Loading…
Cancel
Save