Commit c00699d0 by Michael DeHaan

Merge branch 'integration'

Conflicts:
	lib/ansible/playbook.py
	lib/ansible/runner.py
	library/apt
parents 1c5bcb60 321ed53e
include README.md ansible.spec
include README.md packaging/rpm/ansible.spec
include examples/hosts
include packaging/distutils/setup.py
recursive-include docs *
recursive-include library *
include Makefile
#!/usr/bin/make
########################################################
# Makefile for Ansible
#
# useful targets:
# make sdist ---------------- produce a tarball
# make rpm ----------------- produce RPMs
# make debian --------------- produce a dpkg (FIXME?)
# make docs ----------------- rebuild the manpages (results are checked in)
# make tests ---------------- run the tests
# make pyflakes, make pep8 -- source code checks
########################################################
# variable section
NAME = "ansible"
# Manpages are currently built with asciidoc -- would like to move to markdown
# This doesn't evaluate until it's called. The -D argument is the
# directory of the target file ($@), kinda like `dirname`.
ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $<
ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml
MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1
SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < ansible.spec | cut -d "%" -f1)
RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < ansible.spec | cut -d "%" -f1)
RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)"
# VERSION file provides one place to update the software version
VERSION := $(shell cat VERSION)
# RPM build parameters
RPMSPECDIR= packaging/rpm
RPMSPEC = $(RPMSPECDIR)/ansible.spec
RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1)
RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1)
RPMDIST = $(shell rpm --eval '%dist')
RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)"
########################################################
all: clean python
tests:
PYTHONPATH=./lib nosetests -v
# To force a rebuild of the docs run 'touch VERSION && make docs'
docs: $(MANPAGES)
%.1: %.1.asciidoc
$(ASCII2MAN)
# Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more
# recently than %.1.asciidoc.
%.1.asciidoc: %.1.asciidoc.in
sed "s/%VERSION%/$(VERSION)/" $< > $@
%.5: %.5.asciidoc
# Regenerate %.1 if %.1.asciidoc or VERSION has been modified more
# recently than %.1. (Implicitly runs the %.1.asciidoc recipe)
%.1: %.1.asciidoc VERSION
$(ASCII2MAN)
loc:
......@@ -29,26 +63,30 @@ pep8:
@echo "#############################################"
@echo "# Running PEP8 Compliance Tests"
@echo "#############################################"
pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225 lib/ bin/
pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261 lib/ bin/
pyflakes:
pyflakes lib/ansible/*.py bin/*
clean:
@echo "Cleaning up distutils stuff"
-rm -rf build
-rm -rf dist
rm -rf build
rm -rf dist
@echo "Cleaning up byte compiled python stuff"
find . -regex ".*\.py[co]$$" -delete
find . -type f -regex ".*\.py[co]$$" -delete
@echo "Cleaning up editor backup files"
find . -type f \( -name "*~" -or -name "#*" \) -delete
find . -type f \( -name "*.swp" \) -delete
@echo "Cleaning up asciidoc to man transformations and results"
find ./docs/man -type f -name "*.xml" -delete
find ./docs/man -type f -name "*.asciidoc" -delete
@echo "Cleaning up output from test runs"
-rm -rf test/test_data
rm -rf test/test_data
@echo "Cleaning up RPM building stuff"
-rm -rf MANIFEST rpm-build
rm -rf MANIFEST rpm-build
@echo "Cleaning up Debian building stuff"
rm -rf debian
rm -rf deb-build
python:
python setup.py build
......@@ -59,7 +97,7 @@ install:
python setup.py install
sdist: clean
python ./setup.py sdist
python setup.py sdist -t MANIFEST.in
rpmcommon: sdist
@mkdir -p rpm-build
......@@ -70,9 +108,9 @@ srpm: rpmcommon
--define "_builddir %{_topdir}" \
--define "_rpmdir %{_topdir}" \
--define "_srcrpmdir %{_topdir}" \
--define "_specdir %{_topdir}" \
--define "_specdir $(RPMSPECDIR)" \
--define "_sourcedir %{_topdir}" \
-bs ansible.spec
-bs $(RPMSPEC)
@echo "#############################################"
@echo "Ansible SRPM is built:"
@echo " rpm-build/$(RPMNVR).src.rpm"
......@@ -83,13 +121,21 @@ rpm: rpmcommon
--define "_builddir %{_topdir}" \
--define "_rpmdir %{_topdir}" \
--define "_srcrpmdir %{_topdir}" \
--define "_specdir %{_topdir}" \
--define "_specdir $(RPMSPECDIR)" \
--define "_sourcedir %{_topdir}" \
-ba ansible.spec
-ba $(RPMSPEC)
@echo "#############################################"
@echo "Ansible RPM is built:"
@echo " rpm-build/noarch/$(RPMNVR).noarch.rpm"
@echo "#############################################"
.PHONEY: docs manual clean pep8
vpath %.asciidoc docs/man/man1
debian: sdist
deb: debian
cp -r packaging/debian ./
chmod 755 debian/rules
fakeroot debian/rules clean
fakeroot dh_install
fakeroot debian/rules binary
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
0.0.2
\ No newline at end of file
......@@ -22,13 +22,13 @@
import sys
import getpass
import time
from optparse import OptionParser
import ansible.runner
import ansible.constants as C
from ansible import utils
from ansible import errors
from ansible import callbacks
from ansible import inventory
########################################################
......@@ -47,7 +47,7 @@ class Cli(object):
def parse(self):
''' create an options parser for bin/ansible '''
parser = utils.base_parser(constants=C, port_opts=True, runas_opts=True, async_opts=True,
parser = utils.base_parser(constants=C, runas_opts=True, async_opts=True,
output_opts=True, connect_opts=True, usage='%prog <host-pattern> [options]')
parser.add_option('-a', '--args', dest='module_args',
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
......@@ -69,6 +69,13 @@ class Cli(object):
''' use Runner lib to do SSH things '''
pattern = args[0]
inventory_manager = inventory.Inventory(options.inventory)
hosts = inventory_manager.list_hosts(pattern)
if len(hosts) == 0:
print >>sys.stderr, "No hosts matched"
sys.exit(1)
sshpass = None
sudopass = None
if options.ask_pass:
......@@ -78,7 +85,6 @@ class Cli(object):
if options.tree:
utils.prepare_writeable_dir(options.tree)
if options.seconds:
print "background launch...\n\n"
......@@ -86,11 +92,11 @@ class Cli(object):
module_name=options.module_name, module_path=options.module_path,
module_args=options.module_args,
remote_user=options.remote_user, remote_pass=sshpass,
host_list=options.inventory, timeout=options.timeout,
remote_port=options.remote_port, forks=options.forks,
inventory=inventory_manager, timeout=options.timeout,
forks=options.forks,
background=options.seconds, pattern=pattern,
callbacks=self.callbacks, sudo=options.sudo,
sudo_pass=sudopass, verbose=True,
sudo_pass=sudopass,
transport=options.connection, debug=options.debug
)
return (runner, runner.run())
......@@ -98,14 +104,13 @@ class Cli(object):
# ----------------------------------------------
def get_polling_runner(self, old_runner, hosts, jid):
def get_polling_runner(self, old_runner, jid):
return ansible.runner.Runner(
module_name='async_status', module_path=old_runner.module_path,
module_args="jid=%s" % jid, remote_user=old_runner.remote_user,
remote_pass=old_runner.remote_pass, host_list=hosts,
remote_pass=old_runner.remote_pass, inventory=old_runner.inventory,
timeout=old_runner.timeout, forks=old_runner.forks,
remote_port=old_runner.remote_port, pattern='*',
callbacks=self.silent_callbacks, verbose=True,
pattern='*', callbacks=self.silent_callbacks,
)
# ----------------------------------------------
......@@ -138,8 +143,10 @@ class Cli(object):
clock = options.seconds
while (clock >= 0):
polling_runner = self.get_polling_runner(runner, poll_hosts, jid)
runner.inventory.restrict_to(poll_hosts)
polling_runner = self.get_polling_runner(runner, jid)
poll_results = polling_runner.run()
runner.inventory.lift_restriction()
if poll_results is None:
break
for (host, host_result) in poll_results['contacted'].iteritems():
......
......@@ -20,7 +20,6 @@
import sys
import getpass
from optparse import OptionParser
import ansible.playbook
import ansible.constants as C
......@@ -33,9 +32,7 @@ def main(args):
# create parser for CLI options
usage = "%prog playbook.yml"
parser = utils.base_parser(constants=C, usage=usage, connect_opts=True)
parser.add_option('-e', '--extra-vars', dest='extra_vars',
help='arguments to pass to the inventory script')
parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, runas_opts=True)
parser.add_option('-O', '--override-hosts', dest="override_hosts", default=None,
help="run playbook against these hosts regardless of inventory settings")
......@@ -63,13 +60,20 @@ def main(args):
runner_cb = callbacks.PlaybookRunnerCallbacks(stats)
pb = ansible.playbook.PlayBook(
playbook=playbook,module_path=options.module_path,
host_list=options.inventory, override_hosts=override_hosts,
extra_vars=options.extra_vars,
forks=options.forks, debug=options.debug, verbose=True,
playbook=playbook,
module_path=options.module_path,
host_list=options.inventory,
override_hosts=override_hosts,
forks=options.forks,
debug=options.debug,
remote_user=options.remote_user,
remote_pass=sshpass,
callbacks=playbook_cb, runner_callbacks=runner_cb, stats=stats,
timeout=options.timeout, transport=options.connection,
callbacks=playbook_cb,
runner_callbacks=runner_cb,
stats=stats,
timeout=options.timeout,
transport=options.connection,
sudo=options.sudo,
sudo_pass=sudopass
)
try:
......
'\" t
.\" Title: ansible-playbook
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 04/13/2012
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: 04/17/2012
.\" Manual: System administration commands
.\" Source: Ansible 0.0.2
.\" Language: English
.\"
.TH "ANSIBLE\-PLAYBOOK" "1" "04/13/2012" "Ansible 0\&.0\&.2" "System administration commands"
.TH "ANSIBLE\-PLAYBOOK" "1" "04/17/2012" "Ansible 0\&.0\&.2" "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
.\" -----------------------------------------------------------------
......@@ -77,17 +86,22 @@ Connection timeout to use when trying to talk to hosts, in
\fISECONDS\fR\&.
.RE
.PP
\fB\-e\fR \fIEXTRA_VARS\fR, \fB\-\-extra_vars=\fR\fIEXTRA_VARS\fR
.RS 4
An additional list of space delimited key=value pairs to pass into the playbook that are not declared in the vars section of the playbook\&.
.RE
.PP
\fB\-O\fR \fIOVERRIDE_HOSTS\fR, \fB\-\-override\-hosts=\fR\fIOVERRIDE_HOSTS\fR
.RS 4
Ignore the inventory file and run the playbook against only these hosts\&. "hosts:" line in playbook should be set to
\fIall\fR
when using this option\&.
.RE
.PP
\fB\-s\fR, \fB\-\-sudo\fR
.RS 4
Force all plays to use sudo, even if not marked as such\&.
.RE
.PP
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR
.RS 4
Use this remote user name on playbook steps that do not indicate a user name to run as\&.
.RE
.SH "ENVIRONMENT"
.sp
The following environment variables may specified\&.
......
......@@ -2,7 +2,7 @@ ansible-playbook(1)
===================
:doctype:manpage
:man source: Ansible
:man version: 0.0.2
:man version: %VERSION%
:man manual: System administration commands
NAME
......@@ -69,18 +69,22 @@ Prompt for the password to use for playbook plays that request sudo access, if a
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
*-e* 'EXTRA_VARS', *--extra_vars=*'EXTRA_VARS'::
An additional list of space delimited key=value pairs to pass into the playbook that are not
declared in the vars section of the playbook.
*-O* 'OVERRIDE_HOSTS', *--override-hosts=*'OVERRIDE_HOSTS'::
Ignore the inventory file and run the playbook against only these hosts. "hosts:" line
in playbook should be set to 'all' when using this option.
*-s*, *--sudo*::
Force all plays to use sudo, even if not marked as such.
*-u* 'USERNAME', *--remote-user=*'USERNAME'::
Use this remote user name on playbook steps that do not indicate a user name to run as.
ENVIRONMENT
-----------
......
'\" t
.\" Title: ansible
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 04/13/2012
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: 04/17/2012
.\" Manual: System administration commands
.\" Source: Ansible 0.0.2
.\" Language: English
.\"
.TH "ANSIBLE" "1" "04/13/2012" "Ansible 0\&.0\&.2" "System administration commands"
.TH "ANSIBLE" "1" "04/17/2012" "Ansible 0\&.0\&.2" "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
.\" -----------------------------------------------------------------
......@@ -25,7 +34,7 @@ ansible \- run a command somewhere else
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
.SH "DESCRIPTION"
.sp
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
.SH "ARGUMENTS"
.PP
\fBhost\-pattern\fR
......@@ -63,56 +72,79 @@ to load modules from\&. The default is
\fI/usr/share/ansible\fR\&.
.RE
.PP
\fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\'
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq
.RS 4
The
\fIARGUMENTS\fR
to pass to the module\&.
.RE
.sp
.PP
\fB\-D\fR, \fB\-\-debug\fR
.sp
.RS 4
Print any messages the remote module sends to standard error to the console
.sp
.RE
.PP
\fB\-k\fR, \fB\-\-ask\-pass\fR
.sp
.RS 4
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
.sp
.RE
.PP
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
.sp
.RS 4
Prompt for the password to use with \-\-sudo, if any
.sp
.RE
.PP
\fB\-o\fR, \fB\-\-one\-line\fR
.sp
.RS 4
Try to output everything on one line\&.
.sp
.RE
.PP
\fB\-s\fR, \fB\-\-sudo\fR
.sp
.RS 4
Run the command as the user given by \-u and sudo to root\&.
.sp
.RE
.PP
\fB\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR
.sp
Save contents in this output \fIDIRECTORY\fR, with the results saved in a file named after each host\&.
.sp
.RS 4
Save contents in this output
\fIDIRECTORY\fR, with the results saved in a file named after each host\&.
.RE
.PP
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
.sp
Connection timeout to use when trying to talk to hosts, in \fISECONDS\fR\&.
.sp
.RS 4
Connection timeout to use when trying to talk to hosts, in
\fISECONDS\fR\&.
.RE
.PP
\fB\-B\fR \fINUM\fR, \fB\-\-background=\fR\fINUM\fR
.sp
Run commands in the background, killing the task after \fINUM\fR seconds\&.
.sp
.RS 4
Run commands in the background, killing the task after
\fINUM\fR
seconds\&.
.RE
.PP
\fB\-P\fR \fINUM\fR, \fB\-\-poll=\fR\fINUM\fR
.sp
Poll a background job every \fINUM\fR seconds\&. Requires \fB\-B\fR\&.
.sp
.RS 4
Poll a background job every
\fINUM\fR
seconds\&. Requires
\fB\-B\fR\&.
.RE
.PP
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR
.sp
Use this remote \fIUSERNAME\fR instead of root\&.
.sp
.RS 4
Use this remote
\fIUSERNAME\fR
instead of root\&.
.RE
.PP
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
.sp
Connection type to use\&. Possible options are \fIparamiko\fR (SSH) and \fIlocal\fR\&. Local is mostly useful for crontab or kickstarts\&.
.RS 4
Connection type to use\&. Possible options are
\fIparamiko\fR
(SSH) and
\fIlocal\fR\&. Local is mostly useful for crontab or kickstarts\&.
.RE
.SH "INVENTORY"
.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\&.
......
......@@ -2,7 +2,7 @@ ansible(1)
=========
:doctype:manpage
:man source: Ansible
:man version: 0.0.2
:man version: %VERSION%
:man manual: System administration commands
NAME
......@@ -60,48 +60,48 @@ The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'.
The 'ARGUMENTS' to pass to the module.
*-D*, *--debug*
*-D*, *--debug*::
Print any messages the remote module sends to standard error to the console
*-k*, *--ask-pass*
*-k*, *--ask-pass*::
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
*-K*, *--ask-sudo-pass*
*-K*, *--ask-sudo-pass*::
Prompt for the password to use with --sudo, if any
*-o*, *--one-line*
*-o*, *--one-line*::
Try to output everything on one line.
*-s*, *--sudo*
*-s*, *--sudo*::
Run the command as the user given by -u and sudo to root.
*-t* 'DIRECTORY', *--tree=*'DIRECTORY'
*-t* 'DIRECTORY', *--tree=*'DIRECTORY'::
Save contents in this output 'DIRECTORY', with the results saved in a
file named after each host.
*-T* 'SECONDS', *--timeout=*'SECONDS'
*-T* 'SECONDS', *--timeout=*'SECONDS'::
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
*-B* 'NUM', *--background=*'NUM'
*-B* 'NUM', *--background=*'NUM'::
Run commands in the background, killing the task after 'NUM' seconds.
*-P* 'NUM', *--poll=*'NUM'
*-P* 'NUM', *--poll=*'NUM'::
Poll a background job every 'NUM' seconds. Requires *-B*.
*-u* 'USERNAME', *--remote-user=*'USERNAME'
*-u* 'USERNAME', *--remote-user=*'USERNAME'::
Use this remote 'USERNAME' instead of root.
*-c* 'CONNECTION', *--connection=*'CONNECTION'
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
Connection type to use. Possible options are 'paramiko' (SSH) and 'local'.
Local is mostly useful for crontab or kickstarts.
......
---
# This is a demo of how to manage the selinux context using the file module
- hosts: test
user: root
tasks:
- name: Change setype of /etc/exports to non-default value
action: file path=/etc/exports setype=etc_t
- name: Change seuser of /etc/exports to non-default value
action: file path=/etc/exports seuser=unconfined_u
- name: Set selinux context back to default value
action: file path=/etc/exports context=default
- name: Create empty file
action: command /bin/touch /tmp/foo
- name: Change setype of /tmp/foo
action: file path=/tmp/foo setype=default_t
- name: Try to set secontext to default, but this will fail
because of the lack of a default in the policy
action: file path=/tmp/foo context=default
......@@ -6,6 +6,3 @@ To use it from the root of a checkout:
$ . ./hacking/env-setup
Note the space between the '.' and the './'
Man pages will not load until you run 'make docs' from the root of the
checkout.
......@@ -4,14 +4,17 @@
PREFIX_PYTHONPATH="$PWD/lib"
PREFIX_PATH="$PWD/bin"
PREFIX_MANPATH="$PWD/docs/man"
export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH
export PATH=$PREFIX_PATH:$PATH
export ANSIBLE_LIBRARY="$PWD/library"
export MANPATH=$PREFIX_MANPATH:$MANPATH
echo "PATH=$PATH"
echo "PYTHONPATH=$PYTHONPATH"
echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY"
echo "MANPATH=$MANPATH"
echo "reminder: specify your host file with -i"
echo "done."
echo "Reminder: specify your host file with -i"
echo "Done."
......@@ -30,7 +30,7 @@ import sys
import os
import subprocess
import traceback
import ansible.utils
from ansible import utils
try:
import json
......@@ -70,7 +70,7 @@ try:
print "***********************************"
print "RAW OUTPUT"
print out
results = ansible.utils.parse_json(out)
results = utils.parse_json(out)
except:
print "***********************************"
......@@ -82,7 +82,7 @@ except:
print "***********************************"
print "PARSED OUTPUT"
print results
print utils.bigjson(results)
sys.exit(0)
......
......@@ -151,7 +151,7 @@ class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
print "failed: [%s] => %s => %s\n" % (host, invocation, utils.smjson(results))
def on_ok(self, host, host_result):
invocation = host_result.get('invocation',None)
invocation = host_result.get('invocation','')
if invocation.startswith('async_status'):
pass
elif not invocation or invocation.startswith('setup '):
......
......@@ -45,12 +45,12 @@ class Connection(object):
self.runner = runner
self.transport = transport
def connect(self, host):
def connect(self, host, port=None):
conn = None
if self.transport == 'local' and self._LOCALHOSTRE.search(host):
conn = LocalConnection(self.runner, host)
conn = LocalConnection(self.runner, host, None)
elif self.transport == 'paramiko':
conn = ParamikoConnection(self.runner, host)
conn = ParamikoConnection(self.runner, host, port)
if conn is None:
raise Exception("unsupported connection type")
return conn.connect()
......@@ -64,10 +64,13 @@ class Connection(object):
class ParamikoConnection(object):
''' SSH based connections with Paramiko '''
def __init__(self, runner, host):
def __init__(self, runner, host, port=None):
self.ssh = None
self.runner = runner
self.host = host
self.port = port
if port is None:
self.port = self.runner.remote_port
def _get_conn(self):
ssh = paramiko.SSHClient()
......@@ -75,9 +78,13 @@ class ParamikoConnection(object):
try:
ssh.connect(
self.host, username=self.runner.remote_user,
allow_agent=True, look_for_keys=True, password=self.runner.remote_pass,
timeout=self.runner.timeout, port=self.runner.remote_port
self.host,
username=self.runner.remote_user,
allow_agent=True,
look_for_keys=True,
password=self.runner.remote_pass,
timeout=self.runner.timeout,
port=self.port
)
except Exception, e:
if str(e).find("PID check failed") != -1:
......@@ -183,7 +190,7 @@ class LocalConnection(object):
self.runner = runner
self.host = host
def connect(self):
def connect(self, port=None):
''' connect to the local host; nothing to do here '''
return self
......
......@@ -33,7 +33,6 @@ except ImportError:
from ansible import errors
import ansible.constants as C
###############################################################
# UTILITY FUNCTIONS FOR COMMAND LINE TOOLS
###############################################################
......@@ -239,14 +238,16 @@ def varReplace(raw, vars):
return ''.join(done)
def template(text, vars):
def template(text, vars, setup_cache):
''' run a text buffer through the templating engine '''
vars = vars.copy()
text = varReplace(str(text), vars)
vars['hostvars'] = setup_cache
template = jinja2.Template(text)
return template.render(vars)
def double_template(text, vars):
return template(template(text, vars), vars)
def double_template(text, vars, setup_cache):
return template(template(text, vars, setup_cache), vars, setup_cache)
def template_from_file(path, vars):
''' run a file through the templating engine '''
......@@ -279,7 +280,7 @@ class SortedOptParser(optparse.OptionParser):
self.option_list.sort(key=methodcaller('get_opt_string'))
return optparse.OptionParser.format_help(self, formatter=None)
def base_parser(constants=C, usage="", output_opts=False, port_opts=False, runas_opts=False, async_opts=False, connect_opts=False):
def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, async_opts=False, connect_opts=False):
''' create an options parser for any ansible script '''
parser = SortedOptParser(usage)
......@@ -301,11 +302,6 @@ def base_parser(constants=C, usage="", output_opts=False, port_opts=False, runas
dest='timeout',
help="override the SSH timeout in seconds (default=%s)" % constants.DEFAULT_TIMEOUT)
if port_opts:
parser.add_option('-p', '--port', default=constants.DEFAULT_REMOTE_PORT, type='int',
dest='remote_port',
help="override the remote ssh port (default=%s)" % constants.DEFAULT_REMOTE_PORT)
if output_opts:
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
help='condense output')
......
......@@ -42,7 +42,7 @@ def fail_json(**kwargs):
exit_json(rc=1, **kwargs)
try:
import apt
import apt, apt_pkg
except ImportError:
fail_json(msg="could not import apt, please install the python-apt package on this host")
......@@ -63,17 +63,30 @@ def run_apt(command):
rc = cmd.returncode
return rc, out, err
def package_status(pkgspec, cache):
def package_split(pkgspec):
parts = pkgspec.split('=')
if len(parts) > 1:
return parts[0], parts[1]
else:
return parts[0], None
def package_status(pkgname, version, cache):
try:
pkg = cache[pkgspec]
except:
fail_json(msg="No package matching '%s' is available" % pkgspec)
return (pkg.is_installed, pkg.is_upgradable)
pkg = cache[pkgname]
except KeyError:
fail_json(msg="No package matching '%s' is available" % pkgname)
if version:
return pkg.is_installed and pkg.installed.version == version, False
else:
return pkg.is_installed, pkg.is_upgradable
def install(pkgspec, cache, upgrade=False):
(installed, upgradable) = package_status(pkgspec, cache)
if (not installed) or (upgrade and upgradable):
def install(pkgspec, cache, upgrade=False, default_release=None):
name, version = package_split(pkgspec)
installed, upgradable = package_status(name, version, cache)
if not installed or (upgrade and upgradable):
cmd = "%s -q -y install '%s'" % (APT, pkgspec)
if default_release:
cmd += " -t '%s'" % (default_release,)
rc, out, err = run_apt(cmd)
if rc:
fail_json(msg="'apt-get install %s' failed: %s" % (pkgspec, err))
......@@ -82,15 +95,16 @@ def install(pkgspec, cache, upgrade=False):
return False
def remove(pkgspec, cache, purge=False):
(installed, upgradable) = package_status(pkgspec, cache)
name, version = package_split(pkgspec)
installed, upgradable = package_status(name, version, cache)
if not installed:
return False
else:
purge = '--purge' if purge else ''
cmd = "%s -q -y %s remove '%s'" % (APT, purge, pkgspec)
cmd = "%s -q -y %s remove '%s'" % (APT, purge, name)
rc, out, err = run_apt(cmd)
if rc:
fail_json(msg="'apt-get remove %s' failed: %s" % (pkgspec, err))
fail_json(msg="'apt-get remove %s' failed: %s" % (name, err))
return True
......@@ -109,13 +123,14 @@ if not len(items):
params = {}
for x in items:
(k, v) = x.split("=")
(k, v) = x.split("=", 1)
params[k] = v
state = params.get('state','installed')
package = params.get('pkg', params.get('package', params.get('name', None)))
update_cache = params.get('update-cache', 'no')
purge = params.get('purge', 'no')
state = params.get('state', 'installed')
package = params.get('pkg', params.get('package', params.get('name', None)))
update_cache = params.get('update-cache', 'no')
purge = params.get('purge', 'no')
default_release = params.get('default-release', None)
if state not in ['installed', 'latest', 'removed']:
fail_json(msg='invalid state')
......@@ -130,6 +145,10 @@ if package is None and update_cache != 'yes':
fail_json(msg='pkg=name and/or update-cache=yes is required')
cache = apt.Cache()
if default_release:
apt_pkg.config['APT::Default-Release'] = default_release
# reopen cache w/ modified config
cache.open()
if update_cache == 'yes':
cache.update()
......@@ -137,10 +156,16 @@ if update_cache == 'yes':
if package == None:
exit_json(changed=False)
if package.count('=') > 1:
fail_json(msg='invalid package spec')
if state == 'latest':
changed = install(package, cache, upgrade=True)
if '=' in package:
fail_json(msg='version number inconsistent with state=latest')
changed = install(package, cache, upgrade=True,
default_release=default_release)
elif state == 'installed':
changed = install(package, cache)
changed = install(package, cache, default_release=default_release)
elif state == 'removed':
changed = remove(package, cache, purge == 'yes')
......
......@@ -42,7 +42,10 @@ for x in items:
src = params['src']
dest = params['dest']
if src:
src = os.path.expanduser(src)
if dest:
dest = os.path.expanduser(dest)
# raise an error if there is no src file
if not os.path.exists(src):
......
......@@ -72,6 +72,21 @@ def add_path_info(kwargs):
kwargs['state'] = 'absent'
return kwargs
# If selinux fails to find a default, return an array of None
def selinux_default_context(path, mode=0):
context = [None, None, None, None]
if not HAVE_SELINUX:
return context
try:
ret = selinux.matchpathcon(path, mode)
except OSError:
return context
if ret[0] == -1:
return context
context = ret[1].split(':')
debug("got default secontext=%s" % ret[1])
return context
# ===========================================
argfile = sys.argv[1]
......@@ -89,7 +104,11 @@ for x in items:
state = params.get('state','file')
path = params.get('path', params.get('dest', params.get('name', None)))
if path:
path = os.path.expanduser(path)
src = params.get('src', None)
if src:
src = os.path.expanduser(src)
dest = params.get('dest', None)
mode = params.get('mode', None)
owner = params.get('owner', None)
......@@ -102,8 +121,16 @@ recurse = params.get('recurse', 'false')
seuser = params.get('seuser', None)
serole = params.get('serole', None)
setype = params.get('setype', None)
serange = params.get('serange', 's0')
secontext = [seuser, serole, setype, serange]
selevel = params.get('serange', 's0')
context = params.get('context', None)
secontext = [seuser, serole, setype, selevel]
if context is not None:
if context != 'default':
fail_json(msg='invalid context: %s' % context)
if seuser is not None or serole is not None or setype is not None:
fail_json(msg='cannot define context=default and seuser, serole or setype')
secontext = selinux_default_context(path)
if state not in [ 'file', 'directory', 'link', 'absent']:
fail_json(msg='invalid state: %s' % state)
......@@ -144,34 +171,14 @@ def selinux_context(path):
debug("got current secontext=%s" % ret[1])
return context
# If selinux fails to find a default, return an array of None
def selinux_default_context(path, mode=0):
context = [None, None, None, None]
print >>sys.stderr, path
if not HAVE_SELINUX:
return context
try:
ret = selinux.matchpathcon(path, mode)
except OSError:
return context
if ret[0] == -1:
return context
context = ret[1].split(':')
debug("got default secontext=%s" % ret[1])
return context
def set_context_if_different(path, context, changed):
if not HAVE_SELINUX:
return changed
cur_context = selinux_context(path)
new_context = selinux_default_context(path)
new_context = list(cur_context)
for i in range(len(context)):
if context[i] is not None and context[i] != cur_context[i]:
debug('new context was %s' % new_context[i])
new_context[i] = context[i]
debug('new context is %s' % new_context[i])
elif new_context[i] is None:
new_context[i] = cur_context[i]
debug("current secontext is %s" % ':'.join(cur_context))
debug("new secontext is %s" % ':'.join(new_context))
if cur_context != new_context:
......
#!/usr/bin/python
# (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 sys
import os
import shlex
import base64
try:
import json
except ImportError:
import simplejson as json
# ===========================================
# convert arguments of form a=b c=d
# to a dictionary
if len(sys.argv) == 1:
sys.exit(1)
argfile = sys.argv[1]
if not os.path.exists(argfile):
sys.exit(1)
items = shlex.split(open(argfile, 'r').read())
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
source = os.path.expanduser(params['src'])
# ==========================================
# raise an error if there is no template metadata
if not os.path.exists(source):
print json.dumps(dict(
failed = 1,
msg = "file not found: %s" % source
))
sys.exit(1)
if not os.access(source, os.R_OK):
print json.dumps(dict(
failed = 1,
msg = "file is not readable: %s" % source
))
sys.exit(1)
# ==========================================
data = file(source).read()
data = base64.b64encode(data)
print json.dumps(dict(content=data, encoding='base64'))
sys.exit(0)
......@@ -17,119 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import jinja2
import shlex
try:
import json
except ImportError:
import simplejson as json
environment = jinja2.Environment()
# ===========================================
# convert arguments of form a=b c=d
# to a dictionary
# FIXME: make more idiomatic
if len(sys.argv) == 1:
sys.exit(1)
argfile = sys.argv[1]
if not os.path.exists(argfile):
sys.exit(1)
items = shlex.split(open(argfile, 'r').read())
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
source = params['src']
dest = params['dest']
metadata = params.get('metadata', '/etc/ansible/setup')
module_vars = params.get('vars')
# raise an error if there is no template metadata
if not os.path.exists(metadata):
print json.dumps({
"failed" : 1,
"msg" : "Missing %s, did you run the setup module yet?" % metadata
})
sys.exit(1)
# raise an error if we can't parse the template metadata
#data = {}
try:
f = open(metadata)
data = json.loads(f.read())
f.close()
except:
print json.dumps({
"failed" : 1,
"msg" : "Failed to parse/load %s, rerun the setup module?" % metadata
})
sys.exit(1)
if module_vars:
try:
f = open(module_vars)
vars = json.loads(f.read())
data.update(vars)
f.close()
except:
print json.dumps({
"failed" : 1,
"msg" : "Failed to parse/load %s." % module_vars
})
sys.exit(1)
if not os.path.exists(source):
print json.dumps({
"failed" : 1,
"msg" : "Source template could not be read: %s" % source
})
sys.exit(1)
source = file(source).read()
if os.path.isdir(dest):
print json.dumps({
"failed" : 1,
"msg" : "Destination is a directory"
})
sys.exit(1)
# record md5sum of original source file so we can report if it changed
changed = False
md5sum = None
if os.path.exists(dest):
md5sum = os.popen("md5sum %s" % dest).read().split()[0]
try:
# call Jinja2 here and save the new template file
template = environment.from_string(source)
data_out = template.render(data)
except jinja2.TemplateError, e:
print json.dumps({
"failed": True,
"msg" : e.message
})
sys.exit(1)
f = open(dest, "w+")
f.write(data_out)
f.close()
# record m5sum and return success and whether things have changed
md5sum2 = os.popen("md5sum %s" % dest).read().split()[0]
if md5sum != md5sum2:
changed = True
# mission accomplished
print json.dumps({
"md5sum" : md5sum2,
"changed" : changed
})
# hey the Ansible template module isn't really a remote transferred
# module. All the magic happens in Runner.py making use of the
# copy module, and if not running from a playbook, also the 'slurp'
# module.
......@@ -10,8 +10,7 @@ This software may be freely redistributed under the terms of the GNU
general public license.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
VIRT_FAILED = 1
......
#Maintainer: Michel Blanc <mblanc@erasme.org>
pkgname=ansible-git
pkgver=20120419
pkgrel=1
pkgdesc="A radically simple deployment, model-driven configuration management, and command execution framework"
arch=('any')
url="https://github.com/ansible/ansible"
license=('GPL3')
depends=('python2' 'python2-yaml' 'python-paramiko>=1.7.7' 'python2-jinja' 'python-simplejson')
makedepends=('git' 'asciidoc' 'fakeroot')
_gitroot="https://github.com/ansible/ansible"
_gitname="ansible"
build() {
cd "$srcdir"
msg "Connecting to GIT server...."
if [ -d $_gitname ] ; then
cd $_gitname && git pull origin
msg "The local files are updated."
else
git clone $_gitroot $_gitname
fi
msg "GIT checkout done or server timeout"
cd "$srcdir/$_gitname"
make
}
package() {
cd "$srcdir/$_gitname"
mkdir -p ${pkgdir}/usr/share/ansible
cp ./library/* ${pkgdir}/usr/share/ansible/
python setup.py install -O1 --root=${pkgdir}
install -D docs/man/man1/ansible.1 ${pkgdir}/usr/share/man/man1/ansible.1
install -D docs/man/man1/ansible-playbook.1 ${pkgdir}/usr/share/man/man1/ansible-playbook.1
gzip -9 ${pkgdir}/usr/share/man/man1/ansible.1
gzip -9 ${pkgdir}/usr/share/man/man1/ansible-playbook.1
}
I have added a debian folder for use in building a .deb file for ansible. From the ansible directory you can run the following command to construct a debian package of ansible.
~/ansible$ dpkg-buildpackage -us -uc -rfakeroot
The debian package files will be placed in the ../ directory and can be installed with the following command:
~/$ sudo dpkg -i .deb
Dpkg -i doesn't resolve dependencies, so if the previous command fails because of dependencies, you will need to run the following to install the dependencies (if needed) and then re-run the dpkg -i command to install the package:
$ sudo apt-get -f install
--Henry Graham
etc/ansible
usr/lib/python2.7/site-packages
usr/share/ansible
examples/hosts etc/ansible
library/* usr/share/ansible
docs/man/man1/ansible.1 usr/share/man/man1
docs/man/man1/ansible-playbook.1 usr/share/man/man1
bin/* usr/bin
ansible (0.0.2) debian; urgency=low
* Initial Release
-- Henry Graham (hzgraham) <Henry.Graham@mail.wvu.edu> Tue, 17 Apr 2012 17:17:01 -0400
Source: ansible
Section: admin
Priority: optional
Maintainer: Henry Graham (hzgraham) <Henry.Graham@mail.wvu.edu>
Build-Depends: cdbs, debhelper (>= 5.0.0)
Standards-Version: 3.9.1
Homepage: http://ansible.github.com/
Package: ansible
Architecture: all
Depends: python, python-support (>= 0.90), python-jinja2, python-yaml, python-paramiko
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.
This package was debianized by Henry Graham (hzgraham) <Henry.Graham@mail.wvu.edu> on
Tue, 17 Apr 2012 12:19:47 -0400.
It was downloaded from https://github.com/ansible/ansible.git
Copyright: Henry Graham (hzgraham) <Henry.Graham@mail.wvu.edu>
License:
This package 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; version 2 dated June, 1991.
This package 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 this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
USA.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL'.
#!/usr/bin/make -f
# -- makefile --
include /usr/share/cdbs/1/rules/debhelper.mk
DEB_PYTHON_SYSTEM = pysupport
include /usr/share/cdbs/1/class/python-distutils.mk
Gentoo ebuilds are available here:
https://github.com/uu/ubuilds
%if 0%{?rhel} <= 5
%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
%endif
Name: ansible
Release: 1%{?dist}
Summary: Minimal SSH command and control
Version: 0.0.2
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Group: Development/Libraries
License: GPLv3
Prefix: %{_prefix}
Source0: https://github.com/downloads/ansible/ansible/%{name}-%{version}.tar.gz
Url: http://ansible.github.com
BuildArch: noarch
BuildRequires: asciidoc
BuildRequires: python-devel
BuildRequires: python2-devel
Requires: PyYAML
Requires: python-paramiko
Requires: python-jinja2
%description
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.
Ansible is a radically simple model-driven configuration management,
multi-node deployment, and remote task execution system. Ansible works
over SSH and does not require any software or daemons to be installed
on remote nodes. Extension modules can be written in any language and
are transferred to managed machines automatically.
%prep
%setup -q -n %{name}-%{version}
%setup -q
%build
python setup.py build
%{__python} setup.py build
%install
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
%{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/etc/ansible/
cp examples/hosts $RPM_BUILD_ROOT/etc/ansible/
mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/
mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/
cp -v docs/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1/
mkdir -p $RPM_BUILD_ROOT/%{_datadir}/ansible
cp -v library/* $RPM_BUILD_ROOT/%{_datadir}/ansible/
......@@ -43,14 +48,13 @@ cp -v library/* $RPM_BUILD_ROOT/%{_datadir}/ansible/
rm -rf $RPM_BUILD_ROOT
%files
%doc README.md PKG-INFO
%defattr(-,root,root)
%{_mandir}/man1/*.gz
%{python_sitelib}/*
%{python_sitelib}/ansible*
%{_bindir}/ansible*
%{_datadir}/ansible/*
%config(noreplace) /etc/ansible/hosts
%config(noreplace) %{_sysconfdir}/ansible/
%{_datadir}/ansible
%config(noreplace) %{_sysconfdir}/ansible
%doc README.md PKG-INFO
%doc %{_mandir}/man1/ansible*
%changelog
......
import os
import unittest
from ansible.inventory import Inventory
from ansible.runner import Runner
class TestInventory(unittest.TestCase):
def setUp(self):
self.cwd = os.getcwd()
self.test_dir = os.path.join(self.cwd, 'test')
self.inventory_file = os.path.join(self.test_dir, 'simple_hosts')
self.inventory_script = os.path.join(self.test_dir, 'inventory_api.py')
self.inventory_yaml = os.path.join(self.test_dir, 'yaml_hosts')
os.chmod(self.inventory_script, 0755)
def tearDown(self):
os.chmod(self.inventory_script, 0644)
### Simple inventory format tests
def simple_inventory(self):
return Inventory( self.inventory_file )
def script_inventory(self):
return Inventory( self.inventory_script )
def yaml_inventory(self):
return Inventory( self.inventory_yaml )
def test_simple(self):
inventory = self.simple_inventory()
hosts = inventory.list_hosts()
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_simple_all(self):
inventory = self.simple_inventory()
hosts = inventory.list_hosts('all')
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_simple_norse(self):
inventory = self.simple_inventory()
hosts = inventory.list_hosts("norse")
expected_hosts=['thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_simple_ungrouped(self):
inventory = self.simple_inventory()
hosts = inventory.list_hosts("ungrouped")
expected_hosts=['jupiter', 'saturn']
assert hosts == expected_hosts
def test_simple_combined(self):
inventory = self.simple_inventory()
hosts = inventory.list_hosts("norse:greek")
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_simple_restrict(self):
inventory = self.simple_inventory()
restricted_hosts = ['hera', 'poseidon', 'thor']
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
inventory.restrict_to(restricted_hosts)
hosts = inventory.list_hosts("norse:greek")
assert hosts == restricted_hosts
inventory.lift_restriction()
hosts = inventory.list_hosts("norse:greek")
assert hosts == expected_hosts
def test_simple_vars(self):
inventory = self.simple_inventory()
vars = inventory.get_variables('thor')
assert vars == {}
def test_simple_port(self):
inventory = self.simple_inventory()
vars = inventory.get_variables('hera')
assert vars == {'ansible_ssh_port': 3000}
### Inventory API tests
def test_script(self):
inventory = self.script_inventory()
hosts = inventory.list_hosts()
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
print "Expected: %s"%(expected_hosts)
print "Got : %s"%(hosts)
assert sorted(hosts) == sorted(expected_hosts)
def test_script_all(self):
inventory = self.script_inventory()
hosts = inventory.list_hosts('all')
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert sorted(hosts) == sorted(expected_hosts)
def test_script_norse(self):
inventory = self.script_inventory()
hosts = inventory.list_hosts("norse")
expected_hosts=['thor', 'odin', 'loki']
assert sorted(hosts) == sorted(expected_hosts)
def test_script_combined(self):
inventory = self.script_inventory()
hosts = inventory.list_hosts("norse:greek")
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert sorted(hosts) == sorted(expected_hosts)
def test_script_restrict(self):
inventory = self.script_inventory()
restricted_hosts = ['hera', 'poseidon', 'thor']
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
inventory.restrict_to(restricted_hosts)
hosts = inventory.list_hosts("norse:greek")
assert sorted(hosts) == sorted(restricted_hosts)
inventory.lift_restriction()
hosts = inventory.list_hosts("norse:greek")
assert sorted(hosts) == sorted(expected_hosts)
def test_script_vars(self):
inventory = self.script_inventory()
vars = inventory.get_variables('thor')
assert vars == {"hammer":True}
### Tests for yaml inventory file
def test_yaml(self):
inventory = self.yaml_inventory()
hosts = inventory.list_hosts()
print hosts
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_yaml_all(self):
inventory = self.yaml_inventory()
hosts = inventory.list_hosts('all')
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_yaml_norse(self):
inventory = self.yaml_inventory()
hosts = inventory.list_hosts("norse")
expected_hosts=['thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_simple_ungrouped(self):
inventory = self.yaml_inventory()
hosts = inventory.list_hosts("ungrouped")
expected_hosts=['jupiter']
assert hosts == expected_hosts
def test_yaml_combined(self):
inventory = self.yaml_inventory()
hosts = inventory.list_hosts("norse:greek")
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
def test_yaml_restrict(self):
inventory = self.yaml_inventory()
restricted_hosts = ['hera', 'poseidon', 'thor']
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
inventory.restrict_to(restricted_hosts)
hosts = inventory.list_hosts("norse:greek")
assert hosts == restricted_hosts
inventory.lift_restriction()
hosts = inventory.list_hosts("norse:greek")
assert hosts == expected_hosts
def test_yaml_vars(self):
inventory = self.yaml_inventory()
vars = inventory.get_variables('thor')
assert vars == {"hammer":True}
def test_yaml_change_vars(self):
inventory = self.yaml_inventory()
vars = inventory.get_variables('thor')
vars["hammer"] = False
vars = inventory.get_variables('thor')
assert vars == {"hammer":True}
def test_yaml_host_vars(self):
inventory = self.yaml_inventory()
vars = inventory.get_variables('saturn')
assert vars == {"moon":"titan", "moon2":"enceladus"}
def test_yaml_port(self):
inventory = self.yaml_inventory()
vars = inventory.get_variables('hera')
assert vars == {'ansible_ssh_port': 3000, 'ntp_server': 'olympus.example.com'}
### Test Runner class method
def test_class_method(self):
hosts, groups = Runner.parse_hosts(self.inventory_file)
expected_hosts = ['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
assert hosts == expected_hosts
expected_groups= {
'ungrouped': ['jupiter', 'saturn'],
'greek': ['zeus', 'hera', 'poseidon'],
'norse': ['thor', 'odin', 'loki']
}
assert groups == expected_groups
def test_class_override(self):
override_hosts = ['thor', 'odin']
hosts, groups = Runner.parse_hosts(self.inventory_file, override_hosts)
assert hosts == override_hosts
assert groups == { 'ungrouped': override_hosts }
......@@ -136,12 +136,13 @@ class TestPlaybook(unittest.TestCase):
timeout = 5,
remote_user = self.user,
remote_pass = None,
verbose = False,
stats = ans_callbacks.AggregateStats(),
callbacks = self.test_callbacks,
runner_callbacks = self.test_callbacks
)
return self.playbook.run()
result = self.playbook.run()
print utils.bigjson(dict(events=EVENTS))
return result
def test_one(self):
pb = os.path.join(self.test_dir, 'playbook1.yml')
......
......@@ -14,6 +14,15 @@ try:
except:
import simplejson as json
from nose.plugins.skip import SkipTest
def get_binary(name):
for directory in os.environ["PATH"].split(os.pathsep):
path = os.path.join(directory, name)
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
return None
class TestRunner(unittest.TestCase):
def setUp(self):
......@@ -29,7 +38,6 @@ class TestRunner(unittest.TestCase):
forks=1,
background=0,
pattern='all',
verbose=True,
)
self.cwd = os.getcwd()
self.test_dir = os.path.join(self.cwd, 'test')
......@@ -74,6 +82,8 @@ class TestRunner(unittest.TestCase):
assert "ping" in result
def test_facter(self):
if not get_binary("facter"):
raise SkipTest
result = self._run('facter',[])
assert "hostname" in result
......@@ -168,12 +178,13 @@ class TestRunner(unittest.TestCase):
# almost every time so changed is always true, this just tests that
# rewriting the file is ok
result = self._run('setup', [ "metadata=%s" % output, "a=2", "b=3", "c=4" ])
print "RAW RESULT=%s" % result
assert 'md5sum' in result
def test_async(self):
# test async launch and job status
# of any particular module
result = self._run('command', [ "/bin/sleep", "3" ], background=20)
result = self._run('command', [ get_binary("sleep"), "3" ], background=20)
assert 'ansible_job_id' in result
assert 'started' in result
jid = result['ansible_job_id']
......@@ -191,13 +202,14 @@ class TestRunner(unittest.TestCase):
def test_fetch(self):
input = self._get_test_file('sample.j2')
output = self._get_stage_file('127.0.0.2/sample.j2')
output = os.path.join(self.stage_dir, '127.0.0.2', input)
result = self._run('fetch', [ "src=%s" % input, "dest=%s" % self.stage_dir ])
print "output file=%s" % output
assert os.path.exists(output)
assert open(input).read() == open(output).read()
def test_yum(self):
if not get_binary("yum"):
raise SkipTest
result = self._run('yum', [ "list=repos" ])
assert 'failed' not in result
......
#!/usr/bin/env python
import json
import sys
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-l', '--list', default=False, dest="list_hosts", action="store_true")
parser.add_option('-H', '--host', default=None, dest="host")
parser.add_option('-e', '--extra-vars', default=None, dest="extra")
options, args = parser.parse_args()
systems = {
"ungouped": [ "jupiter", "saturn" ],
"greek": [ "zeus", "hera", "poseidon" ],
"norse": [ "thor", "odin", "loki" ]
}
variables = {
"thor": {
"hammer": True
}
}
if options.list_hosts == True:
print json.dumps(systems)
sys.exit(0)
if options.host is not None:
if options.extra:
k,v = options.extra.split("=")
variables[options.host][k] = v
print json.dumps(variables[options.host])
sys.exit(0)
parser.print_help()
sys.exit(1)
\ No newline at end of file
jupiter
saturn
[greek]
zeus
hera:3000
poseidon
[norse]
thor
odin
loki
---
- jupiter
- host: saturn
vars:
moon: titan
moon2: enceladus
- zeus
- group: greek
hosts:
- zeus
- hera
- poseidon
vars:
- ansible_ssh_port: 3000
- ntp_server: olympus.example.com
- group: norse
hosts:
- host: thor
vars:
- hammer: True
- odin
- loki
- group: multiple
hosts:
- saturn
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment