diff --git a/.gitignore b/.gitignore index f651d7deb742bb..db77f298fa6f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # build products... *.py[co] build +AUTHORS.TXT # Emacs backup files... *~ .\#* @@ -13,6 +14,8 @@ rpm-build .pydevproject # PyCharm stuff... .idea +#IntelliJ IDEA stuff.. +*.iml # Mac OS X stuff... .DS_Store # manpage build stuff... @@ -21,10 +24,15 @@ docs/man/man3/* *.sublime-project *.sublime-workspace # docsite stuff... -docsite/rst/modules -docsite/*.html -docsite/_static/*.gif -docsite/_static/*.png -docsite/_static/websupport.js +docsite/latest/rst/modules +docsite/latest/*.html +docsite/latest/_static/*.gif +docsite/latest/_static/*.png +docsite/latest/_static/websupport.js +docsite/latest/searchindex.js +docsite/latest/htmlout # deb building stuff... debian/ +# Vim swap files +*.swp +*.swo diff --git a/CHANGELOG.md b/CHANGELOG.md index cdddb518c7099b..adfb51cf33926a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,357 @@ Ansible Changes By Release ========================== -1.1 "Mean Street" -- Release pending +1.3 "Top of the World" - Release pending! + +Major new features: + +* new /etc/ansible/facts.d allows JSON or INI style facts to be provided from the remote node, and supports executable fact programs in this dir. Files must end in *.fact. +* ability to make undefined template variables raise errors, see ansible.cfg + +New modules: + +* notifications: datadog_event -- send data to datadog +* cloud: digital_ocean -- module for digital ocean provisioning, also includes inventory module +* cloud: rds -- Amazon relational database service +* cloud: linode -- also included, an inventory module +* net_infrastructure: arista_ +* system: stat -- reports on stat(istics) of remote files, for use with 'register' +* htpasswd -- manipulate htpasswd files + +Misc changes: + +* Added OpenRC support (Gentoo) to the service module +* ansible_ssh_user value is available to templates +* added placement_group parameter to ec2 module +* new sha256 parameter to get_url module for validation +* search for mount binaries in system path and sbin vs assuming path +* allowed inventory file to be read from a pipe +* added Solaris distribution facts +* fixed bug along error path in quantum_network module +* user password update mode is controllable in user module now (creation vs every time) +* added check mode support to the OpenBSD package module +* Fix for MySQL 5.6 compatibility +* HP UX virtualization facts +* fixed some executable bits in git +* made rhn_register module compatible with EL5 +* fix for setup module epoch time in Solaris +* sudo_user is now expanded later allowing to be set at inventory scope +* mondodb_user module change to also support MongoDB 2.2 +* new state=hard option to the file module for hardlinks vs softlinks +* fixes to apt module purging option behavior +* fixes for device facts with multiple PCI domains +* added "with_inventory_hostnames" lookup plugin, which can take a pattern and loop over hostnames matching the pattern and is great for use with delegate_to and so on. +* ec2 module supports adding to multiple security groups +* cloudformation module fix down the error path, removed 'wait_for' parameter +* added --only-if-changed to ansible-pull, which runs only if the repo has changes (not default) +* added 'mandatory', a Jinja2 filter that checks if a variable is defined: {{ foo|mandatory }} +* added support for multiple size formats to the lvol module +* timing reporting on wait_for module now includes the delay time +* IRC module can now send a server password +* "~" now expanded on each component of configured plugin paths +* fix for easy_install module when dealing with virtualenv +* rackspace module now explicitly indicates rackspace vs vanilla openstack +* add_host module does not report changed=True any longer +* explanatory error message when using fireball with sudo message has been improved +* git module now automatically pulls down git submodules +* negated patterns don't require "all:!foo", you can just say "!foo" now to select all not foos. +* fix for Debian services always reporting changed when toggling enablement bit +* roles files now tolerate files named 'main.yaml' and 'main' in addition to main.yaml +* some help cleanup to command line flags on scripts +* force option reinstated for file module, can create symlinks to non-existant files, etc +* added termination support to ec2 module +* --ask-sudo-pass or --sudo-user does not enable all options to use sudo in ansible-playbook + +1.2.2 "Hear About It Later" (reprise) -- July 4, 2013 + +* Added a configuration file option [paramiko_connection] record_host_keys which allows the code that paramiko uses +to update known_hosts to be disabled. This is done because paramiko can be very slow at doing this if you have a +large number of hosts and some folks may not want this behavior. This can be toggled independently of host key checking +and does not affect the ssh transport plugin. Use of the ssh transport plugin is preferred if you have ControlPersist +capability, and Ansible by default in 1.2.1 and later will autodetect. + +1.2.1 "Hear About It Later" -- July 4, 2013 + +* Connection default is now "smart", which discovers if the system openssh can support ControlPersist, and uses + it if so, if not falls back to paramiko. +* Host key checking is on by default. Disable it if you like by adding host_key_checking=False in the [default] + section of /etc/ansible/ansible.cfg or ~/ansible.cfg or by exporting ANSIBLE_HOST_KEY_CHECKING=False +* Paramiko now records host keys it was in contact with host key checking is on. It is somewhat sluggish when doing this, + so switch to the 'ssh' transport if this concerns you. + +1.2 "Right Now" -- June 10, 2013 + +Core Features: + +* capability to set 'all_errors_fatal: True' in a playbook to force any error to stop execution versus + a whole group or serial block needing to fail + usable, without breaking the ability to override in ansible +* ability to use variables from {{ }} syntax in mainline playbooks, new 'when' conditional, as detailed + in documentation. Can disable old style replacements in ansible.cfg if so desired, but are still active + by default. +* can set ansible_ssh_private_key_file as an inventory variable (similar to ansible_ssh_host, etc) +* 'when' statement can be affixed to task includes to auto-affix the conditional to each task therein +* cosmetic: "*****" banners in ansible-playbook output are now constant width +* --limit can now be given a filename (--limit @filename) to constrain a run to a host list on disk +* failed playbook runs will create a retry file in /var/tmp/ansible usable with --limit +* roles allow easy arrangement of reusable tasks/handlers/files/templates +* pre_tasks and post_tasks allow for separating tasks into blocks where handlers will fire around them automatically +* "meta: flush_handler" task capability added for when you really need to force handlers to run +* new --start-at-task option to ansible playbook allows starting at a specific task name in a long playbook +* added a log file for ansible/ansible-playbook, set 'log_path' in the configuration file or ANSIBLE_LOG_PATH in environment +* debug mode always outputs debug in playbooks, without needing to specify -v +* external inventory script added for Spacewalk / Red Hat Satellite servers +* It is now possible to feed JSON structures to --extra-vars. Pass in a JSON dictionary/hash to feed in complex data. +* group_vars/ and host_vars/ directories can now be kept alongside the playbook as well as inventory (or both!) +* more filters: ability to say {{ foo|success }} and {{ foo|failed }} and when: foo|success and when: foo|failed +* more filters: {{ path|basename }} and {{ path|dirname }} +* lookup plugins now use the basedir of the file they have included from, avoiding needs of ../../../ in places and +increasing the ease at which things can be reorganized. + +Modules added: + +* cloud: rax: module for creating instances in the rackspace cloud (uses pyrax) +* packages: npm: node.js package management +* packages: pkgng: next-gen package manager for FreeBSD +* packages: redhat_subscription: manage Red Hat subscription usage +* packages: rhn_register: basic RHN registration +* packages: zypper (SuSE) +* database: postgresql_priv: manages postgresql priveledges +* networking: bigip_pool: load balancing with F5s +* networking: ec2_elb: add and remove machines from ec2 elastic load balancers +* notification: hipchat: send notification events to hipchat +* notification: flowdock: send messages to flowdock during playbook runs +* notification: campfire: send messages to campfire during playbook runs +* notification: mqtt: send messages to the Mosquitto message bus +* notification: irc: send messages to IRC channels +* notification: filesystem - a wrapper around mkfs +* notification: jabber: send jabber chat messages +* notification: osx_say: make OS X say things out loud +* openstack: keystone_user +* openstack: glance_image +* openstack: nova_compute +* openstack: nova_keypair +* openstack: quantum_floating_ip +* openstack: quantum_floating_ip_associate +* openstack: quantum_network +* openstack: quantum_router +* openstack: quantum_router_gateway +* openstack: quantum_router_interface +* openstack: quantum_subnet +* monitoring: newrelic_deployment: notifies newrelic of new deployments +* monitoring: airbrake_deployment - notify airbrake of new deployments +* monitoring: pingdom +* monitoring: pagerduty +* monitoring: monit +* utility: set_fact: sets a variable, which can be the result of a template evaluation + +Modules removed + +* vagrant -- can't be compatible with both versions at once, just run things though the vagrant provisioner in vagrant core + +Bugfixes and Misc Changes: + +* service module happier if only enabled=yes|no specified and no state +* mysql_db: use --password= instead of -p in dump/import so it doesn't go interactive if no pass set +* when using -c ssh and the ansible user is the current user, don't pass a -o to allow SSH config to be +* overwrite parameter added to the s3 module +* private_ip parameter added to the ec2 module +* $FILE and $PIPE now tolerate unicode +* various plugin loading operations have been made more efficient +* hostname now uses platform.node versus socket.gethostname to be more consistant with Unix 'hostname' +* fix for SELinux operations on Unicode path names +* inventory directory locations now ignore files with .ini extensions, making hybrid inventory easier +* copy module in check-mode now reports back correct changed status when used with force=no +* added avail. zone to ec2 module +* fixes to the hash variable merging logic if so enabled in the main settings file (default is to replace, not merge hashes) +* group_vars and host_vars files can now end in a .yaml or .yml extension, (previously required no extension, still favored) +* ec2vol module improvements +* if the user module is told to generate the ssh key, the key generated is now returned in the results +* misc fixes to the Riak module +* make template module slightly more efficient +* base64encode / decode filters are now available to templates +* libvirt module can now work with multiple different libvirt connecton URIs +* fix for postgresql password escaping +* unicode fix for shlex.split in some cases +* apt module upgrade logic improved +* URI module now can follow redirects +* yum module can now install off http URLs +* sudo password now defaults to ssh password if you ask for both and just hit enter on the second prompt +* validate feature on copy and template module, for example, running visudo prior to copying the file over +* network facts upgraded to return advanced configs (bonding, etc) +* region support added to ec2 module +* riak module gets a wait for ring option +* improved check mode support in the file module +* exception handling added to handle scenario when attempt to log to systemd journal fails +* fix for upstart handling when toggling the enablement and running bits at the same time +* when registering a task with a conditional attached, and the task is skipped by the conditional, +the variable is still registered for the host, with the attribute skipped: True. +* delegate_to tasks can look up ansible_ssh_private_key_file variable from inventory correctly now +* s3 module takes a 'dest' parameter to change the destination for uploads +* apt module gets a cache_valid_time option to avoid redundant cache updates +* ec2 module better understands security groups +* fix for postgresql codec usage +* setup module now tolerant of OpenVZ interfaces +* check mode reporting improved for files and directories +* doc system now reports on module requirements +* group_by module can now also make use of globally scoped variables +* localhost and 127.0.0.1 are now fuzzy matched in inventory (are now more or less interchangeable) +* AIX improvements/fixes for users, groups, facts +* lineinfile now does atomic file replacements +* fix to not pass PasswordAuthentication=no in the config file unneccessarily for SSH connection type +* for for authorized_key on Debian Squeeze +* fixes for apt_repository module reporting changed incorrectly on certain repository types +* allow the virtualenv argument to the pip module to be a pathname +* service pattern argument now correctly read for BSD services +* fetch location can now be controlled more directly via the 'flat' parameter. +* added basename and dirname as Jinja2 filters available to all templates +* pip works better when sudoing from unpriveledged users +* fix for user creation with groups specification reporting 'changed' incorrectly in some cases +* fix for some unicode encoding errors in outputing some data in verbose mode +* improved FreeBSD, NetBSD and Solaris facts +* debug module always outputs data without having to specify -v +* fix for sysctl module creating new keys (must specify checks=none) +* NetBSD and OpenBSD support for the user and groups modules +* Add encrypted password support to password lookup + +1.1 "Mean Street" -- 4/2/2013 + +Core Features + +* added --check option for "dry run" mode +* added --diff option to show how templates or copied files change, or might change +* --list-tasks for the playbook will list the tasks without running them +* able to set the environment by setting "environment:" as a dictionary on any task (go proxy support!) +* added ansible_ssh_user and ansible_ssh_pass for per-host/group username and password +* jinja2 extensions can now be loaded from the config file +* support for complex arguments to modules (within reason) +* can specify ansible_connection=X to define the connection type in inventory variables +* a new chroot connection type +* module common code now has basic type checking (and casting) capability +* module common now supports a 'no_log' attribute to mark a field as not to be syslogged +* inventory can now point to a directory containing multiple scripts/hosts files, if using this, put group_vars/host_vars directories inside this directory +* added configurable crypt scheme for 'vars_prompt' +* password generating lookup plugin -- $PASSWORD(path/to/save/data/in) +* added --step option to ansible-playbook, works just like Linux interactive startup! + +Modules Added: + +* bzr (bazaar version control) +* cloudformation +* django-manage +* gem (ruby gems) +* homebrew +* lvg (logical volume groups) +* lvol (LVM logical volumes) +* macports +* mongodb_user +* netscaler +* okg +* openbsd_pkg +* rabbit_mq_plugin +* rabbit_mq_user +* rabbit_mq_vhost +* rabbit_mq_parameter +* rhn_channel +* s3 -- allows putting file contents in buckets for sharing over s3 +* uri module -- can get/put/post/etc +* vagrant -- launching VMs with vagrant, this is different from existing vagrant plugin +* zfs + +Bugfixes and Misc Changes: + +* stderr shown when commands fail to parse +* uses yaml.safe_dump in filter plugins +* authentication Q&A no longer happens before --syntax-check, but after +* ability to get hostvars data for nodes not in the setup cache yet +* SSH timeout now correctly passed to native SSH connection plugin +* raise an error when multiple when_ statements are provided +* --list-hosts applies host limit selections better +* (internals) template engine specifications to use template_ds everywhere +* better error message when your host file can not be found +* end of line comments now work in the inventory file +* directory destinations now work better with remote md5 code +* lookup plugin macros like $FILE and $ENV now work without returning arrays in variable definitions/playbooks +* uses yaml.safe_load everywhere +* able to add EXAMPLES to documentation via EXAMPLES docstring, rather than just in main documentation YAML +* can set ANSIBLE_COW_SELECTION to pick other cowsay types (including random) +* to_nice_yaml and to_nice_json available as Jinja2 filters that indent and sort +* cowsay able to run out of macports (very important!) +* improved logging for fireball mode +* nicer error message when talking to an older system that needs a JSON module installed +* 'magic' variable 'inventory_basedir' now gives path to inventory file +* 'magic' variable 'vars' works like 'hostvars' but gives global scope variables, useful for debugging in templates mostly +* conditionals can be used on plugins like add_host +* developers: all callbacks now have access to a ".runner" and ".playbook", ".play", and ".task" object (use getattr, they may not always be set!) + +Facts: + +* block device facts for the setup module +* facts for AIX +* fact detection for OS type on Amazon Linux +* device fact gathering stability improvements +* ansible_os_family fact added +* user_id (remote user name) +* a whole series of current time information under the 'datetime' hash +* more OS X facts +* support for detecting Alpine Linux +* added facts for OpenBSD + +Module Changes/Fixes: + +* ansible module common code (and ONLY that) which is mixed in with modules, is now BSD licensed. App remains GPLv3. +* service code works better on platforms that mix upstart, systemd, and system-v +* service enablement idempotence fixes for systemd and upstart +* service status 4 is also 'not running' +* supervisorctl restart fix +* increased error handling for ec2 module +* can recursively set permissions on directories +* ec2: change to the way AMI tags are handled +* cron module can now also manipulate cron.d files +* virtualenv module can now inherit system site packages (or not) +* lineinfile module now has an insertbefore option +* NetBSD service module support +* fixes to sysctl module where item has multiple values +* AIX support for the user and group modules +* able to specify a different hg repo to pull from than the original set +* add_host module can set ports and other inventory variables +* add_host module can add modules to multiple groups (groups=a,b,c), groups now alias for groupname +* subnet ID can be set on EC2 module +* MySQL module password handling improvements +* added new virtualenv flags to pip and easy_install modules +* various improvements to lineinfile module, now accepts common arguments from file +* force= now replaces thirsty where used before, thirsty remains an alias +* setup module can take a 'filter=' parameter to just return a few facts (not used by playbooks) +* cron module works even if no crontab is present (for cron.d) +* security group ID settable on EC2 module +* misc fixes to sysctl module +* fix to apt module so packages not in cache are still removable +* charset fix to mail module +* postresql db module now does not try to create the 'PUBLIC' user +* SVN module now works correctly with self signed certs +* apt module now has an upgrade parameter (values=yes, no, or 'dist') +* nagios module gets new silence/unsilence commands +* ability to disable proxy usage in get_url (use_proxy=no) +* more OS X facts +* added a 'fail_on_missing' (default no) option to fetch +* added timeout to the uri module (default 30 seconds, adjustable) +* ec2 now has a 'wait' parameter to wait for the instance to be active, eliminates need for separate wait_for call. +* allow regex backreferences in lineinfile +* id attribute on ec2 module can be used to set idempotent-do-not-recreate launches +* icinga support for nagios module +* fix default logins when no my.conf for MySQL module +* option to create users with non-unique UIDs (user module) +* macports module can enable/disable packages +* quotes in my.cnf are stripped by the MySQL modules +* Solaris Service management added +* service module will attempt to auto-add unmanaged chkconfig services when needed +* service module supports systemd service unit files + +Plugins: * added 'with_random_choice' filter plugin +* fixed ~ expansion for fileglob +* with_nested allows for nested loops (see examples in examples/playbooks) 1.0 "Eruption" -- Feb 1 2013 @@ -103,7 +451,7 @@ Other core changes: * fix for template calls when last character is '$' * if ansible_python_interpreter is set on a delegated host, it now works as intended -* --limit can now take "," as seperator as well as ";" or ":" +* --limit can now take "," as separator as well as ";" or ":" * msg is now displaced with newlines when a task fails * if any with_ plugin has no results in a list (empty list for with_items, etc), the task is now skipped * various output formatting fixes/improvements @@ -161,7 +509,7 @@ Plugin changes: * plugin loading code now more streamlined * lookup plugins for DNS text records, environment variables, and redis * added a template lookup plugin $TEMPLATE('filename.j2') -* various tweaks to the EC2 inventory plugin +* various tweaks to the EC2 inventory plugin * jinja2 filters are now pluggable so it's easy to write your own (to_json/etc, are now impl. as such) 0.8 "Cathedral" -- Oct 19, 2012 @@ -175,7 +523,7 @@ Highlighted Core Changes: Other Core Changes: -* ansible config file can also go in '.ansible.cfg' in cwd in addition to ~/.ansible.cfg and /etc/ansible/ansible.cfg +* 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 * ansible-pull example now sets up logrotate for the ansible-pull cron job log * negative host matching (!hosts) fixed for external inventory script usage @@ -400,7 +748,7 @@ internals: * support for older versions of python-apt in the apt module * a new "assemble" module, for constructing files from pieces of files (inspired by Puppet "fragments" idiom) * ability to override most default values with ANSIBLE_FOO environment variables -* --module-path parameter can support multiple directories seperated with the OS path seperator +* --module-path parameter can support multiple directories separated with the OS path separator * with_items can take a variable of type list * ansible_python_interpreter variable available for systems with more than one Python * BIOS and VMware "fact" upgrades diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c0e39944294a6..726c0ea1d35718 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,6 +60,9 @@ Automation tests are run by "make tests" and contain a mixture of integration an * Your user environment should allow "ssh 127.0.0.1" without a password - so a key held in ssh-agent and an authorized_keys entry. +If your module does not require any additional dependencies beyond ansible-core, it is a good idea to write some +tests for the module. + Contributors License Agreement ============================== diff --git a/Makefile b/Makefile index 4e5d1ee4f69213..a7a193fcc21283 100644 --- a/Makefile +++ b/Makefile @@ -20,11 +20,16 @@ OS = $(shell uname -s) # 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`. +MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 docs/man/man1/ansible-pull.1 docs/man/man1/ansible-doc.1 +ifneq ($(shell which a2x 2>/dev/null),) 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 docs/man/man1/ansible-pull.1 docs/man/man1/ansible-doc.1 +else +ASCII2MAN = @echo "ERROR: AsciiDoc 'a2x' command is not installed but is required to build $(MANPAGES)" && exit 1 +endif -SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") +PYTHON=python +SITELIB = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") # VERSION file provides one place to update the software version VERSION := $(shell cat VERSION) @@ -47,23 +52,28 @@ endif # RPM build parameters RPMSPECDIR= packaging/rpm RPMSPEC = $(RPMSPECDIR)/ansible.spec -RPMDIST = $(shell rpm --eval '%dist') +RPMDIST = $(shell rpm --eval '%{?dist}') RPMRELEASE = 1 ifeq ($(OFFICIAL),) RPMRELEASE = 0.git$(DATE) endif RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)" +NOSETESTS := nosetests + ######################################################## all: clean python tests: - PYTHONPATH=./lib nosetests -d -v + PYTHONPATH=./lib $(NOSETESTS) -d -v # To force a rebuild of the docs run 'touch VERSION && make docs' docs: $(MANPAGES) modulepages +authors: + sh hacking/authors.sh + # Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more # recently than %.1.asciidoc. %.1.asciidoc: %.1.asciidoc.in @@ -109,15 +119,17 @@ clean: rm -rf deb-build rm -rf docs/json rm -rf docs/js + @echo "Cleaning up authors file" + rm -f AUTHORS.TXT python: - python setup.py build + $(PYTHON) setup.py build install: - python setup.py install + $(PYTHON) setup.py install sdist: clean docs - python setup.py sdist -t MANIFEST.in + $(PYTHON) setup.py sdist -t MANIFEST.in rpmcommon: sdist @mkdir -p rpm-build @@ -146,6 +158,7 @@ rpm: rpmcommon --define "_specdir $(RPMSPECDIR)" \ --define "_sourcedir %{_topdir}" \ --define "_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" \ + --define "__python `which $(PYTHON)`" \ -ba rpm-build/$(NAME).spec @rm -f rpm-build/$(NAME).spec @echo "#############################################" @@ -164,23 +177,10 @@ deb: debian # for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory modulepages: - PYTHONPATH=./lib hacking/module_formatter.py -A $(VERSION) -t man -o docs/man/man3/ --module-dir=library --template-dir=hacking/templates - -modulejson: - mkdir -p docs/json - PYTHONPATH=./lib hacking/module_formatter.py -A $(VERSION) -t json -o docs/json --module-dir=library --template-dir=hacking/templates - -modulejs: - mkdir -p docs/js - make modulejson - PYTHONPATH=./lib hacking/module_formatter.py -A $(VERSION) -t js -o docs/js --module-dir=docs/json --template-dir=hacking/templates + PYTHONPATH=./lib $(PYTHON) hacking/module_formatter.py -A $(VERSION) -t man -o docs/man/man3/ --module-dir=library --template-dir=hacking/templates # --verbose # because this requires Sphinx it is not run as part of every build, those building the RPM and so on can ignore this webdocs: - (cd docsite; make docs) - -# just for quick testing of all the module docs -webdocs2: - (cd docsite; make modules) + (cd docsite/latest; make docs) diff --git a/README.md b/README.md index 4c8a6f83357ea8..6931dcc476b1ed 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ Ansible Ansible is a radically simple configuration-management, deployment, task-execution, and multinode orchestration framework. -Read the documentation and more at http://ansible.cc +Read the documentation and more at http://ansibleworks.com/ Many users run straight from the development branch (it's generally fine to do so), but you might also wish to consume a release. You can find -instructions on http://ansible.cc/docs/gettingstarted.html for a variety of platforms. If you want a tarball of the last release, go to -http://ansible.cc/releases/ and you can also install with pip (though that will bring in some optional binary dependencies you normally do not need). +instructions on http://ansibleworks.com/docs/gettingstarted.html for a variety of platforms. If you want a tarball of the last release, go to +http://ansibleworks.com/releases/ and you can also install with pip (though that will bring in some optional binary dependencies you normally do not need). Design Principles ================= @@ -25,7 +25,7 @@ Design Principles Get Involved ============ - * [ansible-project mailing list](http://groups.google.com/group/ansible-project) + * [ansible-project mailing list](http://groups.google.com/group/ansible-project), rss: [here](https://groups.google.com/group/ansible-project/feed/rss_v2_0_msgs.xml?num=50&pli=1) * irc.freenode.net: #ansible Branch Info @@ -39,8 +39,6 @@ Branch Info Author ====== -Michael DeHaan -- michael.dehaan@gmail.com - -[http://michaeldehaan.net](http://michaeldehaan.net/) - +Michael DeHaan -- michael@ansibleworks.com +[AnsibleWorks](http://ansibleworks.com) diff --git a/RELEASES.txt b/RELEASES.txt index 9e22c966141169..836a36c9dee3c7 100644 --- a/RELEASES.txt +++ b/RELEASES.txt @@ -1,16 +1,20 @@ Ansible Releases at a Glance ============================ -1.1 "Mean Street" ----- pending -1.0 "Eruption" -------- 02-01-2013 -0.9 "Dreams" ---------- 11-30-2012 -0.8 "Cathedral" ------- 10-19-2012 -0.7 "Panama" ---------- 09-06-2012 -0.6 "Cabo" ------------ 08-06-2012 -0.5 "Amsterdam" ------- 07-04-2012 -0.4 "Unchained" ------- 05-23-2012 -0.3 "Baluchitherium" -- 04-23-2012 -0.0.2 Untitled -0.0.1 Untitled +1.3 "Top of the World" ----- Release Pending +1.2.2 "Hear About It Later" -- 07-05-2013 +1.2.1 "Hear About It Later" -- 07-04-2013 +1.2 "Right Now" ------------ 06-10-2013 +1.1 "Mean Street" ---------- 04-02-2013 +1.0 "Eruption" ------------- 02-01-2013 +0.9 "Dreams" --------------- 11-30-2012 +0.8 "Cathedral" ------------ 10-19-2012 +0.7 "Panama" --------------- 09-06-2012 +0.6 "Cabo" ----------------- 08-06-2012 +0.5 "Amsterdam" ------------ 07-04-2012 +0.4 "Unchained" ------------ 05-23-2012 +0.3 "Baluchitherium" ------- 04-23-2012 +0.0.2 Untitled +0.0.1 Untitled diff --git a/VERSION b/VERSION index 9459d4ba2a0d3c..7e32cd56983e65 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1 +1.3 diff --git a/bin/ansible b/bin/ansible index 102390f42a72a7..7e1150a2591cdf 100755 --- a/bin/ansible +++ b/bin/ansible @@ -20,7 +20,6 @@ ######################################################## import sys -import getpass from ansible.runner import Runner import ansible.constants as C @@ -28,7 +27,6 @@ from ansible import utils from ansible import errors from ansible import callbacks from ansible import inventory - ######################################################## class Cli(object): @@ -45,15 +43,24 @@ class Cli(object): def parse(self): ''' create an options parser for bin/ansible ''' - parser = utils.base_parser(constants=C, runas_opts=True, subset_opts=True, async_opts=True, - output_opts=True, connect_opts=True, check_opts=True, usage='%prog [options]') + parser = utils.base_parser( + constants=C, + runas_opts=True, + subset_opts=True, + async_opts=True, + output_opts=True, + connect_opts=True, + check_opts=True, + diff_opts=False, + usage='%prog [options]' + ) + parser.add_option('-a', '--args', dest='module_args', help="module arguments", default=C.DEFAULT_MODULE_ARGS) parser.add_option('-m', '--module-name', dest='module_name', help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME, default=C.DEFAULT_MODULE_NAME) - parser.add_option('--list-hosts', dest='listhosts', action='store_true', - help="dump out a list of hosts matching input pattern, does not execute any modules!") + options, args = parser.parse_args() self.callbacks.options = options @@ -70,30 +77,29 @@ class Cli(object): pattern = args[0] inventory_manager = inventory.Inventory(options.inventory) + if options.subset: + inventory_manager.subset(options.subset) hosts = inventory_manager.list_hosts(pattern) if len(hosts) == 0: - print >>sys.stderr, "No hosts matched" + callbacks.display("No hosts matched", stderr=True) sys.exit(1) if options.listhosts: for host in hosts: - print ' %s' % host + callbacks.display(' %s' % host) sys.exit(0) - if options.module_name == 'command' and not options.module_args: - print >>sys.stderr, "No argument passed to command module" + if ((options.module_name == 'command' or options.module_name == 'shell') + and not options.module_args): + callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True) sys.exit(1) sshpass = None sudopass = None options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS - if options.ask_pass: - sshpass = getpass.getpass(prompt="SSH password: ") options.ask_sudo_pass= options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS - if options.ask_sudo_pass: - sudopass = getpass.getpass(prompt="sudo password: ") - options.sudo = True - if options.sudo_user: + ( sshpass, sudopass ) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass) + if options.sudo_user or options.ask_sudo_pass: options.sudo = True options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER if options.tree: @@ -110,11 +116,12 @@ class Cli(object): callbacks=self.callbacks, sudo=options.sudo, sudo_pass=sudopass,sudo_user=options.sudo_user, transport=options.connection, subset=options.subset, - check=options.check + check=options.check, + diff=options.check ) if options.seconds: - print "background launch...\n\n" + callbacks.display("background launch...\n\n", color='cyan') results, poller = runner.run_async(options.seconds) results = self.poll_while_needed(poller, options) else: @@ -137,6 +144,10 @@ class Cli(object): ######################################################## if __name__ == '__main__': + callbacks.display("", log_only=True) + callbacks.display(" ".join(sys.argv), log_only=True) + callbacks.display("", log_only=True) + cli = Cli() (options, args) = cli.parse() try: @@ -148,6 +159,6 @@ if __name__ == '__main__': sys.exit(2) except errors.AnsibleError, e: # Generic handler for ansible specific errors - print "ERROR: %s" % str(e) + callbacks.display("ERROR: %s" % str(e), stderr=True, color='red') sys.exit(1) diff --git a/bin/ansible-doc b/bin/ansible-doc index 7618115d2143f8..c6dfc8f6bc9da6 100755 --- a/bin/ansible-doc +++ b/bin/ansible-doc @@ -20,16 +20,11 @@ import os import sys -import yaml -import json -import ast import textwrap import re import optparse -import time import datetime from ansible import utils -from ansible import errors from ansible.utils import module_docs import ansible.constants as C from ansible.utils import version @@ -91,11 +86,20 @@ def print_man(doc): subsequent_indent=opt_indent) + if 'requirements' in doc and doc['requirements'] is not None and len(doc['requirements']) > 0: + req = ", ".join(doc['requirements']) + print "Requirements:%s\n" % textwrap.fill(tty_ify(req), initial_indent=" ", + subsequent_indent=opt_indent) + if 'examples' in doc and len(doc['examples']) > 0: print "Example%s:\n" % ('' if len(doc['examples']) < 2 else 's') for ex in doc['examples']: print "%s\n" % (ex['code']) + if 'plainexamples' in doc and doc['plainexamples'] is not None: + print doc['plainexamples'] + + def print_snippet(doc): desc = tty_ify("".join(doc['short_description'])) @@ -157,8 +161,10 @@ def main(): continue filename = utils.plugins.module_finder.find_plugin(module) + if os.path.isdir(filename): + continue try: - doc = module_docs.get_docstring(filename) + doc, plainexamples = module_docs.get_docstring(filename) desc = tty_ify(doc.get('short_description', '?')) if len(desc) > 55: desc = desc + '...' @@ -172,20 +178,30 @@ def main(): if len(args) == 0: p.print_help() - + + def print_paths(finder): + ''' Returns a string suitable for printing of the search path ''' + + # Uses a list to get the order right + ret = [] + for i in finder._get_paths(): + if i not in ret: + ret.append(i) + return os.pathsep.join(ret) + for module in args: filename = utils.plugins.module_finder.find_plugin(module) if filename is None: sys.stderr.write("module %s not found in %s\n" % (module, - utils.plugins.module_finder.print_paths())) + print_paths(utils.plugins.module_finder))) continue if any(filename.endswith(x) for x in BLACKLIST_EXTS): continue try: - doc = module_docs.get_docstring(filename) + doc, plainexamples = module_docs.get_docstring(filename) except: traceback.print_exc() sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module) @@ -202,13 +218,16 @@ def main(): doc['filename'] = filename doc['docuri'] = doc['module'].replace('_', '-') doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + doc['plainexamples'] = plainexamples if options.show_snippet: print_snippet(doc) else: print_man(doc) else: - sys.stderr.write("ERROR: module %s missing documentation\n" % module) + # this typically means we couldn't even parse the docstring, not just that the YAML is busted, + # probably a quoting issue. + sys.stderr.write("ERROR: module %s missing documentation (or could not parse documentation)\n" % module) if __name__ == '__main__': main() diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 138e5a0d935728..0659a0e8a62708 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -19,50 +19,60 @@ ####################################################### import sys -import getpass import os import ansible.playbook import ansible.constants as C +import ansible.utils.template from ansible import errors from ansible import callbacks from ansible import utils from ansible.color import ANSIBLE_COLOR, stringc +from ansible.callbacks import display def colorize(lead, num, color): """ Print 'lead' = 'num' in 'color' """ - if num != 0 and ANSIBLE_COLOR: + if num != 0 and ANSIBLE_COLOR and color is not None: return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color)) else: return "%s=%-4s" % (lead, str(num)) -def hostcolor(host, stats): - if ANSIBLE_COLOR: +def hostcolor(host, stats, color=True): + if ANSIBLE_COLOR and color: if stats['failures'] != 0 or stats['unreachable'] != 0: - return "%-41s" % stringc(host, 'red') + return "%-37s" % stringc(host, 'red') elif stats['changed'] != 0: - return "%-41s" % stringc(host, 'yellow') + return "%-37s" % stringc(host, 'yellow') else: - return "%-41s" % stringc(host, 'green') - return "%-30s" % host + return "%-37s" % stringc(host, 'green') + return "%-26s" % host def main(args): ''' run ansible-playbook operations ''' # create parser for CLI options - usage = "%prog playbook.yml" - parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, - runas_opts=True, subset_opts=True, check_opts=True) + parser = utils.base_parser( + constants=C, + usage = "%prog playbook.yml", + connect_opts=True, + runas_opts=True, + subset_opts=True, + check_opts=True, + diff_opts=True + ) parser.add_option('-e', '--extra-vars', dest="extra_vars", default=None, help="set additional key=value variables from the CLI") parser.add_option('-t', '--tags', dest='tags', default='all', help="only run plays and tasks tagged with these values") - # FIXME: list hosts is a common option and can be moved to utils/__init__.py - parser.add_option('--list-hosts', dest='listhosts', action='store_true', - help="dump out a list of hosts, each play will run against, does not run playbook!") parser.add_option('--syntax-check', dest='syntax', action='store_true', - help="do a playbook syntax check on the playbook, do not execute the playbook") + help="perform a syntax check on the playbook, but do not execute it") + parser.add_option('--list-tasks', dest='listtasks', action='store_true', + help="list all tasks that would be executed") + parser.add_option('--step', dest='step', action='store_true', + help="one-step-at-a-time: confirm each task before running") + parser.add_option('--start-at-task', dest='start_at', + help="start the playbook at the task matching this name") options, args = parser.parse_args(args) @@ -77,18 +87,15 @@ def main(args): sshpass = None sudopass = None - if not options.listhosts: + if not options.listhosts and not options.syntax and not options.listtasks: options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS - if options.ask_pass: - sshpass = getpass.getpass(prompt="SSH password: ") options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS - if options.ask_sudo_pass: - sudopass = getpass.getpass(prompt="sudo password: ") - options.sudo = True - if options.sudo_user: - options.sudo = True + ( sshpass, sudopass ) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass) options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER - extra_vars = utils.parse_kv(options.extra_vars) + if options.extra_vars and options.extra_vars[0] in '[{': + extra_vars = utils.json_loads(options.extra_vars) + else: + extra_vars = utils.parse_kv(options.extra_vars) only_tags = options.tags.split(",") for playbook in args: @@ -100,8 +107,15 @@ def main(args): # run all playbooks specified on the command line for playbook in args: + # let inventory know which playbooks are using so it can know the basedirs + inventory.set_playbook_basedir(os.path.dirname(playbook)) + stats = callbacks.AggregateStats() playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY) + if options.step: + playbook_cb.step = options.step + if options.start_at: + playbook_cb.start_at = options.start_at runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY) pb = ansible.playbook.PlayBook( @@ -122,20 +136,37 @@ def main(args): extra_vars=extra_vars, private_key_file=options.private_key_file, only_tags=only_tags, - check=options.check + check=options.check, + diff=options.diff ) - - if options.listhosts: + + if options.listhosts or options.listtasks: + print '' print 'playbook: %s' % playbook + print '' playnum = 0 for (play_ds, play_basedir) in zip(pb.playbook, pb.play_basedirs): playnum += 1 play = ansible.playbook.Play(pb, play_ds, play_basedir) label = play.name - hosts = pb.inventory.list_hosts(play.hosts) - print ' hosts in play %s (%s): #%d' % (playnum, label, len(hosts)) - for host in hosts: - print ' %s' % host + if options.listhosts: + hosts = pb.inventory.list_hosts(play.hosts) + print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts)) + for host in hosts: + print ' %s' % host + if options.listtasks: + matched_tags, unmatched_tags = play.compare_tags(pb.only_tags) + unmatched_tags.discard('all') + unknown_tags = set(pb.only_tags) - (matched_tags | unmatched_tags) + if unknown_tags: + continue + print ' play #%d (%s): task count=%d' % (playnum, label, len(play.tasks())) + for task in play.tasks(): + if set(task.tags).intersection(pb.only_tags): + if getattr(task, 'name', None) is not None: + # meta tasks have no names + print ' %s' % task.name + print '' continue if options.syntax: @@ -143,40 +174,66 @@ def main(args): print 'Playbook Syntax is fine' return 0 + failed_hosts = [] try: pb.run() hosts = sorted(pb.stats.processed.keys()) - print callbacks.banner("PLAY RECAP") + display(callbacks.banner("PLAY RECAP")) playbook_cb.on_stats(pb.stats) + for h in hosts: t = pb.stats.summarize(h) - print "%-30s : %s %s %s %s " % ( + if t['unreachable'] > 0 or t['failures'] > 0: + failed_hosts.append(h) + + if len(failed_hosts) > 0: + filename = pb.generate_retry_inventory(failed_hosts) + if filename: + display(" to retry, use: --limit @%s\n" % filename) + + for h in hosts: + t = pb.stats.summarize(h) + + display("%s : %s %s %s %s" % ( hostcolor(h, t), colorize('ok', t['ok'], 'green'), colorize('changed', t['changed'], 'yellow'), colorize('unreachable', t['unreachable'], 'red'), - colorize('failed', t['failures'], 'red')) - - print "\n" - for h in hosts: - stats = pb.stats.summarize(h) - if stats['failures'] != 0 or stats['unreachable'] != 0: - return 2 + colorize('failed', t['failures'], 'red')), + screen_only=True + ) + + display("%s : %s %s %s %s" % ( + hostcolor(h, t, False), + colorize('ok', t['ok'], None), + colorize('changed', t['changed'], None), + colorize('unreachable', t['unreachable'], None), + colorize('failed', t['failures'], None)), + log_only=True + ) + + + print "" + if len(failed_hosts) > 0: + return 2 except errors.AnsibleError, e: - print >>sys.stderr, "ERROR: %s" % e + display("ERROR: %s" % e, color='red') return 1 return 0 if __name__ == "__main__": + display(" ", log_only=True) + display(" ".join(sys.argv), log_only=True) + display(" ", log_only=True) try: sys.exit(main(sys.argv[1:])) except errors.AnsibleError, e: - print >>sys.stderr, "ERROR: %s" % e + display("ERROR: %s" % e, color='red', stderr=True) sys.exit(1) diff --git a/bin/ansible-pull b/bin/ansible-pull index 89356c7b68e6f0..f5f026c6449fa5 100755 --- a/bin/ansible-pull +++ b/bin/ansible-pull @@ -45,8 +45,9 @@ import socket from optparse import OptionParser DEFAULT_PLAYBOOK = 'local.yml' -PLAYBOOK_ERRORS = { 1: 'File does not exist', - 2: 'File is not readable' } +PLAYBOOK_ERRORS = {1: 'File does not exist', + 2: 'File is not readable'} + def _run(cmd): print >>sys.stderr, "Running: '%s'" % cmd @@ -56,7 +57,8 @@ def _run(cmd): print out if cmd.returncode != 0: print >>sys.stderr, err - return cmd.returncode + return cmd.returncode, out + def try_playbook(path): if not os.path.exists(path): @@ -65,6 +67,7 @@ def try_playbook(path): return 2 return 0 + def select_playbook(path, args): playbook = None if len(args) > 0 and args[0] is not None: @@ -89,25 +92,32 @@ def select_playbook(path, args): print >>sys.stderr, "\n".join(errors) return playbook + def main(args): """ Set up and run a local playbook """ usage = "%prog [options] [playbook.yml]" parser = OptionParser(usage=usage) parser.add_option('--purge', default=False, action='store_true', - help='Purge git checkout after playbook run') + help='purge git checkout after playbook run') + parser.add_option('-o', '--only-if-changed', dest='ifchanged', default=False, action='store_true', + help='only run the playbook if the repository has been updated') parser.add_option('-d', '--directory', dest='dest', default=None, - help='Directory to clone git repository to') + help='directory to clone the git repository to') parser.add_option('-U', '--url', dest='url', default=None, - help='URL of git repository') + help='URL of the git repository') parser.add_option('-C', '--checkout', dest='checkout', default="HEAD", - help='Branch/Tag/Commit to checkout. Defaults to HEAD.') + help='branch/tag/commit to checkout; defaults to HEAD') + parser.add_option('-i', '--inventory-file', dest='inventory', + help="location of the inventory host file") options, args = parser.parse_args(args) if not options.dest: parser.error("Missing required directory argument") return 1 + options.dest = os.path.abspath(options.dest) + if not options.url: parser.error("URL for git repo not specified, use -h for help") return 1 @@ -115,12 +125,18 @@ def main(args): now = datetime.datetime.now() print >>sys.stderr, now.strftime("Starting ansible-pull at %F %T") + inv_opts = 'localhost,' limit_opts = 'localhost:%s:127.0.0.1' % socket.getfqdn() git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout) - cmd = 'ansible all -c local --limit "%s" -m git -a "%s"' % (limit_opts, git_opts) - rc = _run(cmd) + cmd = 'ansible all -c local -i "%s" --limit "%s" -m git -a "%s"' % ( + inv_opts, limit_opts, git_opts + ) + rc, out = _run(cmd) if rc != 0: return rc + elif options.ifchanged and '"changed": true' not in out: + print "Repository has not changed, quitting." + return 0 playbook = select_playbook(options.dest, args) @@ -129,8 +145,10 @@ def main(args): return 1 cmd = 'ansible-playbook -c local --limit "%s" %s' % (limit_opts, playbook) + if options.inventory: + cmd += ' -i "%s"' % options.inventory os.chdir(options.dest) - rc = _run(cmd) + rc, out = _run(cmd) if options.purge: os.chdir('/') diff --git a/docs/man/man1/ansible-doc.1 b/docs/man/man1/ansible-doc.1 index 9bf31657bf38be..16d5759b58d5e1 100644 --- a/docs/man/man1/ansible-doc.1 +++ b/docs/man/man1/ansible-doc.1 @@ -2,12 +2,12 @@ .\" Title: ansible-doc .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 02/01/2013 +.\" Date: 04/02/2013 .\" Manual: System administration commands -.\" Source: Ansible 1.1 +.\" Source: Ansible 1.2 .\" Language: English .\" -.TH "ANSIBLE\-DOC" "1" "02/01/2013" "Ansible 1\&.1" "System administration commands" +.TH "ANSIBLE\-DOC" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -52,6 +52,6 @@ Copyright \(co 2012, Jan\-Piet Mens Ansible is released under the terms of the GPLv3 License\&. .SH "SEE ALSO" .sp -\fBansible\-playbook\fR(1), \fBansible\fR(1) +\fBansible\-playbook\fR(1), \fBansible\fR(1), \fBansible\-pull\fR(1) .sp Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/ diff --git a/docs/man/man1/ansible-doc.1.asciidoc.in b/docs/man/man1/ansible-doc.1.asciidoc.in index 39722aa545617a..f67019861d80f4 100644 --- a/docs/man/man1/ansible-doc.1.asciidoc.in +++ b/docs/man/man1/ansible-doc.1.asciidoc.in @@ -59,7 +59,7 @@ Ansible is released under the terms of the GPLv3 License. SEE ALSO -------- -*ansible-playbook*(1), *ansible*(1) +*ansible-playbook*(1), *ansible*(1), *ansible-pull*(1) Extensive documentation as well as IRC and mailing list info is available on the ansible home page: diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index 548a491a58ee00..85187cdbcfec5a 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -2,12 +2,12 @@ .\" Title: ansible-playbook .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 02/01/2013 +.\" Date: 06/03/2013 .\" Manual: System administration commands -.\" Source: Ansible 1.1 +.\" Source: Ansible 1.2 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "02/01/2013" "Ansible 1\&.1" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "06/03/2013" "Ansible 1\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -51,13 +51,13 @@ to the inventory hosts file, which defaults to .RS 4 The \fIDIRECTORY\fR -to load modules from\&. The default is -\fI/usr/share/ansible\fR\&. +search path to load modules from\&. The default is +\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&. .RE .PP \fB\-e\fR \fIVARS\fR, \fB\-\-extra\-vars=\fR\fIVARS\fR .RS 4 -Extra variables to inject into a playbook, in key=value key=value format\&. +Extra variables to inject into a playbook, in key=value key=value format or as quoted JSON (hashes and arrays)\&. .RE .PP \fB\-f\fR \fINUM\fR, \fB\-\-forks=\fR\fINUM\fR @@ -82,6 +82,26 @@ Prompt for the password to use for playbook plays that request sudo access, if a Desired sudo user (default=root)\&. .RE .PP +\fB\-\-tags\fR +.RS 4 +Run only these tags from the playbook +.RE +.PP +\fB\-\-syntax\-check\fR +.RS 4 +Look for syntax errors in the playbook, but don\(cqt run anything +.RE +.PP +\fB\-\-check\fR +.RS 4 +Do not make any changes on the remote system, but test resources to see what might have changed\&. Note this can not scan all possible resource types and is only a simulation\&. +.RE +.PP +\fB\-\-diff\fR +.RS 4 +When changing any templated files, show the unified diffs of how they changed\&. When used with \-\-check, shows how the files would have changed if \-\-check were not used\&. +.RE +.PP \fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR .RS 4 Connection timeout to use when trying to talk to hosts, in @@ -139,6 +159,6 @@ Copyright \(co 2012, Michael DeHaan Ansible is released under the terms of the GPLv3 License\&. .SH "SEE ALSO" .sp -\fBansible\fR(1), \fBansible\-pull\fR(1) +\fBansible\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1) .sp Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/ diff --git a/docs/man/man1/ansible-playbook.1.asciidoc.in b/docs/man/man1/ansible-playbook.1.asciidoc.in index 2c8b7e07a1245a..5bc24eda6253f3 100644 --- a/docs/man/man1/ansible-playbook.1.asciidoc.in +++ b/docs/man/man1/ansible-playbook.1.asciidoc.in @@ -47,11 +47,14 @@ The 'PATH' to the inventory hosts file, which defaults to *-M* 'DIRECTORY', *--module-path=*'DIRECTORY':: -The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'. +The 'DIRECTORY' search path to load modules from. The default is +'/usr/share/ansible'. This can also be set with the ANSIBLE_LIBRARY +environment variable. *-e* 'VARS', *--extra-vars=*'VARS':: -Extra variables to inject into a playbook, in key=value key=value format. +Extra variables to inject into a playbook, in key=value key=value format or +as quoted JSON (hashes and arrays). *-f* 'NUM', *--forks=*'NUM':: @@ -73,6 +76,25 @@ access, if any. Desired sudo user (default=root). +*--tags*:: + +Run only these tags from the playbook + +*--syntax-check*:: + +Look for syntax errors in the playbook, but don't run anything + +*--check*:: + +Do not make any changes on the remote system, but test resources to see what might +have changed. Note this can not scan all possible resource types and is only +a simulation. + +*--diff*:: + +When changing any templated files, show the unified diffs of how they changed. When +used with --check, shows how the files would have changed if --check were not used. + *-T* 'SECONDS', *--timeout=*'SECONDS':: Connection timeout to use when trying to talk to hosts, in 'SECONDS'. @@ -136,7 +158,7 @@ Ansible is released under the terms of the GPLv3 License. SEE ALSO -------- -*ansible*(1), *ansible-pull*(1) +*ansible*(1), *ansible-pull*(1), *ansible-doc*(1) Extensive documentation as well as IRC and mailing list info is available on the ansible home page: diff --git a/docs/man/man1/ansible-pull.1 b/docs/man/man1/ansible-pull.1 index 4fe3808cae2c7e..a66fac59005c60 100644 --- a/docs/man/man1/ansible-pull.1 +++ b/docs/man/man1/ansible-pull.1 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 02/01/2013 +.\" Date: 04/02/2013 .\" Manual: System administration commands -.\" Source: Ansible 1.1 +.\" Source: Ansible 1.2 .\" Language: English .\" -.TH "ANSIBLE" "1" "02/01/2013" "Ansible 1\&.1" "System administration commands" +.TH "ANSIBLE" "1" "04/02/2013" "Ansible 1\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -59,6 +59,6 @@ Copyright \(co 2012, Michael DeHaan Ansible is released under the terms of the GPLv3 License\&. .SH "SEE ALSO" .sp -\fBansible\fR(1), \fBansible\-playbook\fR(1) +\fBansible\fR(1), \fBansible\-playbook\fR(1), \fBansible\-doc\fR(1) .sp Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/ diff --git a/docs/man/man1/ansible-pull.1.asciidoc.in b/docs/man/man1/ansible-pull.1.asciidoc.in index 5bf63e850ec257..19ed537dead0a5 100644 --- a/docs/man/man1/ansible-pull.1.asciidoc.in +++ b/docs/man/man1/ansible-pull.1.asciidoc.in @@ -12,7 +12,7 @@ ansible-pull - set up a remote copy of ansible on each managed node SYNOPSIS -------- -ansible -d DEST -U URL [ -C CHECKOUT ] +ansible -d DEST -U URL [ -C CHECKOUT ] [ -i INVENTORY ] [ ] DESCRIPTION @@ -35,6 +35,17 @@ ansible-pull runs would be an excellent way to gather and analyze remote logs from ansible-pull. +OPTIONAL ARGUMENT +----------------- + +*filename.yml*:: + +The name of one the YAML format files to run as an ansible playbook. This can +be a relative path within the git checkout. If not provided, ansible-pull +will look for a playbook based on the host's fully-qualified domain name and +finally a playbook named *local.yml*. + + OPTIONS ------- @@ -50,6 +61,14 @@ URL of git repository to clone. Branch/Tag/Commit to checkout. Defaults to 'HEAD'. +*-i* 'PATH', *--inventory=*'PATH':: + +The 'PATH' to the inventory hosts file. This can be a relative path within +the git checkout. + +*--purge*:: + +Purge the git checkout after the playbook is run. AUTHOR @@ -70,7 +89,7 @@ Ansible is released under the terms of the GPLv3 License. SEE ALSO -------- -*ansible*(1), *ansible-playbook*(1) +*ansible*(1), *ansible-playbook*(1), *ansible-doc*(1) Extensive documentation as well as IRC and mailing list info is available on the ansible home page: diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1 index c8daacd08d8918..813f1a8cc0ad3c 100644 --- a/docs/man/man1/ansible.1 +++ b/docs/man/man1/ansible.1 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: 02/01/2013 +.\" Date: 05/29/2013 .\" Manual: System administration commands -.\" Source: Ansible 1.1 +.\" Source: Ansible 1.2 .\" Language: English .\" -.TH "ANSIBLE" "1" "02/01/2013" "Ansible 1\&.1" "System administration commands" +.TH "ANSIBLE" "1" "05/29/2013" "Ansible 1\&.2" "System administration commands" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -69,8 +69,8 @@ Execute the module called .RS 4 The \fIDIRECTORY\fR -to load modules from\&. The default is -\fI/usr/share/ansible\fR\&. +search path to load modules from\&. The default is +\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&. .RE .PP \fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\' @@ -131,7 +131,7 @@ seconds\&. Requires .RS 4 Use this remote \fIUSERNAME\fR -instead of root\&. +instead of the current user\&. .RE .PP \fB\-U\fR \fISUDO_USERNAME\fR, \fB\-\-sudo\-user=\fR\fISUDO_USERNAME\fR @@ -194,6 +194,6 @@ Copyright \(co 2012, Michael DeHaan Ansible is released under the terms of the GPLv3 License\&. .SH "SEE ALSO" .sp -\fBansible\-playbook\fR(1), \fBansible\-pull\fR(1) +\fBansible\-playbook\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1) .sp Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/ diff --git a/docs/man/man1/ansible.1.asciidoc.in b/docs/man/man1/ansible.1.asciidoc.in index f55c7ff41ea8ca..0f88311d933b53 100644 --- a/docs/man/man1/ansible.1.asciidoc.in +++ b/docs/man/man1/ansible.1.asciidoc.in @@ -60,8 +60,9 @@ Execute the module called 'NAME'. *-M* 'DIRECTORY', *--module-path=*'DIRECTORY':: -The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'. - +The 'DIRECTORY' search path to load modules from. The default is +'/usr/share/ansible'. This can also be set with the ANSIBLE_LIBRARY +environment variable. *-a* \'_ARGUMENTS_', *--args=*\'_ARGUMENTS_':: @@ -102,7 +103,7 @@ Poll a background job every 'NUM' seconds. Requires *-B*. *-u* 'USERNAME', *--remote-user=*'USERNAME':: -Use this remote 'USERNAME' instead of root. +Use this remote 'USERNAME' instead of the current user. *-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME':: @@ -173,7 +174,7 @@ Ansible is released under the terms of the GPLv3 License. SEE ALSO -------- -*ansible-playbook*(1), *ansible-pull*(1) +*ansible-playbook*(1), *ansible-pull*(1), *ansible-doc*(1) Extensive documentation as well as IRC and mailing list info is available on the ansible home page: diff --git a/docsite/DOCUMENTATION.yaml b/docsite/DOCUMENTATION.yaml deleted file mode 120000 index 2ddf8e26743715..00000000000000 --- a/docsite/DOCUMENTATION.yaml +++ /dev/null @@ -1 +0,0 @@ -../examples/DOCUMENTATION.yaml \ No newline at end of file diff --git a/docsite/Makefile b/docsite/latest/Makefile similarity index 64% rename from docsite/Makefile rename to docsite/latest/Makefile index cb9b839050a22b..8f480bfd0372fa 100644 --- a/docsite/Makefile +++ b/docsite/latest/Makefile @@ -1,6 +1,6 @@ #!/usr/bin/make SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") -FORMATTER=../hacking/module_formatter.py +FORMATTER=../../hacking/module_formatter.py all: clean docs @@ -11,9 +11,10 @@ viewdocs: clean ./build-site.py view htmldocs: - ./build-site.py rst + ./build-site.py rst clean: + -rm -rf htmlout -rm -f .buildinfo -rm -f *.inv -rm -rf *.doctrees @@ -25,7 +26,7 @@ clean: .PHONEY: docs clean -modules: $(FORMATTER) ../hacking/templates/rst.j2 - $(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../library -o rst/modules/ --includes-file=rst/modules/_list.rst +modules: $(FORMATTER) ../../hacking/templates/rst.j2 + PYTHONPATH=../../lib $(FORMATTER) -t rst --template-dir=../../hacking/templates --module-dir=../../library -o rst/modules/ --includes-file=rst/modules/_list.rst diff --git a/docsite/README.md b/docsite/latest/README.md similarity index 89% rename from docsite/README.md rename to docsite/latest/README.md index 73b569c138b775..0314bf8b7f404a 100644 --- a/docsite/README.md +++ b/docsite/latest/README.md @@ -1,7 +1,7 @@ Homepage and documentation source for the Ansible ================================================= -This project hosts the source behind [ansible.cc/docs](http://ansible.cc/docs) +This project hosts the source behind [ansibleworks.com/docs](http://www.ansibleworks.com/docs/) Contributions to the documentation are welcome. To make changes, submit a pull request that changes the restructured text files in the "rst/" directory only, and Michael can diff --git a/docsite/latest/_static/ansible-local.css b/docsite/latest/_static/ansible-local.css new file mode 100644 index 00000000000000..5fb48d0498d90e --- /dev/null +++ b/docsite/latest/_static/ansible-local.css @@ -0,0 +1,8 @@ +/* Local CSS tweaks for ansible */ +.dropdown-menu { + overflow-y: auto; +} + +h2 { + padding-top: 40px; +} \ No newline at end of file diff --git a/docsite/_static/basic.css b/docsite/latest/_static/basic.css similarity index 100% rename from docsite/_static/basic.css rename to docsite/latest/_static/basic.css diff --git a/docsite/_static/bootstrap-dropdown.js b/docsite/latest/_static/bootstrap-dropdown.js similarity index 100% rename from docsite/_static/bootstrap-dropdown.js rename to docsite/latest/_static/bootstrap-dropdown.js diff --git a/docsite/_static/bootstrap-scrollspy.js b/docsite/latest/_static/bootstrap-scrollspy.js similarity index 100% rename from docsite/_static/bootstrap-scrollspy.js rename to docsite/latest/_static/bootstrap-scrollspy.js diff --git a/docsite/_static/bootstrap-sphinx.css b/docsite/latest/_static/bootstrap-sphinx.css similarity index 100% rename from docsite/_static/bootstrap-sphinx.css rename to docsite/latest/_static/bootstrap-sphinx.css diff --git a/docsite/_static/bootstrap.css b/docsite/latest/_static/bootstrap.css similarity index 100% rename from docsite/_static/bootstrap.css rename to docsite/latest/_static/bootstrap.css diff --git a/docsite/_static/default.css b/docsite/latest/_static/default.css similarity index 100% rename from docsite/_static/default.css rename to docsite/latest/_static/default.css diff --git a/docsite/_static/doctools.js b/docsite/latest/_static/doctools.js similarity index 100% rename from docsite/_static/doctools.js rename to docsite/latest/_static/doctools.js diff --git a/docsite/_static/favicon.ico b/docsite/latest/_static/favicon.ico similarity index 100% rename from docsite/_static/favicon.ico rename to docsite/latest/_static/favicon.ico diff --git a/docsite/_static/file.png b/docsite/latest/_static/file.png similarity index 100% rename from docsite/_static/file.png rename to docsite/latest/_static/file.png diff --git a/docsite/_static/jquery.js b/docsite/latest/_static/jquery.js similarity index 100% rename from docsite/_static/jquery.js rename to docsite/latest/_static/jquery.js diff --git a/docsite/_static/minus.png b/docsite/latest/_static/minus.png similarity index 100% rename from docsite/_static/minus.png rename to docsite/latest/_static/minus.png diff --git a/docsite/_static/plus.png b/docsite/latest/_static/plus.png similarity index 100% rename from docsite/_static/plus.png rename to docsite/latest/_static/plus.png diff --git a/docsite/_static/pygments.css b/docsite/latest/_static/pygments.css similarity index 100% rename from docsite/_static/pygments.css rename to docsite/latest/_static/pygments.css diff --git a/docsite/_static/searchtools.js b/docsite/latest/_static/searchtools.js similarity index 100% rename from docsite/_static/searchtools.js rename to docsite/latest/_static/searchtools.js diff --git a/docsite/_static/sidebar.js b/docsite/latest/_static/sidebar.js similarity index 100% rename from docsite/_static/sidebar.js rename to docsite/latest/_static/sidebar.js diff --git a/docsite/_static/underscore.js b/docsite/latest/_static/underscore.js similarity index 100% rename from docsite/_static/underscore.js rename to docsite/latest/_static/underscore.js diff --git a/docsite/latest/_themes/aworks/layout.html b/docsite/latest/_themes/aworks/layout.html new file mode 100644 index 00000000000000..28068796daa429 --- /dev/null +++ b/docsite/latest/_themes/aworks/layout.html @@ -0,0 +1,49 @@ + + +
+
+ +
+
+ +{% macro navBar() %} + +{% endmacro %} + +{# Silence the sidebar's, relbar's #} +{% block sidebar1 %}{% endblock %} +{% block sidebar2 %}{% endblock %} +{% block relbar1 %}{% endblock %} +{% block relbar2 %}{% endblock %} + +{%- block content %} +
+
+ + {% block header %}{{ navBar() }}{% endblock %} + + +
+ {% block body %} {% endblock %} +
+
+
+{%- endblock %} + + diff --git a/docsite/_themes/bootstrap/relations.html b/docsite/latest/_themes/aworks/relations.html similarity index 100% rename from docsite/_themes/bootstrap/relations.html rename to docsite/latest/_themes/aworks/relations.html diff --git a/docsite/_themes/bootstrap/searchbox.html b/docsite/latest/_themes/aworks/searchbox.html similarity index 100% rename from docsite/_themes/bootstrap/searchbox.html rename to docsite/latest/_themes/aworks/searchbox.html diff --git a/docsite/_themes/bootstrap/sourcelink.html b/docsite/latest/_themes/aworks/sourcelink.html similarity index 100% rename from docsite/_themes/bootstrap/sourcelink.html rename to docsite/latest/_themes/aworks/sourcelink.html diff --git a/docsite/_themes/bootstrap/theme.conf b/docsite/latest/_themes/aworks/theme.conf similarity index 100% rename from docsite/_themes/bootstrap/theme.conf rename to docsite/latest/_themes/aworks/theme.conf diff --git a/docsite/_themes/bootstrap/globaltoc.html b/docsite/latest/_themes/bootstrap/globaltoc.html similarity index 100% rename from docsite/_themes/bootstrap/globaltoc.html rename to docsite/latest/_themes/bootstrap/globaltoc.html diff --git a/docsite/_themes/bootstrap/layout.html b/docsite/latest/_themes/bootstrap/layout.html similarity index 86% rename from docsite/_themes/bootstrap/layout.html rename to docsite/latest/_themes/bootstrap/layout.html index 1d377d48b861c2..43431de0621542 100644 --- a/docsite/_themes/bootstrap/layout.html +++ b/docsite/latest/_themes/bootstrap/layout.html @@ -1,6 +1,6 @@ {% extends "basic/layout.html" %} {% set script_files = script_files + ['_static/bootstrap-dropdown.js', '_static/bootstrap-scrollspy.js'] %} -{% set css_files = ['_static/bootstrap.css', '_static/bootstrap-sphinx.css'] + css_files %} +{% set css_files = ['_static/bootstrap.css', '_static/bootstrap-sphinx.css', '_static/ansible-local.css'] + css_files %} {# Sidebar: Rework into our Boostrap nav section. #} {% macro navBar() %} @@ -97,6 +97,20 @@ s.parentNode.insertBefore(ga, s); })(); + + {% endblock %} @@ -120,6 +134,8 @@ {%- block footer %}
+
+AnsibleWorks

{%- if show_copyright %} {%- if hasdoc('copyright') %} @@ -133,6 +149,7 @@ {%- endif %}

+
{%- endblock %} diff --git a/docsite/_themes/bootstrap/localtoc.html b/docsite/latest/_themes/bootstrap/localtoc.html similarity index 67% rename from docsite/_themes/bootstrap/localtoc.html rename to docsite/latest/_themes/bootstrap/localtoc.html index 6f1bc8433fc6a9..d3014cf7fa3d6f 100644 --- a/docsite/_themes/bootstrap/localtoc.html +++ b/docsite/latest/_themes/bootstrap/localtoc.html @@ -1,5 +1,5 @@ diff --git a/docsite/latest/_themes/bootstrap/relations.html b/docsite/latest/_themes/bootstrap/relations.html new file mode 100644 index 00000000000000..b3df68e3b54227 --- /dev/null +++ b/docsite/latest/_themes/bootstrap/relations.html @@ -0,0 +1,8 @@ +{%- if prev %} +
  • {{ "«"|safe }} {{ prev.title }}
  • +{%- endif %} +{%- if next %} +
  • {{ next.title }} {{ "»"|safe }}
  • +{%- endif %} diff --git a/docsite/latest/_themes/bootstrap/searchbox.html b/docsite/latest/_themes/bootstrap/searchbox.html new file mode 100644 index 00000000000000..3063dde3d1a6d7 --- /dev/null +++ b/docsite/latest/_themes/bootstrap/searchbox.html @@ -0,0 +1,7 @@ +{%- if pagename != "search" %} +
    + + + +
    +{%- endif %} diff --git a/docsite/latest/_themes/bootstrap/sourcelink.html b/docsite/latest/_themes/bootstrap/sourcelink.html new file mode 100644 index 00000000000000..21ae2d8fef81bf --- /dev/null +++ b/docsite/latest/_themes/bootstrap/sourcelink.html @@ -0,0 +1,4 @@ +{%- if show_source and has_source and sourcename %} +
  • {{ _('Source') }}
  • +{%- endif %} diff --git a/docsite/_themes/bootstrap/static/bootstrap-dropdown.js b/docsite/latest/_themes/bootstrap/static/bootstrap-dropdown.js similarity index 100% rename from docsite/_themes/bootstrap/static/bootstrap-dropdown.js rename to docsite/latest/_themes/bootstrap/static/bootstrap-dropdown.js diff --git a/docsite/_themes/bootstrap/static/bootstrap-scrollspy.js b/docsite/latest/_themes/bootstrap/static/bootstrap-scrollspy.js similarity index 100% rename from docsite/_themes/bootstrap/static/bootstrap-scrollspy.js rename to docsite/latest/_themes/bootstrap/static/bootstrap-scrollspy.js diff --git a/docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t b/docsite/latest/_themes/bootstrap/static/bootstrap-sphinx.css_t similarity index 100% rename from docsite/_themes/bootstrap/static/bootstrap-sphinx.css_t rename to docsite/latest/_themes/bootstrap/static/bootstrap-sphinx.css_t diff --git a/docsite/_themes/bootstrap/static/bootstrap.css b/docsite/latest/_themes/bootstrap/static/bootstrap.css similarity index 100% rename from docsite/_themes/bootstrap/static/bootstrap.css rename to docsite/latest/_themes/bootstrap/static/bootstrap.css diff --git a/docsite/latest/_themes/bootstrap/theme.conf b/docsite/latest/_themes/bootstrap/theme.conf new file mode 100644 index 00000000000000..64f9efd56981e8 --- /dev/null +++ b/docsite/latest/_themes/bootstrap/theme.conf @@ -0,0 +1,5 @@ +# Twitter Bootstrap Theme +[theme] +inherit = basic +stylesheet = basic.css +pygments_style = tango diff --git a/docsite/ansible-logo.png b/docsite/latest/ansible-logo.png similarity index 100% rename from docsite/ansible-logo.png rename to docsite/latest/ansible-logo.png diff --git a/docsite/ansible_arch.jpg b/docsite/latest/ansible_arch.jpg similarity index 100% rename from docsite/ansible_arch.jpg rename to docsite/latest/ansible_arch.jpg diff --git a/docsite/ansible_arch2.jpg b/docsite/latest/ansible_arch2.jpg similarity index 100% rename from docsite/ansible_arch2.jpg rename to docsite/latest/ansible_arch2.jpg diff --git a/docsite/build-site.py b/docsite/latest/build-site.py similarity index 90% rename from docsite/build-site.py rename to docsite/latest/build-site.py index b0dfe9d11bec4e..05ed4aa135bfed 100755 --- a/docsite/build-site.py +++ b/docsite/latest/build-site.py @@ -21,7 +21,13 @@ import os import sys import traceback -from sphinx.application import Sphinx +try: + from sphinx.application import Sphinx +except ImportError: + print "#################################" + print "Dependency missing: Python Sphinx" + print "#################################" + sys.exit(1) import os @@ -39,7 +45,7 @@ def __init__(self): try: buildername = 'html' - outdir = os.path.abspath(os.getcwd()) + outdir = os.path.abspath(os.path.join(os.getcwd(), "htmlout")) # Create the output directory if it doesn't exist if not os.access(outdir, os.F_OK): os.mkdir(outdir) diff --git a/docsite/conf.py b/docsite/latest/conf.py similarity index 96% rename from docsite/conf.py rename to docsite/latest/conf.py index 47b17a362f19c3..6c6aff2d655065 100644 --- a/docsite/conf.py +++ b/docsite/latest/conf.py @@ -25,7 +25,7 @@ sys.path.append(os.path.abspath('_themes')) VERSION='0.01' -AUTHOR='Michael DeHaan' +AUTHOR='AnsibleWorks' # General configuration @@ -50,8 +50,8 @@ master_doc = 'index' # General substitutions. -project = 'Ansible Documentation' -copyright = "2012 Michael DeHaan" +project = 'Ansible 1.2 Documentation' +copyright = "2013 AnsibleWorks" # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. @@ -101,7 +101,7 @@ # ----------------------- html_theme_path = ['_themes'] -html_theme = 'bootstrap' +html_theme = 'aworks' html_short_title = 'Ansible Documentation' # The style sheet to use for HTML and HTML Help pages. A file of that name @@ -111,7 +111,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = 'Ansible Documentation' +html_title = 'Ansible 1.2 Documentation' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None @@ -123,7 +123,7 @@ # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = 'favicon.ico' +#html_favicon = 'favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -182,7 +182,7 @@ # (source start file, target name, title, author, document class # [howto/manual]). latex_documents = [ - ('index', 'ansible.tex', 'Ansible Documentation', + ('index', 'ansible.tex', 'Ansible 1.2 Documentation', AUTHOR, 'manual'), ] diff --git a/docsite/github.png b/docsite/latest/github.png similarity index 100% rename from docsite/github.png rename to docsite/latest/github.png diff --git a/docsite/latest/js/ansible/application.js b/docsite/latest/js/ansible/application.js new file mode 100644 index 00000000000000..5e9f81ba526336 --- /dev/null +++ b/docsite/latest/js/ansible/application.js @@ -0,0 +1,106 @@ +angular.module('ansibleApp', []).filter('moduleVersion', function() { + return function(modules, version) { + + var parseVersionString = function (str) { + if (typeof(str) != 'string') { return false; } + var x = str.split('.'); + // parse from string or default to 0 if can't parse + var maj = parseInt(x[0]) || 0; + var min = parseInt(x[1]) || 0; + var pat = parseInt(x[2]) || 0; + return { + major: maj, + minor: min, + patch: pat + } + } + + var vMinMet = function(vmin, vcurrent) { + minimum = parseVersionString(vmin); + running = parseVersionString(vcurrent); + if (running.major != minimum.major) + return (running.major > minimum.major); + else { + if (running.minor != minimum.minor) + return (running.minor > minimum.minor); + else { + if (running.patch != minimum.patch) + return (running.patch > minimum.patch); + else + return true; + } + } + }; + + var result = []; + if (!version) { + return modules; + } + for (var i = 0; i < modules.length; i++) { + if (vMinMet(modules[i].version_added, version)) { + result[result.length] = modules[i]; + } + } + + return result; + }; +}).filter('uniqueVersion', function() { + return function(modules) { + var result = []; + var inArray = function (needle, haystack) { + var length = haystack.length; + for(var i = 0; i < length; i++) { + if(haystack[i] == needle) return true; + } + return false; + } + + var parseVersionString = function (str) { + if (typeof(str) != 'string') { return false; } + var x = str.split('.'); + // parse from string or default to 0 if can't parse + var maj = parseInt(x[0]) || 0; + var min = parseInt(x[1]) || 0; + var pat = parseInt(x[2]) || 0; + return { + major: maj, + minor: min, + patch: pat + } + } + + for (var i = 0; i < modules.length; i++) { + if (!inArray(modules[i].version_added, result)) { + // Some module do not define version + if (modules[i].version_added) { + result[result.length] = "" + modules[i].version_added; + } + } + } + + result.sort( + function (a, b) { + ao = parseVersionString(a); + bo = parseVersionString(b); + if (ao.major == bo.major) { + if (ao.minor == bo.minor) { + if (ao.patch == bo.patch) { + return 0; + } + else { + return (ao.patch > bo.patch) ? 1 : -1; + } + } + else { + return (ao.minor > bo.minor) ? 1 : -1; + } + } + else { + return (ao.major > bo.major) ? 1 : -1; + } + }); + + return result; + }; +}); + diff --git a/docsite/man/ansible-playbook.1.html b/docsite/latest/man/ansible-playbook.1.html similarity index 100% rename from docsite/man/ansible-playbook.1.html rename to docsite/latest/man/ansible-playbook.1.html diff --git a/docsite/man/ansible.1.html b/docsite/latest/man/ansible.1.html similarity index 100% rename from docsite/man/ansible.1.html rename to docsite/latest/man/ansible.1.html diff --git a/docsite/latest/modules.js b/docsite/latest/modules.js new file mode 100644 index 00000000000000..103bc2cadb44b6 --- /dev/null +++ b/docsite/latest/modules.js @@ -0,0 +1,5 @@ +function AnsibleModules($scope) { + $scope.modules = []; + + $scope.orderProp = "module"; +} \ No newline at end of file diff --git a/docsite/rst/YAMLSyntax.rst b/docsite/latest/rst/YAMLSyntax.rst similarity index 94% rename from docsite/rst/YAMLSyntax.rst rename to docsite/latest/rst/YAMLSyntax.rst index b8fb861cbbccf2..ce15390bd3df9e 100644 --- a/docsite/rst/YAMLSyntax.rst +++ b/docsite/latest/rst/YAMLSyntax.rst @@ -95,6 +95,12 @@ You will want to quote any hash values using colons, like so: And then the colon will be preserved. +Further, Ansible uses "{{ var }}" for variables. If a value after a colon starts +with a "{", YAML will think it a dictionary, so you must quote it, like so:: + + foo: "{{ variable }}" + + .. seealso:: :doc:`playbooks` diff --git a/docsite/latest/rst/amazon_web_services.rst b/docsite/latest/rst/amazon_web_services.rst new file mode 100644 index 00000000000000..67a272790ea3ff --- /dev/null +++ b/docsite/latest/rst/amazon_web_services.rst @@ -0,0 +1,159 @@ +Amazon Web Services +=================== + +.. contents:: + :depth: 2 + :backlinks: top + +Introduction +```````````` + +Ansible contains a number of core modules for interacting with Amazon Web Services (AWS) and other API compatible private clouds, such as Eucalyptus. (There are other supported cloud systems, but this documentation is about AWS ones). This page illustrates some use cases for provisioning and managing hosts EC2 resources. + +Requirements for the AWS modules are minimal. All of the modules require and are tested against boto 2.5 or higher. You'll need this Python module installed on the execution host. If you are using Red Hat Enterprise Linux or CentOS, install boto from `EPEL `_: + +.. code-block:: bash + + $ yum install python-boto + +Provisioning +```````````` + +The ec2 module provides the ability to provision instance(s) within EC2. Typically the provisioning task will be performed against your Ansible master server as a local_action. + +.. note:: + + Authentication with the AWS-related modules is handled by either + specifying your access and secret key as ENV variables or passing + them as module arguments. + +.. note:: + + To talk to specific endpoints, the environmental variable EC2_URL + can be set. This is useful if using a private cloud like Eucalyptus, + exporting the variable as EC2_URL=https://myhost:8773/services/Eucalyptus. + This can be set using the 'environment' keyword in Ansible if you like. + +Provisioning a number of instances in ad-hoc mode mode: + +.. code-block:: bash + + # ansible localhost -m ec2 -a "image=ami-6e649707 instance_type=m1.large keypair=mykey group=webservers wait=yes" + +In a play, this might look like (assuming the parameters are held as vars):: + + tasks: + - name: Provision a set of instances + local_action: ec2 keypair={{mykeypair}} group={{security_group}} instance_type={{instance_type}} image={{image}} wait=true count={{number}} + register: ec2 + +By registering the return its then possible to dynamically create a host group consisting of these new instances. This facilitates performing configuration actions on the hosts immediately in a subsequent play:: + + tasks: + - name: Add all instance public IPs to host group + local_action: add_host hostname={{ item.public_ip }} groupname=ec2hosts + with_items: ec2.instances + +With the host group now created, the second play in your provision playbook might now have some configuration steps:: + + - name: Configuration play + hosts: ec2hosts + user: ec2-user + gather_facts: true + + tasks: + - name: Check NTP service + action: service name=ntpd state=started + +The method above ties the configuration of a host with the provisioning step. This isn't always ideal and leads us onto the next section. + +Advanced Usage +`````````````` + +Host Inventory +++++++++++++++ + +Once your nodes are spun up, you'll probably want to talk to them again. The best way to handle his is to use the ec2 inventory plugin. + +Even for larger environments, you might have nodes spun up from Cloud Formations or other tooling. You don't have to use Ansible to spin up guests. Once these are created and you wish to configure them, the EC2 API can be used to return system grouping with the help of the EC2 inventory script. This script can be used to group resources by their security group or tags. Tagging is highly recommended in EC2 and can provide an easy way to sort between host groups and roles. The inventory script is documented `in the API chapter `_. + +You may wish to schedule a regular refresh of the inventory cache to accomodate for frequent changes in resources: + +.. code-block:: bash + + # ./ec2.py --refresh-cache + +Put this into a crontab as appropriate to make calls from your Ansible master server to the EC2 API endpoints and gather host information. The aim is to keep the view of hosts as up-to-date as possible, so schedule accordingly. Playbook calls could then also be scheduled to act on the refreshed hosts inventory after each refresh. This approach means that machine images can remain "raw", containing no payload and OS-only. Configuration of the workload is handled entirely by Ansible. + +Pull Configuration +++++++++++++++++++ + +For some the delay between refreshing host information and acting on that host information (i.e. running Ansible tasks against the hosts) may be too long. This may be the case in such scenarios where EC2 AutoScaling is being used to scale the number of instances as a result of a particular event. Such an event may require that hosts come online and are configured as soon as possible (even a 1 minute delay may be undesirable). Its possible to pre-bake machine images which contain the necessary ansible-pull script and components to pull and run a playbook via git. The machine images could be configured to run ansible-pull upon boot as part of the bootstrapping procedure. + +More information on pull-mode playbooks can be found `here `_. + +(Various developments around Ansible are also going to make this easier in the near future. Stay tuned!) + +Use Cases +````````` + +This section covers some usage examples built around a specific use case. + +Example 1 ++++++++++ + + Example 1: I'm using CloudFormation to deploy a specific infrastructure stack. I'd like to manage configuration of the instances with Ansible. + +Provision instances with your tool of choice and consider using the inventory plugin to group hosts based on particular tags or security group. Consider tagging instances you wish to managed with Ansible with a suitably unique key=value tag. + +Example 2 ++++++++++ + + Example 2: I'm using AutoScaling to dynamically scale up and scale down the number of instances. This means the number of hosts is constantly fluctuating but I'm letting EC2 automatically handle the provisioning of these instances. I don't want to fully bake a machine image, I'd like to use Ansible to configure the hosts. + +There are two approaches to this use case. The first is to use the inventory plugin to regularly refresh host information and then target hosts based on the latest inventory data. The second is to use ansible-pull triggered by a user-data script (specified in the launch configuration) which would then mean that each instance would fetch Ansible and the latest playbook from a git repository and run locally to configure itself. + +Example 3 ++++++++++ + + Example 3: I don't want to use Ansible to manage my instances but I'd like to consider using Ansible to build my fully-baked machine images. + +There's nothing to stop you doing this. If you like working with Ansible's playbook format then writing a playbook to create an image; create an image file with dd, give it a filesystem and then install packages and finally chroot into it for further configuration. Ansible has the 'chroot' plugin for this purpose, just add the following to your inventory file:: + + /chroot/path ansible_connection=chroot + +And in your playbook:: + + hosts: /chroot/path + +Pending Information +``````````````````` + +In the future look here for more topics. + +Using Ansible's S3 module ++++++++++++++++++++++++++ + +these modules are documented on the module page, more walk throughs coming soon + +Using Ansible's Elastic Load Balancer Support ++++++++++++++++++++++++++++++++++++++++++++++ + +these modules are documented on the module page, more walk throughs coming soon + +Using Ansible's Cloud Formation Module +++++++++++++++++++++++++++++++++++++++ + +these modules are documented on the module page, more walk throughs coming soon + +.. seealso:: + + :doc:`examples` + Examples of basic commands + :doc:`playbooks` + Learning ansible's configuration management language + `Mailing List `_ + Questions? Help? Ideas? Stop by the list on Google Groups + `irc.freenode.net `_ + #ansible IRC chat channel + diff --git a/docsite/rst/api.rst b/docsite/latest/rst/api.rst similarity index 91% rename from docsite/rst/api.rst rename to docsite/latest/rst/api.rst index e3ea52310d883d..1a7b806f31038c 100644 --- a/docsite/rst/api.rst +++ b/docsite/latest/rst/api.rst @@ -35,7 +35,7 @@ expressed in the 'ansible-modules' documentation.:: { "dark" : { "web1.example.com" : "failure message" - } + }, "contacted" : { "web2.example.com" : 1 } @@ -105,22 +105,22 @@ Each group's value should be either a hash/dictionary containing a list of each simply a list of host/IP addresses, like so:: { - 'databases' : { - 'hosts' : [ 'host1.example.com', 'host2.example.com' ], - 'vars' : { - 'a' : true + "databases" : { + "hosts" : [ "host1.example.com", "host2.example.com" ], + "vars" : { + "a" : true } }, - 'webservers' : [ 'host2.example.com', 'host3.example.com' ], - 'atlanta' : { - 'hosts' : [ 'host1.example.com', 'host4.example.com', 'host5.example.com' ], - 'vars' : { - 'b' : false + "webservers" : [ "host2.example.com", "host3.example.com" ], + "atlanta" : { + "hosts" : [ "host1.example.com", "host4.example.com", "host5.example.com" ], + "vars" : { + "b" : false }, - 'children': [ 'marietta', '5points' ], + "children": [ "marietta", "5points" ], }, - 'marietta' : [ 'host6.example.com' ], - '5points' : [ 'host7.example.com' ] + "marietta" : [ "host6.example.com" ], + "5points" : [ "host7.example.com" ] } .. versionadded: 1.0 @@ -132,9 +132,9 @@ hash/dictionary, or a hash/dictionary of variables to make available to template if the script does not wish to do this, returning an empty hash/dictionary is the way to go:: { - 'favcolor' : 'red', - 'ntpserver' : 'wolf.example.com', - 'monitoring' : 'pack.example.com' + "favcolor" : "red", + "ntpserver" : "wolf.example.com", + "monitoring" : "pack.example.com" } Example: The Cobbler External Inventory Script @@ -202,7 +202,7 @@ You can use this script in one of two ways. The easiest is to use Ansible's ``-i ansible -i ec2.py -u ubuntu us-east-1d -m ping -The second option is to copy the script to `/etc/ansible/hosts` and `chmod +x` it. You will also need to copy the ``ec2.ini`` file to `/etc/ansible/ec2.ini`. Then you can run ansible as you would normally. +The second option is to copy the script to `/etc/ansible/hosts` and `chmod +x` it. You will also need to copy the `ec2.ini `_ file to `/etc/ansible/ec2.ini`. Then you can run ansible as you would normally. To successfully make an API call to AWS, you will need to configure Boto (the Python interface to AWS). There are a `variety of methods `_ available, but the simplest is just to export two environment variables: @@ -301,7 +301,8 @@ To see the complete list of variables available for an instance, run the script Example: OpenStack Inventory Script ``````````````````````````````````` -Though not detailed here in as much depth as the EC2 module, there's also a OpenStack Nova external inventory source in the plugins directory. See the inline comments in the module source for how to use it. +Though not detailed here in as much depth as the EC2 module, there's also a OpenStack Compute external inventory source in the plugins directory. It requires the Grizzly release of OpenStack or +later. See the inline comments in the module source for how to use it. Callback Plugins ---------------- @@ -312,12 +313,12 @@ system, or even (yes, really) making sound effects. Some examples are contained Connection Type Plugins ----------------------- -By default, ansible ships with a 'paramiko' SSH, native ssh (just called 'ssh'), and 'local' connection type. Release 0.8 also -added an accelerated connection type named 'fireball'. All of these can be used +By default, ansible ships with a 'paramiko' SSH, native ssh (just called 'ssh'), and 'local' connection type, and an accelerated connection type named 'fireball'. All of these can be used in playbooks and with /usr/bin/ansible to decide how you want to talk to remote machines. The basics of these connection types are covered in the 'getting started' section. Should you want to extend Ansible to support other transports (SNMP? Message bus? Carrier Pigeon?) it's as simple as copying the format of one of the existing modules and dropping it into the connection plugins -directory. +directory. The value of 'smart' for a connection allows selection of paramiko or openssh based on system capabilities, and chooses +'ssh' if OpenSSH supports ControlPersist, in Ansible 1.2.1 an later. Previous versions did not support 'smart'. Lookup Plugins -------------- diff --git a/docsite/latest/rst/bestpractices.rst b/docsite/latest/rst/bestpractices.rst new file mode 100644 index 00000000000000..caff4c94062a44 --- /dev/null +++ b/docsite/latest/rst/bestpractices.rst @@ -0,0 +1,347 @@ +Best Practices +============== + +Here are some tips for making the most of Ansible. + +You can find some example playbooks illustrating these best practices in our `ansible-examples repository `_. (NOTE: These may not use all of the features in the latest release just yet). + +.. contents:: + :depth: 2 + :backlinks: top + +Content Organization +++++++++++++++++++++++ + +The following section shows one of many possible ways to organize content. Your usage of Ansible should fit your needs, +so feel free to modify this approach and organize as you see fit. + +(One thing you will definitely want to do though, is use the "roles" organization feature, which is documented as part +of the main playbooks page) + +Directory Layout +```````````````` + +The top level of the directory would contain files and directories like so:: + + production # inventory file for production servers + stage # inventory file for stage environment + + group_vars/ + group1 # here we assign variables to particular groups + group2 # "" + host_vars/ + hostname1 # if systems need specific variables, put them here + hostname2 # "" + + site.yml # master playbook + webservers.yml # playbook for webserver tier + dbservers.yml # playbook for dbserver tier + + roles/ + common/ # this hierarchy represents a "role" + tasks/ # + main.yml # <-- tasks file can include smaller files if warranted + handlers/ # + main.yml # <-- handlers file + templates/ # <-- files for use with the template resource + ntp.conf.j2 # <------- templates end in .j2 + files/ # + bar.txt # <-- files for use with the copy resource + foo.sh # <-- script files for use with the script resource + + webtier/ # same kind of structure as "common" was above, done for the webtier role + monitoring/ # "" + fooapp/ # "" + +How to Arrange Inventory, Stage vs Production +````````````````````````````````````````````` + +In this example, the *production* file contains the inventory of all of your production hosts. Of course you can pull inventory from an external +data source as well, but this is just a basic example. Define groups based on purpose of the host (roles) and also geography or datacenter location:: + + # file: production + + [atlanta-webservers] + www-atl-1.example.com + www-atl-2.example.com + + [boston-webservers] + www-bos-1.example.com + www-bos-2.example.com + + [atlanta-dbservers] + db-atl-1.example.com + db-atl-2.example.com + + [boston-dbservers] + db-bos-1.example.com + + # webservers in all geos + [webservers:children] + atlanta-webservers + boston-webservers + + # dbservers in all geos + [dbservers:children] + atlanta-dbservers + boston-dbservers + + # everything in the atlanta geo + [atlanta:children] + atlanta-webservers + atlanta-dbservers + + # everything in the boston geo + [boston:children] + boston-webservers + boston-dbservers + +Group And Host Variables +```````````````````````` + +Now, groups are nice for organization, but that's not all groups are good for. You can also assign variables to them! For instance, atlanta +has it's own NTP servers, so when settings up ntp.conf, we should use them. Let's set those now:: + + --- + # file: group_vars/atlanta + ntp: ntp-atlanta.example.com + backup: backup-atlanta.example.com + +Variables aren't just for geographic information either! Maybe the webservers have some configuration that doesn't make sense for the database +servers:: + + --- + # file: group_vars/webservers + apacheMaxRequestsPerChild: 3000 + apacheMaxClients: 900 + +If we had any default values, or values that were universally true, we would put them in a file called group_vars/all:: + + --- + # file: group_vars/all + ntp: ntp-boston.example.com + backup: backup-boston.example.com + +We can define specific hardware variance in systems in a host_vars file, but avoid doing this unless you need to:: + + --- + # file: host_vars/db-bos-1.example.com + foo_agent_port: 86 + bar_agent_port: 99 + +Top Level Playbooks Are Separated By Role +````````````````````````````````````````` + +In site.yml, we include a playbook that defines our entire infrastructure. Note this is SUPER short, because it's just including +some other playbooks. Remember playbooks are nothing more than lists of plays:: + + --- + # file: site.yml + - include: webservers.yml + - include: dbservers.yml + +In a file like webservers.yml (also at the top level), we simply map the configuration of the webservers group to the roles performed by the webservers group. Also notice this is incredibly short. For example:: + + --- + # file: webservers.yml + - hosts: webservers + roles: + - common + - webtier + +Task And Handler Organization For A Role +```````````````````````````````````````` + +Below is an example tasks file, that explains how a role works. Our common role here just sets up NTP, but it could do more if we wanted:: + + --- + # file: roles/common/tasks/main.yml + + - name: be sure ntp is installed + yum: pkg=ntp state=installed + tags: ntp + + - name: be sure ntp is configured + template: src=ntp.conf.j2 dest=/etc/ntp.conf + notify: + - restart ntpd + tags: ntp + + - name: be sure ntpd is running and enabled + service: name=ntpd state=running enabled=yes + tags: ntp + +Here is an example handlers file. As a review, handlers are only fired when certain tasks report changes, and are run at the end +of each play:: + + --- + # file: roles/common/handlers/main.yml + - name: restart ntpd + service: name=ntpd state=restarted + +What This Organization Enables (Examples) +````````````````````````````````````````` + +So that's our basic organizational structure. + +Now what sort of use cases does this layout enable? Lots! If I want to reconfigure my whole infrastructure, it's just:: + + ansible-playbook -i production site.yml + +What about just reconfiguring NTP on everything? Easy.:: + + ansible-playbook -i production site.yml --tags ntp + +What about just reconfiguring my webservers?:: + + ansible-playbook -i production webservers.yml + +What about just my webservers in Boston?:: + + ansible-playbook -i production webservers.yml --limit boston + +What about just the first 10, and then the next 10?:: + + ansible-playbook -i production webservers.yml --limit boston[0-10] + ansible-playbook -i production webservers.yml --limit boston[10-20] + +And of course just basic ad-hoc stuff is also possible.:: + + ansible -i production -m ping + ansible -i production -m command -a '/sbin/reboot' --limit boston + +And there are some useful commands (at least in 1.1 to know):: + + # confirm what task names would be run if I ran this command and said "just ntp tasks" + ansible-playbook -i production webservers.yml --tags ntp --list-tasks + + # confirm what hostnames might be communicated with if I said "limit to boston" + ansible-playbook -i production webservers.yml --limit boston --list-hosts + +Deployment vs Configuration Organization +```````````````````````````````````````` + +The above setup models a typical OS configuration topology. When doing multi-tier deployments, there are going +to be some additional playbooks that hop between tiers to roll out an application. In this case, 'site.yml' +may be augmented by playbooks like 'deploy_exampledotcom.yml' but the general concepts can still apply. + +Ansible allows you to deploy and configure using the same tool, so you would likely reuse groups and just +keep the OS configuration in separate playbooks from the app deployment. + +Stage vs Production ++++++++++++++++++++ + +As also mentioned above, a good way to keep your stage (or testing) and production environments separate is to use a separate inventory file for stage and production. This way you pick with -i what you are targetting. Keeping them all in one file can lead to surprises! + +Testing things in a stage environment before trying in production is always a great idea. Your environments need not be the same +size and you can use group variables to control the differences between those environments. + +Rolling Updates ++++++++++++++++ + +Understand the 'serial' keyword. If updating a webserver farm you really want to use it to control how many machines you are +updating at once in the batch. + +Always Mention The State +++++++++++++++++++++++++ + +The 'state' parameter is optional to a lot of modules. Whether 'state=present' or 'state=absent', it's always best to leave that +parameter in your playbooks to make it clear, especially as some modules support additional states. + +Group By Roles +++++++++++++++ + +A system can be in multiple groups. See :doc:`patterns`. Having groups named after things like +*webservers* and *dbservers* is repeated in the examples because it's a very powerful concept. + +This allows playbooks to target machines based on role, as well as to assign role specific variables +using the group variable system. + +Operating System and Distribution Variance +++++++++++++++++++++++++++++++++++++++++++ + +When dealing with a parameter that is different between two different operating systems, the best way to handle this is +by using the group_by module. + +This makes a dynamic group of hosts matching certain criteria, even if that group is not defined in the inventory file:: + + --- + + # talk to all hosts just so we can learn about them + + - hosts: all + tasks: + - group_by: key=${ansible_distribution} + + # now just on the CentOS hosts... + + - hosts: CentOS + gather_facts: False + tasks: + - # tasks that only happen on CentOS go here + +If group specific settings are needed, this can also be done, for example:: + + --- + # file: group_vars/all + asdf: 10 + + --- + # file: group_vars/CentOS + asdf: 42 + +In the above example, CentOS machines get the value of '42' for asdf, but other machines get 10. + + +Bundling Ansible Modules With Playbooks ++++++++++++++++++++++++++++++++++++++++ + +.. versionadded:: 0.5 + +If a playbook has a "./library" directory relative to it's YAML file, this directory can be used to add ansible modules that will +automatically be in the ansible module path. This is a great way to keep modules that go with a playbook together. + +Whitespace and Comments ++++++++++++++++++++++++ + +Generous use of whitespace to break things up, and use of comments (which start with '#'), is encouraged. + +Always Name Tasks ++++++++++++++++++ + +It is possible to leave off the 'name' for a given task, though it is recommended to provide a description +about why something is being done instead. This name is shown when the playbook is run. + +Keep It Simple +++++++++++++++ + +When you can do something simply, do something simply. Do not reach +to use every feature of Ansible together, all at once. Use what works +for you. For example, you should probably not need 'vars', +'vars_files', 'vars_prompt' and '--extra-vars' all at once, +while also using an external inventory file. + +Version Control ++++++++++++++++ + +Use version control. Keep your playbooks and inventory file in git +(or another version control system), and commit when you make changes +to them. This way you have an audit trail describing when and why you +changed the rules automating your infrastructure. + +.. seealso:: + + :doc:`YAMLSyntax` + Learn about YAML syntax + :doc:`playbooks` + Review the basic playbook features + :doc:`modules` + Learn about available modules + :doc:`moduledev` + Learn how to extend Ansible by writing your own modules + :doc:`patterns` + Learn about how to select hosts + `Github examples directory `_ + Complete playbook files from the github project source + `Mailing List `_ + Questions? Help? Ideas? Stop by the list on Google Groups diff --git a/docsite/rst/contrib.rst b/docsite/latest/rst/contrib.rst similarity index 66% rename from docsite/rst/contrib.rst rename to docsite/latest/rst/contrib.rst index fe17caebcc12e4..5100a1c9782be6 100644 --- a/docsite/rst/contrib.rst +++ b/docsite/latest/rst/contrib.rst @@ -3,35 +3,45 @@ Ansible Resources User contributed playbooks, modules, and articles. This is a small curated list, but growing. Everyone is encouraged to add to this -document, just send in a github pull request to docsite/rst/contrib.rst! +document, just `edit it on Github `_ +and send a pull request! Ansible Modules ``````````````` Ansible modules are a way of adding new client-side logic to ansible. -They can be written in any language. +They can be written in any language. Generally our goal is to include most modules in core ("batteries included!"), +though a few may remain outside of core depending on use cases and implementations. -- `Official "core" ansible modules `_ - various -- `mercurial `_ - bradobro +- `Official "core" ansible modules `_ - various +- `Linode `_ - Lex Toumbourou - `zypper (bash module example) `_ - jp\_mens -- `homebrew `_ - swehack - `additional provisioning-related modules `_ - jhoekx and dagwieers - `dynamic dns updates `_ - jp\_mens -- `RabbitMQ `_ - elventear +- `apk-tools `_ - Bartłomiej Piotrowski -Python modules using 0.6 and later can and should use the common "AnsibleModule" -class to dramatically reduce the amount of boilerplate code required. -Not all modules above yet take advantage of this feature. See the -official documentation for more details. +All python modules (especially all submitted to core) should use the common "AnsibleModule" class to dramatically reduce the amount of boilerplate code required. + +Not all modules above may take advantage of this feature. See the official documentation for more details. Selected Playbooks `````````````````` -`Playbooks `_ are Ansible's +`Playbooks `_ are Ansible's configuration management language. It should be easy to write your own -from scratch for most applications, but it's always helpful to look at -what others have done for reference. +from scratch for most applications (we keep the language simple for EXACTLY that reason), but it can +be helpful to look at what others have done for reference and see what is possible. + +The ansible-examples repo on github contains some examples of best-practices Ansible content deploying some +full stack workloads: + +- `Ansible-Examples `_ + +And here are some other community-developed playbooks. Feel free to submit a pull request to the docs +to add your own. +- `edX Online `_ - `edX Online `_ +- `Fedora Infrastructure `_ - `Fedora `_ - `Hadoop `_ - jkleint - `LAMP `_ - `Four Kitchens `_ - `LEMP `_ - francisbesset @@ -39,7 +49,6 @@ what others have done for reference. - `Nginx `_ - cocoy - `OpenStack `_ - lorin - `Systems Configuration `_ - cegeddin -- `Fedora Infrastructure `_ - `Fedora `_ Callbacks and Plugins ````````````````````` @@ -50,6 +59,7 @@ storage. Talk to Cobbler and EC2, tweak the way things are logged, or even add sound effects. - `Ansible-Plugins `_ +- `Various modules, plugins, and scripts `_ sergevanginderachter Scripts And Misc ```````````````` @@ -65,8 +75,8 @@ integrations with other interesting pieces of software. - `uptime (API demo) `_ - mpdehaan - `vim snippet generator `_ - bleader -Blogs & Articles -```````````````` +Blogs, Videos & Articles +```````````````````````` - `HighScalability.com `_ - mpdehaan - `ColoAndCloud.com interview `_ - mpdehaan @@ -81,15 +91,18 @@ Blogs & Articles - `Deploying Flask/uWSGI, Nginx, and Supervisorctl `_ - mattupstate - `Infracoders Presentation `_ - Daniel Hall - `Ansible - an introduction `_ - jp\_mens +- `Using Ansible to setup complex networking - `_ - Robert Verspuy +- `Video presentation to Montreal Linux `_ - Alexandre Bourget +- `Provisioning CentOS EC2 Instances with Ansible `_ - jp\_mens Disclaimer `````````` Modules and playbooks here may not be using the latest in Ansible features. When in doubt to the features of a particular version of -Ansbile, always consult `ansible.cc `_ and in -particular see `Best Practices `_ for some tips -and tricks that may be useful. +Ansible, always consult `ansibleworks.com `_ and in +particular see `Best Practices `_ +for some tips and tricks that may be useful. Ansible is (C) 2012, `Michael DeHaan `_ and others and is available under the GPLv3 license. Content here is as diff --git a/docsite/rst/examples.rst b/docsite/latest/rst/examples.rst similarity index 90% rename from docsite/rst/examples.rst rename to docsite/latest/rst/examples.rst index ff6762f50fbcb7..6fa38a4352f13a 100644 --- a/docsite/rst/examples.rst +++ b/docsite/latest/rst/examples.rst @@ -121,22 +121,23 @@ with yum. Ensure a package is installed, but don't update it:: - $ ansible webservers -m yum -a "pkg=acme state=installed" + $ ansible webservers -m yum -a "name=acme state=installed" Ensure a package is installed to a specific version:: - $ ansible webservers -m yum -a "pkg=acme-1.5 state=installed" + $ ansible webservers -m yum -a "name=acme-1.5 state=installed" Ensure a package is at the latest version:: - $ ansible webservers -m yum -a "pkg=acme state=latest" + $ ansible webservers -m yum -a "name=acme state=latest" Ensure a package is not installed:: - $ ansible webservers -m yum -a "pkg=acme state=removed" + $ ansible webservers -m yum -a "name=acme state=removed" -Currently Ansible only has modules for managing packages with yum and apt. You can install -for other packages for now using the command module or (better!) contribute a module +Ansible has modules for managing packages under many platforms. If your package manager +does not have a module available for it, you can install +for other packages using the command module or (better!) contribute a module for other package managers. Stop by the mailing list for info/details. Users and Groups @@ -209,6 +210,17 @@ the remote nodes will be terminated. Typically you'll be only be backgrounding long-running shell commands or software upgrades only. Backgrounding the copy module does not do a background file transfer. :doc:`playbooks` also support polling, and have a simplified syntax for this. +Gathering Facts +``````````````` + +Facts are described in the playbooks section and represent discovered variables about a +system. These can be used to implement conditional execution of tasks but also just to get ad-hoc information about your system. You can see all facts via:: + + $ ansible all -m setup + +Its also possible to filter this output to just export certain facts, see the "setup" module documentation for details. + + Limiting Selected Hosts ``````````````````````` @@ -226,7 +238,7 @@ also works with ``ansible-playbook``:: $ ansible webservers:dbservers -m command -a "/bin/foo xyz" --limit region -Assuming version 0.9 or later, as with other host patterns, values to limit can be seperated with ";", ":", or ",". +Assuming version 0.9 or later, as with other host patterns, values to limit can be separated with ";", ":", or ",". Now let's talk about range selection. Suppose you have 1000 servers in group 'datacenter', but only want to target one at a time. This is also easy:: @@ -247,8 +259,8 @@ Ansible has an optional configuration file that can be used to tune settings and the first config file it finds present: 1. File specified by the ``ANSIBLE_CONFIG`` environment variable -2. ``ansible.cfg`` in the current working directory. (version 0.8 and up) -3. ``~/.ansible.cfg`` +2. ``~/.ansible.cfg`` +3. ``ansible.cfg`` in the current working directory. (version 0.8 and up) 4. ``/etc/ansible/ansible.cfg`` For those running from source, a sample configuration file lives in the examples/ directory. The RPM will install configuration into /etc/ansible/ansible.cfg automatically. diff --git a/docsite/latest/rst/faq.rst b/docsite/latest/rst/faq.rst new file mode 100644 index 00000000000000..a0304c35a8ddf4 --- /dev/null +++ b/docsite/latest/rst/faq.rst @@ -0,0 +1,178 @@ +Frequently Asked Questions +========================== + +Here are some commonly-asked questions and their answers. + +.. contents:: + :depth: 2 + :backlinks: top + +How do I handle different machines needing different user accounts or ports to log in with? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Setting inventory variables in the inventory file is the easiest way. + +For instance, suppose these hosts have different usernames and ports:: + + [webservers] + asdf.example.com ansible_ssh_port=5000 ansible_ssh_user=alice + jkl.example.com ansible_ssh_port=5001 ansible_ssh_user=bob + +You can also dictate the connection type to be used, if you want:: + + [testcluster] + localhost ansible_connection=local + /path/to/chroot1 ansible_connection=chroot + foo.example.com + bar.example.com + +You may also wish to keep these in group variables instead, or file in them in a group_vars/ file. +See the rest of the documentation for more information about how to organize variables. + + +How do I get ansible to reuse connections, enable Kerberized SSH, or have Ansible pay attention to my local SSH config file? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Switch your default connectiont type in the configuration file to 'ssh', or use '-c ssh' to use +Native OpenSSH for connections instead of the python paramiko library. In Ansible 1.2.1 and later, 'ssh' will be used +by default if OpenSSH is new enough to support ControlPersist as an option. + +Paramiko is great for starting out, but the OpenSSH type offers many advanced options. You will want to run Ansible +from a machine new enough to support ControlPersist, if you are using this connection type. You can still manage +older clients. If you are using RHEL 6, CentOS 6, SLES 10 or SLES 11 the version of OpenSSH is still a bit old, so +consider managing from a Fedora or openSUSE client even though you are managing older nodes, or just use paramiko. + +We keep paramiko as the default as if you are first installing Ansible on an EL box, it offers a better experience +for new users. + +How do I speed up management inside EC2? +++++++++++++++++++++++++++++++++++++++++ + +Don't try to manage a fleet of EC2 machines from your laptop. Connect to a management node inside EC2 first +and run Ansible from there. + +How do I handle python pathing not having a Python 2.X in /usr/bin/python on a remote machine? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +While you can write ansible modules in any language, most ansible modules are written in Python, and some of these +are important core ones. + +By default Ansible assumes it can find a /usr/bin/python on your remote system that is a 2.X version of Python, specifically +2.4 or higher. + +Setting of an inventory variable 'ansible_python_interpreter' on any host will allow Ansible to auto-replace the interpreter +used when executing python modules. Thus, you can point to any python you want on the system if /usr/bin/python on your +system does not point to a Python 2.X interpreter. + +Some Linux operating systems, such as Arch, may only have Python 3 installed by default. This is not sufficient and you will +get syntax errors trying to run modules with Python 3. Python 3 is essentially not the same +language as Python 2. Ansible modules currently need to support older Pythons for users that still have Enterprise Linux 5 deployed, so they are not yet ported to run under Python 3.0. This is not a problem though as you can just install Python 2 also on a managed host. + +Python 3.0 support will likely be addressed at a later point in time when usage becomes more mainstream. + +Do not replace the shebang lines of your python modules. Ansible will do this for you automatically at deploy time. + +What is the best way to make content reusable/redistributable? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you have not done so already, read all about "Roles" in the playbooks documentation. This helps you make playbook content +self contained, and works will with things like git submodules for sharing content with others. + +If some of these plugin types look strange to you, see the API documentation for more details about ways Ansible can be extended. + +Where does the configuration file live and what can I configure in it? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Technically ansible doesn't need a configuration file, but OS packages are likely to include a default one in /etc/ansible/ansible.cfg +that you may customize. You can also install your own copy in ~/.ansible.cfg or keep a copy in a directory relative to your playbook named "ansible.cfg". + +For what values you can use in this file, see `the configuration file on github `_. + +Generally you would configure the default module path or connection type here, among other things, though the defaults are usually +good enough for starting out. + +How do I disable cowsay? +++++++++++++++++++++++++ + +If cowsay is installed, Ansible takes it upon itself to make your day happier when running playbooks. If you decide +that you would like to work in a professional cow-free environment, you can either uninstall cowsay, or set an environment variable:: + + export ANSIBLE_NOCOWS=1 + +How do I see a list of all of the ansible\_ variables? +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Ansible by default gathers "facts" about the machines under management, and these facts can be accessed in Playbooks and in templates. To see a list of all of the facts that are available about a machine, you can run the "setup" module as an ad-hoc action:: + + ansible -m setup hostname + +This will print out a dictionary of all of the facts that are available for that particular host. + +How do I loop over a list of hosts in a group, inside of a template? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +A pretty common pattern is to iterate over a list of hosts inside of a host group, perhaps to populate a template configuration +file with a list of servers. To do this, you can just access the "$groups" dictionary in your template, like this:: + + {% for host in groups['db_servers'] %} + {{ host }} + {% endfor %} + +If you need to access facts about these hosts, for instance, the IP address of each hostname, you need to make sure that the facts have been populated. For example, make sure you have a play that talks to db_servers:: + + - hosts: db_servers + tasks: + - # doesn't matter what you do, just that they were talked to previously. + +Then you can use the facts inside your template, like this:: + + {% for host in groups['db_servers'] %} + {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} + {% endfor %} + +How do I copy files recursively onto a target host? ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The "copy" module doesn't handle recursive copies of directories. A common solution to do this is to use a local action to call 'rsync' to recursively copy files to the managed servers. + +Here is an example:: + + --- + # ... + tasks: + - name: recursively copy files from management server to target + local_action: command rsync -a /path/to/files $inventory_hostname:/path/to/target/ + +Note that you'll need passphrase-less SSH or ssh-agent set up to let rsync copy without prompting for a passphase or password. + +How do I access shell environment variables? +++++++++++++++++++++++++++++++++++++++++++++ + +If you just need to access existing variables, use the 'env' lookup plugin. For example, to access the value of the HOME +environment variable on management machine:: + + --- + # ... + vars: + local_home: "{{ lookup('env','HOME') }}" + +If you need to set environment variables, see the Advanced Playbooks section about environments. + +Can I get training on Ansible or find commerical support? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Yes! See `AnsibleWorks.com `_ or email `info@ansibleworks.com `_. + +How do I submit a change to the documentation? +++++++++++++++++++++++++++++++++++++++++++++++ + +Great question! Documentation for the Ansible project is kept in `Github `_ in restructured text format. Simply send in a pull request for changes, or file a ticket if you found an error but do not have time to submit +a change request. Thanks! + +I don't see my question here +++++++++++++++++++++++++++++ + +See the "Resources" section of the documentation for a link to the IRC and Google Group. + + + diff --git a/docsite/latest/rst/gettingstarted.rst b/docsite/latest/rst/gettingstarted.rst new file mode 100644 index 00000000000000..3991128e765a0f --- /dev/null +++ b/docsite/latest/rst/gettingstarted.rst @@ -0,0 +1,370 @@ +Getting Started +=============== + +.. contents:: + :depth: 2 + :backlinks: top + +Requirements +```````````` + +Requirements for Ansible are extremely minimal. + +For the central Ansible machine, you will need an environment with Python 2.6 or greater installed. If you are running Python 2.5 on an "Enterprise Linux" variant, we'll show you how to add 2.6 to your distribution. (Windows is not supported as the Ansible control machine.) + +You will also want the following Python modules (installed via pip or perhaps via your OS package manager via slightly different names): + +* ``paramiko`` +* ``PyYAML`` +* ``jinja2`` + +If you are using RHEL or CentOS 5, Python is version 2.4 by default, but you can get Python 2.6 installed easily. `Use EPEL `_ and install these dependencies as follows: + +.. code-block:: bash + + $ yum install python26 python26-PyYAML python26-paramiko python26-jinja2 + +On the managed nodes, you only need Python 2.4 or later, but if you are are running less than Python 2.6 on them, you will +also need: + +* ``python-simplejson`` + +.. note:: + + Ansible's "raw" module (for executing commands in a quick and dirty + way) and the script module don't even need that. So technically, you can use + Ansible to install python-simplejson using the raw module, which + then allows you to use everything else. (That's jumping ahead + though.) + +.. note:: + + Python 3 is a slightly different language than Python 2 and most python programs (including + Ansible) are not + switching over yet. However, some Linux distributions (Gentoo, Arch) may not have a + Python 2.X interpreter installed by default. On those systems, you should install one, and set + the 'ansible_python_interpreter' variable in inventory (see :doc:`patterns`) to point at your 2.X python. Distributions + like Red Hat Enterprise Linux, CentOS, Fedora, and Ubuntu all have a 2.X interpreter installed + by default and this does not apply to those distributions. This is also true of nearly all + Unix systems. If you need to bootstrap these remote systems by installing Python 2.X, + using the 'raw' module will be able to do it remotely. + +Getting Ansible +``````````````` + +If you are interested in using all the latest features, you may wish to keep up to date +with the development branch of the git checkout. This also makes it easiest to contribute +back to the project. + +Instructions for installing from source are below. + +Ansible's release cycles are usually about two months long. Due to this +short release cycle, minor bugs will generally be fixed in the next release versus maintaining +backports on the stable branch. + +You may also wish to follow the `Github project `_ if +you have a github account. This is also where we keep the issue tracker for sharing +bugs and feature ideas. + + +Running From Checkout ++++++++++++++++++++++ + +Ansible is trivially easy to run from a checkout, root permissions are not required +to use it: + +.. code-block:: bash + + $ git clone git://github.com/ansible/ansible.git + $ cd ./ansible + $ source ./hacking/env-setup + +You can optionally specify an inventory file (see :doc:`patterns`) other than /etc/ansible/hosts: + +.. code-block:: bash + + $ echo "127.0.0.1" > ~/ansible_hosts + $ export ANSIBLE_HOSTS=~/ansible_hosts + +You can read more about the inventory file in later parts of the manual. + +Now let's test things: + +.. code-block:: bash + + $ ansible all -m ping --ask-pass + + +Make Install +++++++++++++ + +If you are not working from a distribution where Ansible is packaged yet, you can install Ansible +using "make install". This is done through `python-distutils`: + +.. code-block:: bash + + $ git clone git://github.com/ansible/ansible.git + $ cd ./ansible + $ sudo make install + +Via Pip ++++++++ + +Are you a python developer? + +Ansible can be installed via Pip, but when you do so, it will ask to install other dependencies used for +things like 'fireball' mode that you might not need:: + + $ sudo easy_install pip + $ sudo pip install ansible + +Readers that use virtualenv can also install Ansible under virtualenv. Do not use easy_install to install +ansible directly. + +Via RPM ++++++++ + +RPMs for the last Ansible release are available for `EPEL +`_ 6 and currently supported +Fedora distributions. RPMs for openSUSE can be found via the +`openSUSE Software Portal `_ +(in the systemsmanagement Project) for all currently supported +openSUSE and SLES distributions. + +Ansible itself can manage earlier operating +systems that contain python 2.4 or higher. + +If you are using RHEL or CentOS and have not already done so, `configure EPEL `_ + +.. code-block:: bash + + # install the epel-release RPM if needed on CentOS, RHEL, or Scientific Linux + $ sudo yum install ansible + +For openSUSE and SUSE Linux Enterprise, add the `systemsmanagement repository `_ +for your distribution: + +.. code-block:: bash + + # replace $dist with the correct distribution found here: http://download.opensuse.org/repositories/systemsmanagement/ + $ sudo zypper ar -f http://download.opensuse.org/repositories/systemsmanagement/$dist/systemsmanagement.repo + $ sudo zypper install ansible + +You can also use the ``make rpm`` command to build an RPM you can distribute and install. +Make sure you have ``rpm-build``, ``make``, and ``python2-devel`` installed. + +.. code-block:: bash + + $ git clone git://github.com/ansible/ansible.git + $ cd ./ansible + $ make rpm + $ sudo rpm -Uvh ~/rpmbuild/ansible-*.noarch.rpm + +Via MacPorts +++++++++++++ + +An OSX port is available via MacPorts, to install the stable version of +Ansible from MacPorts (this is the recommended way), run: + +.. code-block:: bash + + $ sudo port install ansible + +If you wish to install the latest build via the MacPorts system from a +git checkout, run: + +.. code-block:: bash + + $ git clone git://github.com/ansible/ansible.git + $ cd ./ansible/packaging/macports + $ sudo port install + +Please refer to the documentation at for +further information on using Portfiles with MacPorts. + + +Ubuntu and Debian ++++++++++++++++++ + +Ubuntu builds are available `in a PPA here `_. + +In Ubuntu 13.04 (raring) its part of the backports repository: + +.. code-block:: bash + + $ sudo apt-get install ansible/raring-backports + +In Debian testing/unstable and Ubntu 13.10+ it is available via + +.. code-block:: bash + + $ sudo apt-get install ansible + +Debian/Ubuntu package recipes can also be built from the source checkout, run: + +.. code-block:: bash + + $ make debian + +Gentoo, Arch, Others +++++++++++++++++++++ + +Gentoo eBuilds are in portage, version 1.0 `coming soon `_. + +.. code-block:: bash + + $ emerge ansible + + +An Arch PKGBUILD is available on `AUR `_ +If you have python3 installed on Arch, you probably want to symlink python to python2: + +.. code-block:: bash + + $ sudo ln -sf /usr/bin/python2 /usr/bin/python + +You should also set a 'ansible_python_interpreter' inventory variable (see :doc:`patterns`) for hosts that have python +pointing to python3, so the right python can be found on the managed nodes. + +Tagged Releases ++++++++++++++++ + +Tarballs of releases are available on the ansibleworks.com page. + +* `Ansible/downloads `_ + +These releases are also tagged in the git repository with the release version. + +Choosing Between Paramiko and Native SSH +```````````````````````````````````````` + +By default, ansible uses paramiko to talk to managed nodes over SSH. Paramiko is fast, works +very transparently, requires no configuration, and is a good choice for most users. +However, it does not support some advanced SSH features that folks will want to use. + +.. versionadded:: 0.5 + +If you want to leverage more advanced SSH features (such as Kerberized +SSH or jump hosts), pass the flag "--connection=ssh" to any ansible +command, or set the ANSIBLE_TRANSPORT environment variable to +'ssh'. This will cause Ansible to use openssh tools instead. + +If ANSIBLE_SSH_ARGS are not set, ansible will try to use some sensible ControlMaster options +by default. You are free to override this environment variable, but should still pass ControlMaster +options to ensure performance of this transport. With ControlMaster in use, both transports +are roughly the same speed. Without CM, the binary ssh transport is signficantly slower. + +If none of this makes sense to you, the default paramiko option is probably fine. + + +Your first commands +``````````````````` + +Now that you've installed Ansible, it's time to test it. + +Edit (or create) /etc/ansible/hosts and put one or more remote systems in it, for +which you have your SSH key in ``authorized_keys``:: + + 192.168.1.50 + aserver.example.org + bserver.example.org + +Set up SSH agent to avoid retyping passwords: + +.. code-block:: bash + + $ ssh-agent bash + $ ssh-add ~/.ssh/id_rsa + +(Depending on your setup, you may wish to ansible's --private-key option to specify a pem file instead) + +Now ping all your nodes: + +.. code-block:: bash + + $ ansible all -m ping + +Ansible will attempt to remote connect to the machines using your current +user name, just like SSH would. To override the remote user name, just use the '-u' parameter. + +If you would like to access sudo mode, there are also flags to do that: + +.. code-block:: bash + + # as bruce + $ ansible all -m ping -u bruce + # as bruce, sudoing to root + $ ansible all -m ping -u bruce --sudo + # as bruce, sudoing to batman + $ ansible all -m ping -u bruce --sudo --sudo-user batman + +(The sudo implementation is changeable in ansible's configuration file if you happen to want to use a sudo +replacement. Flags passed dot sudo can also be set.) + +Now run a live command on all of your nodes: + +.. code-block:: bash + + $ ansible all -a "/bin/echo hello" + +Congratulations. You've just contacted your nodes with Ansible. It's +soon going to be time to read some of the more real-world :doc:`examples`, and explore +what you can do with different modules, as well as the Ansible +:doc:`playbooks` language. Ansible is not just about running commands, it +also has powerful configuration management and deployment features. There's more to +explore, but you already have a fully working infrastructure! + +A note about Connection (Transport) Modes +````````````````````````````````````````` + +Ansible has two major forms of SSH transport implemented, 'ssh' (OpenSSH) and 'paramiko'. Paramiko is a python +SSH implementation and 'ssh' simply calls OpenSSH behind the scenes. There are additionally 'fireball' (an accelerated +remote transport), 'local', and 'chroot' connection modes in Ansible that don't use SSH, but connecting by one of the two +SSH transports is the most common way to manage systems. It is useful to understand the difference between the 'ssh' +and 'paramiko' modes. + +Paramiko is provided because older Enterprise Linux operating systems do not have an efficient OpenSSH that support +ControlPersist technology, and in those cases, 'paramiko' is faster than 'ssh'. Thus, until EL6 backports a newer +SSH, 'paramiko' is the faster option on that platform. + +However, if you have a newer 'ssh' that supports ControlPersist, usage of the 'ssh' transport unlocks additional +configurability, including the option to use Kerberos. For instance, the latest Fedora and Ubuntu releases +all offer a sufficiently new OpenSSH. With ControlPersist available, 'ssh' is usually about as fast as paramiko. +If you'd like even more speed, read about 'fireball' in the Advanced Playbooks section. + +Starting with Ansible 1.2.1, the default transport mode for Ansible is 'smart', which means it will detect +if OpenSSH supports ControlPersist, and will select 'ssh' if available, and otherwise pick 'paramiko'. +Previous versions of Ansible defaulted to 'paramiko'. + +A note about Host Key Checking +`````````````````````````````` + +Ansible 1.2.1 and later have host key checking enabled by default. + +If a host is reinstalled and has a different key in 'known_hosts', this will result in a error message until +corrected. If a host is not initially in 'known_hosts' this will result in prompting for confirmation of the key, +which results in a interactive experience if using Ansible, from say, cron. + +If you wish to disable this behavior and understand the implications, you can do so by editing /etc/ansible/ansible.cfg or ~/.ansible.cfg:: + + [default] + host_key_checking = False + +Alternatively this can be set by an environment variable: + + $ export ANSIBLE_HOST_KEY_CHECKING=False + +Also note that host key checking in paramiko mode is reasonably slow, therefore switching to 'ssh' is also recommended when using this +feature. + +.. seealso:: + + :doc:`examples` + Examples of basic commands + :doc:`playbooks` + Learning ansible's configuration management language + `Mailing List `_ + Questions? Help? Ideas? Stop by the list on Google Groups + `irc.freenode.net `_ + #ansible IRC chat channel + diff --git a/docsite/latest/rst/glossary.rst b/docsite/latest/rst/glossary.rst new file mode 100644 index 00000000000000..e70eca3f25f8a9 --- /dev/null +++ b/docsite/latest/rst/glossary.rst @@ -0,0 +1,435 @@ +Glossary +======== + +The following is a list (and re-explanation) of term definitions used elsewhere in the Ansible documentation. + +Consult the documentation home page for the full documentation and to see this in context, but this should be a good resource +to check you know all of components of ansible and how they fit together. It's something you might wish to read for review or +when a term comes up on the mailing list. + +See the main documentation if you are looking for examples to put all of this into context. + +Action +++++++ + +An action is a part of ref:`task` that says what ref:`module` to run and what arguments to pass to that module. Each task can +have only one action, but it may also have other parameters. + +Ad Hoc +++++++ + +Refers to running ansible to do some quick command, using /usr/bin/ansible, rather than the orchestration language, which is +/usr/bin/ansible-playbook. An example of an ad-hoc command might be rebooting 50 machines in your infrastructure. Anything +you can do ad-hoc you can do by writing a playbook, and playbooks can also glue lots of other operations together. + +Async ++++++ + +Refers to a task that is configured to run in the background rather than waiting for completion. If you have a long process +that would run longer than the SSH timeout, it would make sense to launch that task in async mode. Async modes can poll +for completion every so many seconds, or can be configured to "fire and forget" in which case ansible will not even +check on the task again, it will just kick it off and proceed to future steps. Async modes work with both /usr/bin/ansible +and /usr/bin/ansible-playbook. + +Callback Plugin ++++++++++++++++ + +Refers to some user-written code that can intercept the results from Ansible and do something with it. Some supplied examples +in the github project perform custom logging, send email, or even play sound effects. + +Check Mode +++++++++++ + +Refers to running ansible with --check, which does not make any changes on the remote systems, but only alerts what changes +might occur if run without --check. This is analogous to so-called "dry run" mode in other systems, though the user should +be warned that this does not take into account unexpected command failures or cascade effects (nor do those modes in other +systems). Use this to get an idea what might happen, but is not a substitute for a good staging environment. + +Connection Type, Connection Plugin +++++++++++++++++++++++++++++++++++ + +Ansible by default talks to remote machines through pluggable libraries. Ansible supports native OpenSSH ('ssh'), or a python +implementation called 'paramiko'. OpenSSH is preferred if you have a new-enough open SSH, and also enables some features +like Kereberos and jump hosts. This is covered in the getting started section. +There are also other connection types like 'fireball' mode, which must be bootstrapped +over one of the SSH based types but is very fast, and local mode, which acts on the local system. +Users can also write their own connection plugins. + +Conditionals +++++++++++++ + +A conditional is an expression that evaluates to true or false that decides whether a given task will be executed on a given +machine or not. Ansible's conditionals include 'only_if', and the syntactically superior alternatives 'when_boolean', +'when_string', and 'when_integer'. These are discussed in the playbook documentation. + +Diff Mode ++++++++++ + +A --diff flag can be passed to ansible to show how template files change when they are overwritten, or how they might change when used +with --check mode. These diffs come out in unified diff format. + +Facts ++++++ + +Facts are simply things that are discovered about remote nodes. While they can be used in playbooks and templates just like variables, facts +are things that are inferred, rather than set. Facts are discovered automatically by ansible when running plays by running the internal 'setup' +module on the remote nodes. You never have to call the setup module explicitly, it just runs, but it can be disabled to save time if it is +not needed. For convience of users switching from other config systems, the fact module will also pull in facts from the 'ohai' and 'facter' +tools if they are installed, which are fact libraries from Chef and Puppet, respectfully. + +Filter Plugin ++++++++++++++ + +A filter plugin is something that most users will never need to understand to use at all. These allow creation of new Jinja2 filters, which +are more of less only of use to people who know what Jinja2 filters are. If you need them, you can learn how to write them in the API +docs section. + +Fireball Mode ++++++++++++++ + +By default Ansible uses SSH for connections -- either Paramiko or a common alternative, native Open SSH. (Ansible tries to use +'ssh' by default if possible in Ansible 1.2.1 and later, and before defaulted to Paramiko). Some users +may want to execute operations even faster though, and they can if they opt in on running an ephmeral message bus, 'fireball'. What happens is Ansible +will start talking to a node over SSH, and then set up a temporary secured message bus good only to talk from one machine, that will +self destruct after a set period of time. This means the bus does not allow management of any kind after the time interval has expired. + +Forks ++++++ + +Ansible talks to remote nodes in parallel, the level of parallelism can be set either by passing --forks, or editing the default in a configuration +file. The default is a very conservative 5 forks, though if you have a lot of RAM, you can easily set this to a value like 50 for increased +parallelism. + +Gather Facts (Boolean) +++++++++++++++++++++++ + +Facts are mentioned above. Sometimes in running a multi-play playbook it is deseriable to have some plays that don't bother with fact +computation as they aren't going to need any values from facts. Setting `gather_facts: False` on a playbook allows this implicit +fact gathering to be skipped. + +Globbing +++++++++ + +Globbing is a way to select lots of hosts based on wildcard, rather than the name of the host specifically, or the name of the group +they are in. For instance, it is possible to select "www*" to match all hosts starting with "www". This concept is pulled directly +from Func, one of Michael's earlier projects. In addition to basic globbing, various set operations are also possible, such as +hosts in this group and not in another group, and so on. + +Group ++++++ + +A group consists of several hosts assigned to a pool that can be targetted conviently together, and also given variables that they share in +common. + +Group Vars +++++++++++ + +The "group_vars/" files are files that live in a directory alongside an inventory file, with an optional filename named after each group. +This is a convient place to put variables that will be provided to a given group, especially complex datastructures, so that these +variables do not have to be embedded in the inventory file or playbook. + +Handlers +++++++++ + +Handlers are just like regular tasks in an ansible playbok (see Tasks), but are only run if the Task contains a "notify" directive and +also indicates that it changed something. An example is if a config file is changed, the task referencing the config file templating +operation may notify a service restart handler when it changes. This means services can be bounced only if they need to be restarted. +Handlers can be used for things other than service restarts, but service restarts are the most common usage. + +Host +++++ + +A host is simply a remote machine that ansible manages. They can have individual variables assigned to them, and can also be organized +in groups. All hosts have a name they can be reached at (which is either an IP address or a domain name) and optionally a port number, +if they are not to be accessed on the default SSH port. + +Host Specifier +++++++++++++++ + +Each Play in Ansible maps a series of tasks (which define the role, purpose, or orders of a system) to a set of systems. + +This "hosts:" directive in each play is often called the hosts specifier. + +It may select one system, many systems, one or more groups, or even some hosts that in one group and explicitly not in another. + +Host Vars ++++++++++ + +Just like "Group Vars", a directory alongside the inventory file named "host_vars/" can contain a file named after each hostname in +the inventory file, in YAML format. This provides a convient place to assign variables to the host without having to embed +them in the inventory file. The Host Vars file can also be used to define complex datastructures that can't be represented in the +inventory file. + +Lazy Evalution +++++++++++++++ + +In general Ansible evaluates any variables in playbook content at the last possible second, which means that if you define a datastructure +that datastructure itself can define variable values within it, and everything "just works" as you would expect. This also means variable +strings can include other variables inside of those strings. + +Lookup Plugin ++++++++++++++ + +A lookup plugin is a way to get data into Ansible from the outside world. These are how such things as "with_items" are implemented, which is a +basic looping plugin, but there are also things like "with_file" which loads data from a file, and even things for querying environment variables, +DNS text records, or key value stores. Lookup plugins can also be accessed in templates using an all caps form, such as the contents of a file +on the local machine can be accessed like ``$FILE(/path/to/file)``. + +Multi-Tier +++++++++++ + +The concept that IT systems are not managed one system at a time, but by interactions between multiple systems, and groups of systems, in +well defined orders. For instance, a web server may need to be updated before a database server, and pieces on the web server may need +to be updated after *THAT* database server, and various load balancers and monitoring servers may need to be contacted. Ansible models +entire IT topologies and workflows rather than looking at configuration in a "one system at a time" perspective. + +Idempotency ++++++++++++ + +The concept that change commands should only be applied when they need to be applied, and that it is better to describe the desired +state of a system than the process of how to get to that state. As an analogy, the path from North Carolina in the United States to +California involves driving a very long way West, but if I were instead in Anchorage, Alaska, driving a long ways west is no longer +the right way to get to California. Ansible's Resources like you to say "put me in California" and then decide how to get there. If +you were already in California, nothing needs to happen, and it will let you know it didn't need to change anything. + +Includes +++++++++ + +The idea that playbook files (which are nothing more than list of plays) can include other lists of plays, and task lists +can externalize lists of tasks in other files, and similarly with handlers. Includes can be parameterized, which means that the +loaded file can pass variables. For instance, an included play for setting up a wordpress blog may take a parameter called "user" +and thant play could be included more than once to create a blog for both "alice" and "bob". + +Inventory ++++++++++ + +A file (by default, Ansible uses a simple INI format) that describes Hosts and Groups in Ansible. Inventory can also be provided +via an "Inventory Script" (sometimes called an "External Inventory Script"). + +Inventory Script +++++++++++++++++ + +A very simple program (or a complicated one) that looks up hosts, group membership for hosts, and variable information from an external +resource -- whether that be a SQL database, a CMDB solution, or something like LDAP. This concept was adapted from Puppet (where it is +called an "External Nodes Classifier") and works more or less exactly the same way. + +Jinja2 +++++++ + +Jinja2 is the preferred templating language of Ansible's template module. It is a very simple Python template language that is generally +readable and easy to write. + +JSON +++++ + +Ansible uses JSON for return data from remote modules. This allows modules to be written in any language, not just Python. + +only_if ++++++++ + +A conditional statement that decides if a task is going to be executed in a playbook based on whether if the following expression +given is true or false. The newer 'when_' statements provide a cleaner way to express conditionals, 'only_if' is an older +construct. Though it may be still be useful in advanced situations. + +Library ++++++++ + +A collection of modules made availabe to /usr/bin/ansible or an ansible playbook. + +Limit Groups +++++++++++++ + +By passing "--limit somegroup" to ansible or ansible playbook, the commands can be limited to a subset of hosts. For instance, +this can be used to run a playbook that normally targets an entire set of servers to one particular server. + +Local Connection +++++++++++++++++ + +By using "connection: local" in a playbook, or "-c local" to /usr/bin/ansible, this indicates that we are managing the local +host and not a remote machine. + +Local Action +++++++++++++ + +A local_action directive in a playbook targetting remote machines means that the given step will actually occur on local +machine, but that the variable '$ansible_hostname' can be passed in to reference the remote hostname being referred to in +that step. This can be used to trigger, for example, an rsync operation. + +Loops ++++++ + +Generally Ansible is not a programming language, it prefers to be more declarative, though various constructs like "with_items" +allow a particular task to be repeated for multiple items in a list. Certain modules, like yum and apt, are actually optimized +for this, and can install all packages given in those lists within a single transaction, dramatically speaking up total +time to configuration. + +Modules ++++++++ + +Modules are the units of work that Ansible ships out to remote machines. Modules are kicked off by either /usr/bin/ansible or +/usr/bin/ansible-playbook (where multiple tasks use lots of different modules in conjunction). Modules can be implemented in any +language including Perl, Bash, or Ruby -- but can leverage some useful communal library code if written in Python. Modules just +have to return JSON or simple key=value pairs. Once modules are executed on remote machines, they are removed, so no long running +daemons are used. Ansible refers to the collection of available modules as a 'library'. + +Notify +++++++ + +The act of a task registering a change event and informing a handler task that another action needs to be run at the end of the play. +If a handler is notified by multiple tasks, it will still be run only once. Handlers are run in the order they are listed, not +in the order that they are notified. + +Orchestration ++++++++++++++ + +Many software automation systems use this word to mean different things. Ansible uses it as a conductor would conduct an orchestra. +A datacenter or cloud architecture is full of many systems, playing many parts -- web servers, database servers, maybe load balancers, +monitoring systems, continuous integration systems, etc. In performing any process, it is neccessary to touch systems in particular orders, +often to simulate rolling updates or deploy software correctly. Some system may perform some steps, then others, then previous systems +already processed may need to perform more steps. Along the way, email may need to be sent or web services contacted. Ansible +orchestration is all about modelling that kind of process. + +Paramiko +++++++++ + +Ansible by default manages machines over SSH. The library that ansible uses by default to do this is a python-powered library called +Paramiko. Paramiko is generally fast and easy to manage, though users desiring Kerberos or Jump Host support may wish to switch +to the native SSH connection type, by specifying the connection type in their playbook or using the "-c ssh" flag. + +Playbooks ++++++++++ + +Playbooks are the language by which Ansible orchestrates, configures, administers, or deploys systems. They are called playbooks partially because it's a sports analogy, and it's supposed to be fun using them. They aren't workbooks :) + +Plays ++++++ + +A playbook is a list of plays. A play is minimally a mapping between a set of hosts (usually chosen by groups, but sometimes my hostname +globs), selected by a host specifier -- and the tasks which run on those hosts to define the role at which those systems will perform. There +can be one or many plays in a playbook. + +Pull Mode ++++++++++ + +Ansible by default runs in push mode, which allows it very fine grained control over when it talks to what kinds of systems. Pull mode is +provided for when you would rather have nodes check in every N minutes on a particular schedule. It uses a program called ansible-pull and can also be set up (or reconfigured) using a push-mode playbook. Most ansible users use push mode, but it is included for variety and the sake +of having choices. + +ansible-pull works by checking configuration orders out of git on a crontab and then managing the machine locally, using the local +connection plugin. + +Push Mode ++++++++++ + +Push mode is the default mode of ansible, in fact, it's not really a mode at all -- it's just how ansible works when you aren't +thinking about it. Push mode allows ansible to be fine grained and conduct nodes in complex orchestration processes without +waiting for them to check in. + +Register Variable ++++++++++++++++++ + +The result of running any task in ansible can be stored in a variable for use in a template or a conditional statement. +The keyword used to name the variable to use is called 'register', taking it's name from the idea of registers in assembly +programming, though Ansible will never feel like assembly programming. There are an infinite number of variable names +you can use for registration. + +Resource Model +++++++++++++++ + +Ansible modules work in terms of resources. For instance the file module will select a particular file, say, /etc/motd +and ensure that attributes of that resource match a particular model, for instance, we might wish to set the ownership +to 'root' if not already set to root, or set the mode to '0644' if not already set to '0644'. The resource models +are 'idempotent' meaning change commands are not run unless needed, and ansible will bring the system back to a desired +state regardless of the actual state -- rather than you having to tell it how to get to the state. + +Roles ++++++ + +Roles are units of organization in ansible. Assigning a role to a group of hosts (or a set of groups, or host patterns, etc) implies that they should implement a specific behavior. A role +may include applying certain variable values, certain tasks, and certain handlers -- or just one or more of these things. Because of the file structure associated with a role, roles become +units of redistributablity for sharing behavior among playbooks -- or even with other users. + +Rolling Update +++++++++++++++ + +The act of addressing a number of nodes in a group N at a time to avoid updating them all at once and bringing the system +offline. For instance, in a web topology of 500 nodes handling very large volume, it may be reasonable to update 10 or 20 +machines at a time, moving on to the next 10 or 20 when done. The "serial:" keyword in an ansible playbook controls the +size of the rolling update pool. The default is to address the batch size all at once, so this is something that you must +opt-in to. OS configuration (such as making sure config files are correct) does not typically have to use the rolling update +model, but can if desired. + +Runner +++++++ + +A core software component of ansible that is the power behind /usr/bin/ansible directly -- and corresponds to the invocation +of each task in a playbook. The Runner is something ansible developers may talk about, but it's not really userland +vocabulary. + +Serial +++++++ + +See "Rolling Update". + +Sudo +++++ + +Ansible does not require root logins, and since it's daemonless, definitely does not require root level daemons (which can +be a security concern in sensitive environments). Ansible can log in and perform many operations wrapped in a sudo command, +and can work with both passwordless and passworded sudo. Some operations that don't normally work with sudo (like scp +file transfer) can be achieved with Ansible's copy, template, and fetch resources while running in sudo mode. + +SSH (Native) +++++++++++++ + +Native openssh as an Ansible tranpsort is specified with "-c ssh" (or a config file, or a directive in the playbook) +and can be useful if wanting to login via Kerberized SSH or use SSH jump hosts, etc. In 1.2.1, 'ssh' will be used if the OpenSSH +on the control machine is sufficiently new, by default. Previously Ansible selected 'paramiko' as a default. +Using a client that supports ControlMaster and ControlPersist is recommended for maximum performance -- if you don't have that and don't need Kerberos, jump hosts, or other features, paramiko (the default) is a good choice. Ansible will warn you if it doesn't detect ControlMaster/ControlPersist capability. + +Tags +++++ + +Ansible allows tagging resources in a playbook with arbitrary keywords, and then running only the parts of the playbook that +correspond to those certain keywords. For instance, it is possible to have an entire OS configuration, and have certain steps +labelled "ntp", and then run just the "ntp" steps to reconfigure the time server information on a remote server. + +Tasks ++++++ + +Playbooks exist to run tasks. Tasks combine an action (a module combined with what variables to pass) with a name and optionally some other keywords (like looping directives). Handlers are also Tasks, but they are a special kind of task that do not run unless they are notified by name when a task reports an underlying change on a remote system. + +Templates ++++++++++ + +Ansible can easily transfer remote files to remote systems, but often it is desirable to substitute variables in other files. Variables +may come from the inventory file, Host Vars, Group Vars, or Facts -- templates use the Jinja2 template engine and can also include logical +constructs like loops and if statements. + +Transport ++++++++++ + +Ansible uses "Connection Plugins" to define types of available transports. These are simply how ansible will reach out to managed systems. Transports included are paramiko (the default SSH transport), SSH (using openssh), fireball (an SSH bootstrapped accelerated connection plugin), and local. + +When +++++ + +When statements (when_string, when_changed, when_boolean, when_integer, etc) are easier to write forms of the only_if conditional. They can be affixed to any task to make that task decide to run only when an expression involving variables or facts is actually true. + +Van Halen ++++++++++ + +For no particular reason other than Michael really likes them, all Ansible releases are code named after Van Halen songs. There is no preference given to David Lee Roth vs Sammy Lee Hagar era songs, and instrumentals are also allowed. It is unlikely there will never be a Jump release, but it may be there is going to be a Van Halen III codenamed release. You never know. + +Vars (Variables) +++++++++++++++++ + +As opposed to Facts, variables are names of values (they can be simple scalar values --integers, booleans, strings) or complex ones (dictionaries/hashes, lists) that can be used in templates and playbooks. They are declared things, not things that are inferred from the remote systems current state or nature (which is what Facts are). + +YAML +++++ + +Ansible does not want to force people to write programming language code to automate infrastructure, so Ansible uses YAML to define playbook configuration languages and also variable files. YAML is nice because it has a minimum of syntax and is very clean and easy for people to skim. It is a good data format for configuration files and humans, but also machine readable. Ansible's usage of YAML stemmed from Michael's first use of it inside of Cobbler around 2006. YAML is fairly popular in the dynamic language community and the format has libraries available +for serialization in many different languages (Python, Perl, Ruby, etc). + + diff --git a/docsite/rst/index.rst b/docsite/latest/rst/index.rst similarity index 58% rename from docsite/rst/index.rst rename to docsite/latest/rst/index.rst index 8990079256178e..a38ab4c2cfe0f5 100644 --- a/docsite/rst/index.rst +++ b/docsite/latest/rst/index.rst @@ -1,12 +1,15 @@ -Ansible Documentation -````````````````````` +Ansible Documentation Index +``````````````````````````` + +Welcome to the Ansible documentation. This documentation covers the current released +version of Ansible (1.2) and may also reference some development version features. -This page contains documentation about how to use `Ansible `_. +For the previous released version, see `Ansible 1.1 Docs `_ instead. -Before we dive into playbooks, configuration management, deployment, and orchestration, learn how to get Ansible installed and some -basic information. We'll go over how to execute ad-hoc commands in parallel across your nodes using /usr/bin/ansible. We'll also see +Before we dive into playbooks, configuration management, deployment, and orchestration, we'll learn how to get Ansible installed and some +basic information. We'll go over how to execute ad-hoc commands in parallel across your nodes using /usr/bin/ansible. We'll also see what sort of modules are available in Ansible's core (though you can also write your own, which we'll also show later). .. toctree:: @@ -17,21 +20,21 @@ what sort of modules are available in Ansible's core (though you can also write examples modules - Overview ```````` -.. image:: http://ansible.cc/img/ansible_arch.png - :alt: ansible architecture diagram - :width: 566px - :height: 439px +.. image:: http://www.ansibleworks.com/wp-content/uploads/2013/06/ANSIBLE_DIAGRAM.jpg + :alt: ansible architecture diagram + :width: 788px + :height: 436px + Playbooks ````````` Playbooks are Ansible's orchestration language. At a basic level, playbooks can be used to manage configurations and deployments of remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions -to other hosts, interacting with monitoring servers and load balancers along the way. You can start small and pick up more features +to other hosts, interacting with monitoring servers and load balancers along the way. You can start small and pick up more features over time as you need them. Playbooks are designed to be human-readable and are developed in a basic text language. There are multiple ways to organize playbooks and the files they include, and we'll offer up some suggestions on that and making the most out of Ansible. @@ -42,7 +45,17 @@ ways to organize playbooks and the files they include, and we'll offer up some s playbooks2 bestpractices YAMLSyntax + Example Playbooks + +Specific Solutions +`````````````````` + +A chance to dive into some more topics in depth: +.. toctree:: + :maxdepth: 1 + + amazon_web_services Developer Information ````````````````````` @@ -56,15 +69,19 @@ with other solutions in your environment. api moduledev - Miscellaneous ````````````` -A list of some people using Ansible, and some additional resources. +`Learn and share neat Ansible tricks on Coderwall `_ - sign-in using GitHub or Twitter to vote on top tips and add your own! + +`A list of some Ansible users and quotes about Ansible `_. + +More links: .. toctree:: :maxdepth: 1 - who_uses_ansible + faq contrib + glossary diff --git a/docsite/rst/moduledev.rst b/docsite/latest/rst/moduledev.rst similarity index 88% rename from docsite/rst/moduledev.rst rename to docsite/latest/rst/moduledev.rst index bd13d04aa366f1..011688854c2143 100644 --- a/docsite/rst/moduledev.rst +++ b/docsite/latest/rst/moduledev.rst @@ -5,7 +5,7 @@ Ansible modules are reusable units of magic that can be used by the Ansible API, or by the `ansible` or `ansible-playbook` programs. Modules can be written in any language and are found in the path specified -by `ANSIBLE_LIBRARY_PATH` or the ``--module-path`` command line option. +by `ANSIBLE_LIBRARY` or the ``--module-path`` command line option. .. contents:: :depth: 2 @@ -175,7 +175,7 @@ Let's test that module:: This should return something like:: - {"changed": True, "time": "2012-03-14 12:23:00.000307"} + {"changed": true, "time": "2012-03-14 12:23:00.000307"} Module Provided 'Facts' ``````````````````````` @@ -249,6 +249,32 @@ can function outside of Ansible. If submitting a module to ansible's core code, which we encourage, use of the AnsibleModule class is required. +Check Mode +`````````` +.. versionadded:: 1.1 + +Modules may optionally support check mode. If the user runs Ansible in check +mode, the module should try to predict whether changes will occur. + +For your module to support check mode, you must pass ``supports_check_mode=True`` +when instantiating the AnsibleModule object. The AnsibleModule.check_mode attribute +will evaluate to True when check mode is enabled. For example:: + + module = AnsibleModule( + argument_spec = dict(...), + supports_check_mode=True + ) + + if module.check_mode: + # Check if any changes would be made by don't actually make those changes + module.exit_json(changed=check_if_system_state_would_be_changed()) + +Remember that, as module developer, you are responsible for ensuring that no +system state is altered when the user enables check mode. + +If your module does not support check mode, when the user runs Ansible in check +mode, your module will simply be skipped. + Common Pitfalls ``````````````` @@ -322,15 +348,10 @@ syntax highlighting before you include it in your Python file. Example +++++++ -Here's a correctly formatted YAML document we could use for a -``DOCUMENTATION`` string: - -.. literalinclude:: ../DOCUMENTATION.yaml +To print a basic documentation string, run ``./hacking/module_formatter.py -G``. -This is available in the 'examples/' directory of the of the Ansible -github repository, and you can have that generated with -``./hacking/module_formatter.py -G``. You can copy it into your module and use -it as a starting point when writing your own docs. +You can copy it into your module and use it as a starting point +when writing your own docs. Include it in your module file like this:: @@ -342,18 +363,28 @@ Include it in your module file like this:: module: modulename short_description: This is a sentence describing the module # ... snip ... - examples: - - code: modulename opt1=arg1 opt2=arg2 - description: Optional words describing this example ''' -The ``description``, ``notes`` and ``description`` within ``examples`` +The ``description``, and ``notes`` support formatting in some of the output formats (e.g. ``rst``, ``man``). These formatting functions are ``U()``, ``M()``, ``I()``, and ``C()`` for URL, module, italic, and constant-width respectively. It is suggested to use ``C()`` for file and option names, and ``I()`` when referencing parameters; module names should be specifies as ``M(module)``. +Examples (which typically contain colons, quotes, etc.) are difficult +to format with YAML, so these must be +written in plain text in an ``EXAMPLES`` string within the module +like this:: + + EXAMPLES = ''' + - action: modulename opt1=arg1 opt2=arg2 + ''' + +The ``module_formatter.py`` script and ``ansible-doc(1)`` append the +``EXAMPLES`` blob after any existing (deprecated) ``examples`` you may have in the +YAML ``DOCUMENTATION`` string. + Building & Testing ++++++++++++++++++ @@ -383,6 +414,10 @@ output formats available: If you're having a problem with the syntax of your YAML you can validate it on the `YAML Lint `_ website. +.. tip:: + + You can use ANSIBLE_KEEP_REMOTE_FILES=1 to prevent ansible from + deleting the remote files so you can debug your module. Getting Your Module Into Core ````````````````````````````` @@ -397,7 +432,7 @@ the program. Stop by the mailing list to inquire about requirements. :doc:`modules` Learn about available modules - `Ansible Resources `_ + :doc:`contrib` User contributed playbooks, modules, and articles `Github modules directory `_ Browse source of core modules diff --git a/docsite/rst/modules.rst b/docsite/latest/rst/modules.rst similarity index 93% rename from docsite/rst/modules.rst rename to docsite/latest/rst/modules.rst index 6b209de9c26ef5..4bbf0ff23d1a51 100644 --- a/docsite/rst/modules.rst +++ b/docsite/latest/rst/modules.rst @@ -2,7 +2,7 @@ Ansible Modules =============== .. contents:: - :depth: 2 + :depth: 3 :backlinks: top Introduction @@ -44,6 +44,13 @@ Modules are `idempotent`, meaning they will seek to avoid changes to the system playbooks, these modules can trigger 'change events' in the form of notifying 'handlers' to run additional tasks. +Documention for each module can be accessed from the command line with the +ansible-doc as well as the man command:: + + ansible-doc command + + man ansible.template + Let's see what's available in the Ansible module library, out of the box: diff --git a/docsite/rst/modules/.gitdir b/docsite/latest/rst/modules/.gitdir similarity index 100% rename from docsite/rst/modules/.gitdir rename to docsite/latest/rst/modules/.gitdir diff --git a/docsite/rst/patterns.rst b/docsite/latest/rst/patterns.rst similarity index 65% rename from docsite/rst/patterns.rst rename to docsite/latest/rst/patterns.rst index 98f9ef853220af..fc58eed7620ca0 100644 --- a/docsite/rst/patterns.rst +++ b/docsite/latest/rst/patterns.rst @@ -7,7 +7,7 @@ Ansible works against multiple systems in your infrastructure at the same time. It does this by selecting portions of systems listed in Ansible's inventory file, which defaults to /etc/ansible/hosts. -.. contents:: `Table of contents` +.. contents:: :depth: 2 :backlinks: top @@ -48,7 +48,7 @@ Adding a lot of hosts? In 0.6 and later, if you have a lot of hosts following s [webservers] www[01:50].example.com - + In 1.0 and later, you can also do this for alphabetic ranges:: @@ -57,6 +57,53 @@ In 1.0 and later, you can also do this for alphabetic ranges:: For numeric patterns, leading zeros can be included or removed, as desired. Ranges are inclusive. +In 1.1 and later, you can also select the connection type and user on a per host basis:: + + [targets] + + localhost ansible_connection=local + other1.example.com ansible_connection=ssh ansible_ssh_user=mpdehaan + other2.example.com ansible_connection=ssh ansible_ssh_user=mdehaan + +All of these variables can of course also be set outside of the inventory file, in 'host_vars' if you wish +to keep your inventory file simple. + +List of Reserved Inventory Parameters ++++++++++++++++++++++++++++++++++++++ + +As a summary, you can set these parameters as host inventory variables. (Some we have already +mentioned). + +ansible_ssh_host + The name of the host to connect to, if different from the alias you wish to give to it. +ansible_ssh_port + The ssh port number, if not 22 +ansible_ssh_user + The default ssh user name to use. +ansible_ssh_pass + The ssh password to use (this is insecure, we strongly recommend using --ask-pass or SSH keys) +ansible_connection + Connection type of the host. Candidates are local, ssh or paramiko. The default is paramiko before Ansible 1.2, and 'smart' afterwards which detects whether usage of 'ssh' would be feasible based on whether ControlPersist is supported. +ansible_ssh_private_key_file + Private key file used by ssh. Useful if using multiple keys and you don't want to use SSH agent. +ansible_syslog_facility + The syslog facility to log to. +ansible_python_interpreter + The target host python path. This is userful for systems with more + than one Python or not located at "/usr/bin/python" such as \*BSD, or where /usr/bin/python + is not a 2.X series Python. +ansible\_\*\_interpreter + Works for anything such as ruby or perl and works just like ansible_python_interpreter. + This replaces shebang of modules which will run on that host. + +Examples from a host file:: + + some_host ansible_ssh_port=2222 ansible_ssh_user=manager + aws_host ansible_ssh_private_key_file=/home/example/.ssh/aws.pem + freebsd_host ansible_python_interpreter=/usr/local/bin/python + ruby_module_host ansible_ruby_interpreter=/usr/bin/ruby.1.9.3 + + Selecting Targets +++++++++++++++++ @@ -95,7 +142,19 @@ You can exclude groups as well, for instance, all webservers not in Phoenix:: webservers:!phoenix -Individual host names (or IPs), but not groups, can also be referenced using +You can also specify the intersection of two groups:: + + webservers:&staging + +You can do combinations:: + + webservers:dbservers:!phoenix:&staging + +You can also use variables:: + + webservers:!{{excluded}}:&{{required}} + +Individual host names, IPs and groups, can also be referenced using wildcards:: *.example.com @@ -105,6 +164,10 @@ It's also ok to mix wildcard patterns and groups at the same time:: one*.com:dbservers +And if the pattern starts with a '~' it is treated as a regular expression:: + + ~(web|db).*\.example\.com + Easy enough. See :doc:`examples` and then :doc:`playbooks` for how to do things to selected hosts. Host Variables @@ -162,7 +225,7 @@ variables to groups. These variables can be used by /usr/bin/ansible-playbook, southeast If you need to store lists or hash data, or prefer to keep host and group specific variables -seperate from the inventory file, see the next section. +separate from the inventory file, see the next section. Splitting Out Host and Group Specific Data ++++++++++++++++++++++++++++++++++++++++++ @@ -194,12 +257,16 @@ the 'raleigh' group might look like:: It is ok if these files do not exist, this is an optional feature. +Tip: In Ansible 1.2 or later the group_vars/ and host_vars/ directories can exist in either +the playbook directory OR the inventory directory. If both paths exist, variables in the playbook +directory will be loaded second. + Tip: Keeping your inventory file and variables in a git repo (or other version control) is an excellent way to track changes to your inventory and host variables. .. versionadded:: 0.5 - If you ever have two python interpreters on a system, set a - variable called 'ansible_python_interpreter' to the Python + If you ever have two python interpreters on a system, or your Python version 2 interpreter is not found + at /usr/bin/python, set an inventory variable called 'ansible_python_interpreter' to the Python interpreter path you would like to use. .. seealso:: diff --git a/docsite/rst/playbooks.rst b/docsite/latest/rst/playbooks.rst similarity index 66% rename from docsite/rst/playbooks.rst rename to docsite/latest/rst/playbooks.rst index b2cf63748c641a..adf03f18d76929 100644 --- a/docsite/rst/playbooks.rst +++ b/docsite/latest/rst/playbooks.rst @@ -27,14 +27,17 @@ Let's dive in and see how they work. As you go, you may wish to open the `github examples directory `_ in another tab, so you can apply the theory to what things look like in practice. +There are also some full sets of playbooks illustrating a lot of these techniques in the +`ansible-examples repository `_. + Playbook Language Example ````````````````````````` Playbooks are expressed in YAML format and have a minimum of syntax. Each playbook is composed of one or more 'plays' in a list. -The goal of a play is map a group of hosts to some well defined roles, represented by -things ansible called tasks. At the basic level, a task is nothing more than a call +The goal of a play is to map a group of hosts to some well defined roles, represented by +things ansible calls tasks. At a basic level, a task is nothing more than a call to an ansible module, which you should have learned about in earlier chapters. By composing a playbook of multiple 'plays', it is possible to @@ -90,6 +93,16 @@ Support for running things from sudo is also available:: user: yourname sudo: yes +You can also use sudo on a particular task instead of the whole play:: + + --- + - hosts: webservers + user: yourname + tasks: + - service: name=nginx state=started + sudo: yes + + You can also login as you, and then sudo to different users than root:: --- @@ -128,28 +141,28 @@ The `vars` section contains a list of variables and values that can be used in t van_halen_port: 5150 other: 'magic' -These variables can be used later in the playbook like this:: +.. note:: + You can also keep variables in separate files and include them alongside inline `vars` with a `vars_files` declaration + in the play. See the `Advanced Playbooks chapter `_ + for more info. - $varname or ${varname} +These variables can be used later in the playbook like this:: -The later is useful in the event you need to do something like ${other}_some_string. + $varname or ${varname} or {{ varname }} -Inside templates, the full power of the `Jinja2 `_ templating language is also available, which looks like this:: +If you ever want to do anything complex like uppercasing a string, {{ varname }} is best, as it uses the Jinja2 templating engine. It is a good idea to get in the habit of using this form most of the time when the output is to be a string. - {{ varname }} +If just referencing the value of another simple variable though, it's fine to say $x or ${x}. This is common for when a datastructure has a member that is the value of another datastructure. -The Jinja2 documentation provides information about how to construct loops and conditionals for those -who which to use more advanced templating. This is optional and the $varname format still works in template -files. +To learn more about Jinja2, you can optionally see the `Jinja2 docs `_ - though remember that Jinja2 loops and conditionals are only for 'templates' in Ansible, in playbooks, ansible has the 'when' and 'with' keywords for conditionals and loops. If there are discovered variables about the system, called 'facts', these variables bubble up back into the playbook, and can be used on each system just like explicitly set variables. Ansible provides several of these, prefixed with 'ansible', and are documented under 'setup' in the module documentation. Additionally, -facts can be gathered by ohai and facter if they are installed. Facter variables are prefixed with ``facter_`` and Ohai -variables are prefixed with ``ohai_``. +facts can be gathered by ohai and facter if they are installed. Facter variables are prefixed with ``facter_`` and Ohai variables are prefixed with ``ohai_``. These add extra dependencies and are only there for ease of users +porting over from those other configuration systems. -So for instance, if I wanted -to write the hostname into the /etc/motd file, I could say:: +How about an example. If I wanted to write the hostname into the /etc/motd file, I could say:: - name: write the motd action: template src=/srv/templates/motd.j2 dest=/etc/motd @@ -158,7 +171,7 @@ And in /srv/templates/motd.j2:: You are logged into {{ facter_hostname }} -But we're getting ahead of ourselves. Let's talk about tasks. +But we're getting ahead of ourselves, as that just showed a task in a playbook ahead of schedule. Let's talk about tasks. Tasks list ++++++++++ @@ -212,7 +225,15 @@ who's successful exit code is not zero, you may wish to do this:: tasks: - name: run this command and ignore the result - action: shell /usr/bin/somecommand && /bin/true + action: shell /usr/bin/somecommand || /bin/true + +Or this:: + + tasks: + - name: run this command and ignore the result + action: shell /usr/bin/somecommand + ignore_errors: True + If the action line is getting too long for comfort you can break it on a space and indent any continuation lines:: @@ -226,8 +247,8 @@ Variables can be used in action lines. Suppose you defined a variable called 'vhost' in the 'vars' section, you could do this:: tasks: - - name: create a virtual host file for $vhost - action: template src=somefile.j2 dest=/etc/httpd/conf.d/$vhost + - name: create a virtual host file for {{ vhost }} + action: template src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} Those same variables are usable in templates, which we'll get to later. @@ -247,8 +268,8 @@ It is also possible to say: template: src=templates/foo.j2 dest=/etc/foo.conf -The name of the module is simply followed by a colon and the arguments to that module. We think this is a lot more intuitive. -Our documentation has not converted over to this new format just yet as many users may still be using older versions. +The name of the module is simply followed by a colon and the arguments to that module. We think this is a lot more intuitive. +Our documentation has not converted over to this new format just yet as many users may still be using older versions. You'll be able to use both formats forever. Running Operations On Change @@ -258,9 +279,12 @@ As we've mentioned, modules are written to be 'idempotent' and can relay when they have made a change on the remote system. Playbooks recognize this and have a basic event system that can be used to respond to change. -These 'notify' actions are triggered at the end of each 'play' in a playbook, and -trigger only once each. For instance, multiple resources may indicate -that apache needs to be restarted, but apache will only be bounced once. +These 'notify' actions are triggered at the end of each block of tasks in a playbook, and will only be +triggered once even if notified by multiple different tasks. + +For instance, multiple resources may indicate +that apache needs to be restarted because they have changed a config file, +but apache will only be bounced once to avoid unneccessary restarts. Here's an example of restarting two services when the contents of a file change, but only if the file changes:: @@ -294,9 +318,22 @@ won't need them for much else. .. note:: Notify handlers are always run in the order written. +Roles are described later on. It's worth while to point out that handlers are +automatically processed between 'pre_tasks', 'roles', 'tasks', and 'post_tasks' +sections. If you ever want to flush all the handler commands immediately though, +in 1.2 and later, you can:: + + tasks: + - shell: some tasks go here + - meta: flush_handlers + - shell: some other tasks + +In the above example any queued up handlers would be processed early when the 'meta' +statement was reached. This is a bit of a niche case but can come in handy from +time to time. -Include Files And Encouraging Reuse -``````````````````````````````````` +Task Include Files And Encouraging Reuse +```````````````````````````````````````` Suppose you want to reuse lists of tasks between plays or playbooks. You can use include files to do this. Use of included task lists is a great way to define a role @@ -329,11 +366,26 @@ contain all of my wordpress tasks in a single wordpress.yml file, and use it lik Variables passed in can then be used in the included files. You can reference them like this:: - $user + {{ user }} (In addition to the explicitly passed in parameters, all variables from the vars section are also available for use here as well.) +Starting in 1.0, variables can also be passed to include files using an alternative syntax, +which also supports structured variables:: + + tasks: + + - include: wordpress.yml + vars: + user: timmy + some_list_variable: + - alpha + - beta + - gamma + +Playbooks can include other playbooks too, but that's mentioned in a later section. + .. note:: As of 1.0, task include statements can be used at arbitrary depth. They were previously limited to a single level, so task includes @@ -383,9 +435,108 @@ inside another. this, consider how you can restructure your playbook to be more class/role oriented. This is to say you cannot use a 'fact' to decide what include file to use. All hosts contained within the - play are going to get the same tasks. ('only_if' provides some + play are going to get the same tasks. ('*when*' provides some ability for hosts to conditionally skip tasks). +.. _roles: + +Roles +````` + +.. versionadded: 1.2 + +Now that you have learned about vars_files, tasks, and handlers, what is the best way to organize your playbooks? +The short answer is to use roles! Roles are ways of automatically loading certain vars_files, tasks, and +handlers based on a known file structure. Grouping content by roles also allows easy sharing of roles with other users. + +Roles are just automation around 'include' directives as redescribed above, and really don't contain much +additional magic beyond some improvements to search path handling for referenced files. However, that can be a big thing! + +Example project structure:: + + site.yml + webservers.yml + fooservers.yml + roles/ + common/ + files/ + templates/ + tasks/ + handlers/ + vars/ + webservers/ + files/ + templates/ + tasks/ + handlers/ + vars/ + +In a playbook, it would look like this:: + + --- + - hosts: webservers + roles: + - common + - webservers + +This designates the following behaviors, for each role 'x': + +- If roles/x/tasks/main.yml exists, tasks listed therein will be added to the play +- If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play +- If roles/x/vars/main.yml exists, variables listed therein will be added to the play +- Any copy tasks can reference files in roles/x/files/ without having to path them relatively or absolutely +- Any script tasks can reference scripts in roles/x/files/ without having to path them relatively or absolutely +- Any template tasks can reference files in roles/x/templates/ without having to path them relatively or absolutely + +If any files are not present, they are just ignored. So it's ok to not have a 'vars/' subdirectory for the role, +for instance. + +Note, you are still allowed to list tasks, vars_files, and handlers "loose" in playbooks without using roles, +but roles are a good organizational feature and are highly recommended. if there are loose things in the playbook, +the roles are evaluated first. + +Also, should you wish to parameterize roles, by adding variables, you can do so, like this:: + + --- + - hosts: webservers + roles: + - common + - { role: foo_app_instance, dir: '/opt/a', port: 5000 } + - { role: foo_app_instance, dir: '/opt/b', port: 5001 } + +While it's probably not something you should do often, you can also conditionally apply roles like so:: + + --- + - hosts: webservers + roles: + - { role: some_role, when: "ansible_os_family == 'RedHat'" } + +This works by applying the conditional to every task in the role. Conditionals are covered later on in +the documentation. + +Finally, you may wish to assign tags to the roles you specify. You can do so inline::: + + --- + - hosts: webservers + roles: + - { role: foo, tags: ["bar", "baz"] } + + +If the play still has a 'tasks' section, those tasks are executed after roles are applied. + +If you want to define certain tasks to happen before AND after roles are applied, you can do this:: + + --- + - hosts: webservers + pre_tasks: + - shell: echo 'hello' + roles: + - { role: some_role } + tasks: + - shell: echo 'still busy' + post_tasks: + - shell: echo 'goodbye' + Executing A Playbook ```````````````````` @@ -399,7 +550,7 @@ Tips and Tricks Look at the bottom of the playbook execution for a summary of the nodes that were executed and how they performed. General failures and fatal "unreachable" communication attempts are -kept seperate in the counts. +kept separate in the counts. If you ever want to see detailed output from successful modules as well as unsuccessful ones, use the '--verbose' flag. This is available in Ansible 0.5 and later. diff --git a/docsite/rst/playbooks2.rst b/docsite/latest/rst/playbooks2.rst similarity index 51% rename from docsite/rst/playbooks2.rst rename to docsite/latest/rst/playbooks2.rst index 1d66a4728ba5db..92283d549ffc96 100644 --- a/docsite/rst/playbooks2.rst +++ b/docsite/latest/rst/playbooks2.rst @@ -2,7 +2,7 @@ Advanced Playbooks ================== Here are some advanced features of the playbooks language. Using all of these features -are not neccessary, but many of them will prove useful. If a feature doesn't seem immediately +are not necessary, but many of them will prove useful. If a feature doesn't seem immediately relevant, feel free to skip it. For many people, the features documented in `playbooks` will be 90% or more of what they use in Ansible. @@ -23,7 +23,7 @@ Example:: tasks: - - action: yum name=$item state=installed + - action: yum name={{ item }} state=installed with_items: - httpd - memcached @@ -49,7 +49,7 @@ your webservers in "webservers.yml" and all your database servers in "dbservers.yml". You can create a "site.yml" that would reconfigure all of your systems like this:: - ---- + --- - include: playbooks/webservers.yml - include: playbooks/dbservers.yml @@ -73,37 +73,29 @@ Accessing Complex Variable Data ``````````````````````````````` Some provided facts, like networking information, are made available as nested data structures. To access -them a simple '$foo' is not sufficient, but it is still easy to do. Here's how we get an IP address:: +them a simple {{ foo }} is not sufficient, but it is still easy to do. Here's how we get an IP address:: - ${ansible_eth0.ipv4.address} + {{ ansible_eth0["ipv4"]["address"] }} -It is also possible to access variables whose elements are arrays:: +Similarly, this is how we access the first element of an array: - ${somelist[0]} + {{ foo[0] }} -And the array and hash reference syntaxes can be mixed. +Magic Variables, and How To Access Information About Other Hosts +```````````````````````````````````````````````````````````````` -In templates, the simple access form still holds, but they can also be accessed from Jinja2 in more Python-native ways if -that is preferred:: +Even if you didn't define them yourself, Ansible provides a few variables for you, automatically. +The most important of these are 'hostvars', 'group_names', and 'groups'. Users should not use +these names themselves as they are reserved. 'environment' is also reserved. - {{ ansible_eth0["ipv4"]["address"] }} - -Accessing Information About Other Hosts -``````````````````````````````````````` +Hostvars lets you ask about the variables of another host, including facts that have been gathered +about that host. If, at this point, you haven't talked to that host yet in any play in the playbook +or set of playbooks, you can get at the variables, but you will not be able to see the facts. -If your database server wants to check the value of a 'fact' from another node, or an inventory variable +If your database server wants to use the value of a 'fact' from another node, or an inventory variable assigned to another node, it's easy to do so within a template or even an action line:: - ${hostvars.hostname.factname} - -.. note:: - No database or other complex system is required to exchange data - between hosts. The hosts that you want to reference data from must - be included in either the current play or any previous play if you - are using a version prior to 0.8. If you are using 0.8, and you have - not yet contacted the host, you'll be able to read inventory variables - but not fact variables. Speak to the host by including it in a play - to make fact information available. + {{ hostvars['test.example.com']['ansible_distribution'] }} Additionally, *group_names* is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2 syntax to make template source files that vary based on the group membership (or role) of the host:: @@ -118,15 +110,25 @@ For example:: # something that applies to all app servers. {% endfor %} -Use cases include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc. +A frequently used idiom is walking a group to find all IP addresses in that group:: -*inventory_hostname* is the name of the hostname as configured in Ansible's inventory host file. This can + {% for host in groups['app_servers'] %} + {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} + {% endfor %} + +An example of this could include pointing a frontend proxy server to all of the app servers, setting up the correct firewall rules between servers, etc. + +Just a few other 'magic' variables are available... There aren't many. + +Additionally, *inventory_hostname* is the name of the hostname as configured in Ansible's inventory host file. This can be useful for when you don't want to rely on the discovered hostname `ansible_hostname` or for other mysterious -reasons. If you have a long FQDN, *inventory_hostname_short* (in Ansible 0.6) also contains the part up to the first -period. +reasons. If you have a long FQDN, *inventory_hostname_short* also contains the part up to the first +period, without the rest of the domain. Don't worry about any of this unless you think you need it. You'll know when you do. +Also available, *inventory_dir* is the pathname of the directory holding Ansible's inventory host file. + Variable File Separation ```````````````````````` @@ -184,6 +186,15 @@ in a push-script:: There are full examples of both of these items in the github examples/playbooks directory. +If you have a variable that changes infrequently, it might make sense to +provide a default value that can be overridden. This can be accomplished using +the default argument:: + + vars_prompt: + - name: "release_version" + prompt: "Product release version" + default: "1.0" + An alternative form of vars_prompt allows for hiding input from the user, and may later support some other options, but otherwise works equivalently:: @@ -195,12 +206,46 @@ some other options, but otherwise works equivalently:: prompt: "Product release version" private: no +If `Passlib `_ is installed, vars_prompt can also crypt the +entered value so you can use it, for instance, with the user module to define a password:: + + vars_prompt: + - name: "my_password2" + prompt: "Enter password2" + private: yes + encrypt: "md5_crypt" + confirm: yes + salt_size: 7 + +You can use any crypt scheme supported by 'Passlib': + +- *des_crypt* - DES Crypt +- *bsdi_crypt* - BSDi Crypt +- *bigcrypt* - BigCrypt +- *crypt16* - Crypt16 +- *md5_crypt* - MD5 Crypt +- *bcrypt* - BCrypt +- *sha1_crypt* - SHA-1 Crypt +- *sun_md5_crypt* - Sun MD5 Crypt +- *sha256_crypt* - SHA-256 Crypt +- *sha512_crypt* - SHA-512 Crypt +- *apr_md5_crypt* - Apache’s MD5-Crypt variant +- *phpass* - PHPass’ Portable Hash +- *pbkdf2_digest* - Generic PBKDF2 Hashes +- *cta_pbkdf2_sha1* - Cryptacular’s PBKDF2 hash +- *dlitz_pbkdf2_sha1* - Dwayne Litzenberger’s PBKDF2 hash +- *scram* - SCRAM Hash +- *bsd_nthash* - FreeBSD’s MCF-compatible nthash encoding + +However, the only parameters accepted are 'salt' or 'salt_size'. You can use you own salt using +'salt', or have one generated automatically using 'salt_size'. If nothing is specified, a salt +of size 8 will be generated. Passing Variables On The Command Line ````````````````````````````````````` In addition to `vars_prompt` and `vars_files`, it is possible to send variables over -the ansible command line. This is particularly useful when writing a generic release playbook +the Ansible command line. This is particularly useful when writing a generic release playbook where you may want to pass in the version of the application to deploy:: ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo" @@ -209,149 +254,114 @@ This is useful, for, among other things, setting the hosts group or the user for Example:: - ----- - - user: $user - hosts: $hosts + --- + - user: '{{ user }}' + hosts: '{{ hosts }}' tasks: - ... ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck" +As of Ansible 1.2, you can also pass in extra vars as quoted JSON, like so:: + + --extra-vars "{'pacman':'mrs','ghosts':['inky','pinky','clyde','sue']}" + +The key=value form is obviously simpler, but it's there if you need it! + + Conditional Execution ````````````````````` +(Note: this section covers 1.2 conditionals, if you are using a previous version, select +the previous version of the documentation, `Ansible 1.1 Docs `_ . +Those conditional forms continue to be operational in 1.2, although the new mechanisms are cleaner.) + Sometimes you will want to skip a particular step on a particular host. This could be something as simple as not installing a certain package if the operating system is a particular version, or it could be something like performing some cleanup steps if a filesystem is getting full. -This is easy to do in Ansible, with the `only_if` clause, which actually is a Python expression. +This is easy to do in Ansible, with the `when` clause, which actually is a Python expression. Don't panic -- it's actually pretty simple:: - vars: - favcolor: blue - is_favcolor_blue: "'$favcolor' == 'blue'" - is_centos: "'$facter_operatingsystem' == 'CentOS'" - tasks: - - name: "shutdown if my favorite color is blue" + - name: "shutdown Debian flavored systems" action: command /sbin/shutdown -t now - only_if: '$is_favcolor_blue' + when: ansible_os_family == "Debian" -Variables from tools like `facter` and `ohai` can be used here, if installed, or you can -use variables that bubble up from ansible, which many are provided by the :ref:`setup` module. As a reminder, -these variables are prefixed, so it's `$facter_operatingsystem`, not `$operatingsystem`. Ansible's -built in variables are prefixed with `ansible_`. - -The only_if expression is actually a tiny small bit of Python, so be sure to quote variables and make something -that evaluates to `True` or `False`. It is a good idea to use 'vars_files' instead of 'vars' to define -all of your conditional expressions in a way that makes them very easy to reuse between plays -and playbooks. - -You cannot use live checks here, like 'os.path.exists', so don't try. - -It's also easy to provide your own facts if you want, which is covered in :doc:`moduledev`. To run them, just -make a call to your own custom fact gathering module at the top of your list of tasks, and variables returned -there will be accessible to future tasks:: +A number of Jinja2 "filters" can also be used in when statements, some of which are unique +and provided by Ansible. Suppose we want to ignore the error of one statement and then +decide to do something conditionally based on success or failure:: tasks: - - name: gather site specific fact data - action: site_facts - - action: command echo ${my_custom_fact_can_be_used_now} + - action: command /bin/false + register: result + ignore_errors: True + - action: command /bin/something + when: result|failed + - action: command /bin/something_else + when: result|success + - action: command /bin/still/something_else + when: result|skipped -One common useful trick with only_if is to key off the changed result of a last command. As an example:: - tasks: - - action: template src=/templates/foo.j2 dest=/etc/foo.conf - register: last_result - - action: command echo 'the file has changed' - only_if: '${last_result.changed}' +As a reminder, to see what derived variables are available, you can do:: -$last_result is a variable set by the register directive. This assumes Ansible 0.8 and later. + ansible hostname.example.com -m setup -In Ansible 0.8, a few shortcuts are available for testing whether a variable is defined or not:: +Tip: Sometimes you'll get back a variable that's a string and you'll want to do a comparison on it. You can do this like so: tasks: - - action: command echo hi - only_if: is_set('$some_variable') + - shell: echo "only on Red Hat 6, derivatives, and later" + when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6 -There is a matching 'is_unset' that works the same way. Quoting the variable inside the function is mandatory. +Variables defined in the playbooks or inventory can also be used. -When combining `only_if` with `with_items`, be aware that the `only_if` statement is processed seperately for each item. -This is by design:: +If a required variable has not been set, you can skip or fail using Jinja2's +`defined` test. For example:: tasks: - - action: command echo $item - with_item: [ 0, 2, 4, 6, 8, 10 ] - only_if: "$item > 5" - -While `only_if` is a pretty good option for advanced users, it exposes more guts than we'd like, and -we can do better. In 1.0, we added 'when', which is like syntactic sugar for `only_if` and hides -this level of complexity. See more on this below. - -Conditional Execution (Simplified) -`````````````````````````````````` - -.. versionadded: 0.8 - -In Ansible 0.9, we realized that only_if was a bit syntactically complicated, and exposed too much Python -to the user. As a result, the 'when' set of keywords was added. The 'when' statements do not have -to be quoted or casted to specify types, but you should seperate any variables used with whitespace. In -most cases users will be able to use 'when', but for more complex cases, only_if may still be required. - -Here are various examples of 'when' in use. 'when' is incompatible with 'only_if' in the same task:: - - - name: "do this if my favcolor is blue, and my dog is named fido" - action: shell /bin/false - when_string: $favcolor == 'blue' and $dog == 'fido' - - - name: "do this if my favcolor is not blue, and my dog is named fido" - action: shell /bin/true - when_string: $favcolor != 'blue' and $dog == 'fido' + - shell: echo "I've got '{{ foo }}' and am not afraid to use it!" + when: foo is defined - - name: "do this if my SSN is over 9000" - action: shell /bin/true - when_integer: $ssn > 9000 + - fail: msg="Bailing out: this play requires 'bar'" + when: bar is not defined - - name: "do this if I have one of these SSNs" - action: shell /bin/true - when_integer: $ssn in [ 8675309, 8675310, 8675311 ] +This is especially useful in combination with the conditional import of vars +files (see below). - - name: "do this if a variable named hippo is NOT defined" - action: shell /bin/true - when_unset: $hippo - - - name: "do this if a variable named hippo is defined" - action: shell /bin/true - when_set: $hippo +It's also easy to provide your own facts if you want, which is covered in :doc:`moduledev`. To run them, just +make a call to your own custom fact gathering module at the top of your list of tasks, and variables returned +there will be accessible to future tasks:: - - name: "do this if a variable named hippo is true" - action: shell /bin/true - when_boolean: $hippo + tasks: + - name: gather site specific fact data + action: site_facts + - action: command echo {{ my_custom_fact_can_be_used_now }} -The when_boolean check will look for variables that look to be true as well, such as the string 'True' or -'true', non-zero numbers, and so on. +One useful trick with *when* is to key off the changed result of a last command. As an example:: -.. versionadded: 1.0 + tasks: + - action: template src=/templates/foo.j2 dest=/etc/foo.conf + register: last_result + - action: command echo 'the file has changed' + when: last_result.changed -In 1.0, we also added when_changed and when_failed so users can execute tasks based on the status of previously -registered tasks. As an example:: +{{ last_result }} is a variable set by the register directive. This assumes Ansible 0.8 and later. - - name: "register a task that might fail" - action: shell /bin/false - register: result - ignore_errors: True +When combining `when` with `with_items`, be aware that the `when` statement is processed separately for each item. +This is by design:: - - name: "do this if the registered task failed" - action: shell /bin/true - when_failed: $result + tasks: + - action: command echo {{ item }} + with_items: [ 0, 2, 4, 6, 8, 10 ] + when: item > 5 - - name: "register a task that might change" - action: yum pkg=httpd state=latest - register: result +Note that if you have several tasks that all share the same conditional statement, you can affix the conditional +to a task include statement as below. Note this does not work with playbook includes, just task includes. All the tasks +get evaluated, but the conditional is applied to each and every task:: - - name: "do this if the registered task changed" - action: shell /bin/true - when_changed: $result + - include: tasks/sometasks.yml + when: "'reticulating splines' in output" Conditional Imports ``````````````````` @@ -367,13 +377,13 @@ but it is easily handled with a minimum of syntax in an Ansible Playbook:: user: root vars_files: - "vars/common.yml" - - [ "vars/$facter_operatingsystem.yml", "vars/os_defaults.yml" ] + - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ] tasks: - name: make sure apache is running - action: service name=$apache state=running + action: service name={{ apache }} state=running .. note:: - The variable (`$facter_operatingsystem`) is being interpolated into + The variable 'ansible_os_family' is being interpolated into the list of filenames being defined for vars_files. As a reminder, the various YAML files contain just keys and values:: @@ -409,15 +419,15 @@ Loops To save some typing, repeated tasks can be written in short-hand like so:: - - name: add user $item - action: user name=$item state=present groups=wheel + - name: add several users + action: user name={{ item }} state=present groups=wheel with_items: - testuser1 - testuser2 If you have defined a YAML list in a variables file, or the 'vars' section, you can also do:: - with_items: $somelist + with_items: somelist The above would be the equivalent of:: @@ -431,21 +441,25 @@ The yum and apt modules use with_items to execute fewer package manager transact Note that the types of items you iterate over with 'with_items' do not have to be simple lists of strings. If you have a list of hashes, you can reference subkeys using things like:: - ${item.subKeyName} + - name: add several users + action: user name={{ item.name }} state=present groups={{ item.groups }} + with_items: + - { name: 'testuser1', groups: 'wheel' } + - { name: 'testuser2', groups: 'root' } Lookup Plugins - Accessing Outside Data ``````````````````````````````````````` .. versionadded: 0.8 -Various 'lookup plugins' allow additional ways to iterate over data. Ansible will have more of these +Various *lookup plugins* allow additional ways to iterate over data. Ansible will have more of these over time. You can write your own, as is covered in the API section. Each typically takes a list and can accept more than one parameter. -'with_fileglob' matches all files in a single directory, non-recursively, that match a pattern. It can +``with_fileglob`` matches all files in a single directory, non-recursively, that match a pattern. It can be used like this:: - ---- + --- - hosts: all tasks: @@ -454,75 +468,63 @@ be used like this:: - action: file dest=/etc/fooapp state=directory # copy each file over that matches the given pattern - - action: copy src=$item dest=/etc/fooapp/ owner=root mode=600 - with_fileglob: + - action: copy src={{ item }} dest=/etc/fooapp/ owner=root mode=600 + with_fileglob: - /playbooks/files/fooapp/* -'with_file' loads data in from a file directly:: +``with_file`` loads data in from a file directly:: - - action: authorized_key user=foo key=$item + - action: authorized_key user=foo key={{ item }} with_file: - /home/foo/.ssh/id_rsa.pub -As an alternative, lookup plugins can also be accessed in variables like so:: +.. note:: - vars: - motd_value: $FILE(/etc/motd) - hosts_value: $LOOKUP(file,/etc/hosts) + When using ``with_fileglob`` or ``with_file`` with :ref:`roles`, if you + specify a relative path (e.g., :file:`./foo`), Ansible resolves the path + relative to the :file:`roles//files` directory. .. versionadded: 0.9 -Many new lookup abilities were added in 0.9. Remeber lookup plugins are run on the "controlling" machine:: +Many new lookup abilities were added in 0.9. Remember, lookup plugins are run on the *controlling* machine:: --- - hosts: all tasks: - - action: debug msg="$item is an environment variable" - with_env: - - HOME - - LANG + - action: debug msg="{{ lookup('env','HOME') }} is an environment variable" - - action: debug msg="$item is a line from the result of this command" + - action: debug msg="{{ item }} is a line from the result of this command" with_lines: - cat /etc/motd - - action: debug msg="$item is the raw result of running this command" - with_pipe: - - date + - action: debug msg="{{ lookup('pipe','date') }} is the raw result of running this command" - - action: debug msg="$item is value in Redis for somekey" - with_redis_kv: - - redis://localhost:6379,somekey + - action: debug msg="{{ lookup('redis_kv', 'redis://localhost:6379,somekey') }} is value in Redis for somekey" - - action: debug msg="$item is a DNS TXT record for example.com" - with_dnstxt: - - example.com + - action: debug msg="{{ lookup('dnstxt', 'example.com') }} is a DNS TXT record for example.com" - - action: debug msg="$item is a value from evaluation of this template" - with_template: - - ./some_template.j2 + - action: debug msg="{{ lookup('template', './some_template.j2') }} is a value from evaluation of this template" -You can also assign these to variables, should you wish to do this instead, that will be evaluated -when they are used in a task (or template):: +As an alternative you can also assign lookup plugins to variables or use them +elsewhere. This macros are evaluated each time they are used in a task (or +template):: vars: - redis_value: $LOOKUP(redis,redis://localhost:6379,info_${inventory_hostname}) - auth_key_value: $FILE(/home/mdehaan/.ssh/id_rsa.pub) + motd_value: "{{ lookup('file', '/etc/motd') }}" tasks: - - debug: msg=Redis value for host is $redis_value + - debug: msg="motd value is {{ motd_value }}" .. versionadded: 1.0 -'with_sequence' generates a sequence of items in ascending numerical order. You +``with_sequence`` generates a sequence of items in ascending numerical order. You can specify a start, end, and an optional step value. -Arguments can be either key-value pairs or as a shortcut in the format -"[start-]end[/stride][:format]". The format is a printf style string. +Arguments should be specified in key=value pairs. If supplied, the 'format' is a printf style string. -Numerical values can be specified in decimal, hexadecimal (0x3f8) or octal (0600). +Numerical values can be specified in decimal, hexadecimal (0x3f8) or octal (0600). Negative numbers are not supported. This works as follows:: --- @@ -534,26 +536,108 @@ Negative numbers are not supported. This works as follows:: - group: name=evens state=present - group: name=odds state=present - # create 32 test users - - user: name=$item state=present groups=odds - with_sequence: 32/2:testuser%02x - - - user: name=$item state=present groups=evens - with_sequence: 2-32/2:testuser%02x + # create some test users + - user: name={{ item }} state=present groups=evens + with_sequence: start=0 end=32 format=testuser%02x - # create a series of directories for some reason - - file: dest=/var/stuff/$item state=directory - with_sequence: start=4 end=16 + # create a series of directories with even numbers for some reason + - file: dest=/var/stuff/{{ item }} state=directory + with_sequence: start=4 end=16 stride=2 # a simpler way to use the sequence plugin # create 4 groups - - group: name=group${item} state=present + - group: name=group{{ item }} state=present with_sequence: count=4 +.. versionadded: 1.1 + +``with_password`` and associated lookup macro generate a random plaintext password and store it in +a file at a given filepath. Support for crypted save modes (as with vars_prompt) are pending. If the +file exists previously, it will retrieve its contents, behaving just like with_file. Usage of variables like "{{ inventory_hostname }}" in the filepath can be used to set +up random passwords per host (what simplifies password management in 'host_vars' variables). + +Generated passwords contain a random mix of upper and lowercase ASCII letters, the +numbers 0-9 and punctuation (". , : - _"). The default length of a generated password is 30 characters. +This length can be changed by passing an extra parameter:: + + --- + - hosts: all + + tasks: + + # create a mysql user with a random password: + - mysql_user: name={{ client }} + password="{{ lookup('password', 'credentials/' + client + '/' + tier + '/' + role + '/mysqlpassword length=15') }}" + priv={{ client }}_{{ tier }}_{{ role }}.*:ALL + + (...) + + # dump a mysql database with a given password (this example showing the other form). + - mysql_db: name={{ client }}_{{ tier }}_{{ role }} + login_user={{ client }} + login_password={{ item }} + state=dump + target=/tmp/{{ client }}_{{ tier }}_{{ role }}_backup.sql + with_password: credentials/{{ client }}/{{ tier }}/{{ role }}/mysqlpassword length=15 + + (...) + + # create a user with a given password + - user: name=guestuser + state=present + uid=5000 + password={{ item }} + with_password: credentials/{{ hostname }}/userpassword encrypt=sha256_crypt + +Setting the Environment (and Working With Proxies) +`````````````````````````````````````````````````` + +.. versionadded: 1.1 + +It is quite possible that you may need to get package updates through a proxy, or even get some package +updates through a proxy and access other packages not through a proxy. Ansible makes it easy for you +to configure your environment by using the 'environment' keyword. Here is an example:: + + - hosts: all + user: root + + tasks: + + - apt: name=cobbler state=installed + environment: + http_proxy: http://proxy.example.com:8080 + +The environment can also be stored in a variable, and accessed like so:: + + - hosts: all + user: root + + # here we make a variable named "env" that is a dictionary + vars: + proxy_env: + http_proxy: http://proxy.example.com:8080 + + tasks: + + - apt: name=cobbler state=installed + environment: "{{ proxy_env }}" + +While just proxy settings were shown above, any number of settings can be supplied. The most logical place +to define an environment hash might be a group_vars file, like so:: + + --- + # file: group_vars/boston + + ntp_server: ntp.bos.example.com + backup: bak.bos.example.com + proxy_env: + http_proxy: http://proxy.bos.example.com:8080 + https_proxy: http://proxy.bos.example.com:8080 + Getting values from files ````````````````````````` -.. versionadded: 0.8 +.. versionadded:: 0.8 Sometimes you'll want to include the content of a file directly into a playbook. You can do so using a macro. This syntax will remain in future versions, though we will also will provide ways to do this via lookup plugins (see "More Loops") as well. What follows @@ -561,14 +645,12 @@ is an example using the authorized_key module, which requires the actual text of tasks: - name: enable key-based ssh access for users - authorized_key: user=$item key='$FILE(/keys/$item)' + authorized_key: user={{ item }} key="{{ lookup('file', '/keys/' + item ) }}" with_items: - pinky - brain - snowball -The "$PIPE" macro works just like file, except you would feed it a command string instead. It executes locally, not remotely, as does $FILE. - Selecting Files And Templates Based On Variables ```````````````````````````````````````````````` @@ -578,9 +660,9 @@ The following construct selects the first available file appropriate for the var The following example shows how to template out a configuration file that was very different between, say, CentOS and Debian:: - name: template a file - action: template src=$item dest=/etc/myapp/foo.conf + action: template src={{ item }} dest=/etc/myapp/foo.conf first_available_file: - - /srv/templates/myapp/${ansible_distribution}.conf + - /srv/templates/myapp/{{ ansible_distribution }}.conf - /srv/templates/myapp/default.conf first_available_file is only available to the copy and template modules. @@ -657,7 +739,7 @@ Turning Off Facts ````````````````` If you know you don't need any fact data about your hosts, and know everything about your systems centrally, you -can turn off fact gathering. This has advantages in scaling ansible in push mode with very large numbers of +can turn off fact gathering. This has advantages in scaling Ansible in push mode with very large numbers of systems, mainly, or if you are using Ansible on experimental platforms. In any play, just do this:: - hosts: whatever @@ -670,8 +752,8 @@ The use of playbooks in local mode (above) is made extremely powerful with the a A script for setting up ansible-pull is provided in the examples/playbooks directory of the source checkout. -The basic idea is to use Ansible 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 push architecture of ansible into a pull +The basic idea is to use Ansible 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 push architecture of Ansible into a pull architecture, which has near-limitless scaling potential. The setup playbook can be tuned to change the cron frequency, logging locations, and parameters to ansible-pull. @@ -685,9 +767,9 @@ Register Variables Often in a playbook it may be useful to store the result of a given command in a variable and access it later. Use of the command module in this way can in many ways eliminate the need to write site specific facts, for -instance, you could test for the existance of a particular program. +instance, you could test for the existence of a particular program. -The 'register' keyword decides what variable to save a result in. The resulting variables can be used in templates, action lines, or only_if statements. It looks like this (in an obviously trivial example):: +The 'register' keyword decides what variable to save a result in. The resulting variables can be used in templates, action lines, or *when* statements. It looks like this (in an obviously trivial example):: - name: test play hosts: all @@ -698,7 +780,7 @@ The 'register' keyword decides what variable to save a result in. The resulting register: motd_contents - action: shell echo "motd contains the word hi" - only_if: "'${motd_contents.stdout}'.find('hi') != -1" + when: motd_contents.stdout.find('hi') != -1 Rolling Updates @@ -706,8 +788,8 @@ Rolling Updates .. versionadded:: 0.7 -By default ansible will try to manage all of the machines referenced in a play in parallel. For a rolling updates -use case, you can define how many hosts ansible should manage at a single time by using the ''serial'' keyword:: +By default, Ansible will try to manage all of the machines referenced in a play in parallel. For a rolling updates +use case, you can define how many hosts Ansible should manage at a single time by using the ''serial'' keyword:: - name: test play @@ -733,30 +815,43 @@ a good idea:: tasks: - name: take out of load balancer pool - action: command /usr/bin/take_out_of_pool $inventory_hostname + action: command /usr/bin/take_out_of_pool {{ inventory_hostname }} delegate_to: 127.0.0.1 - name: actual steps would go here action: yum name=acme-web-stack state=latest - name: add back to load balancer pool - action: command /usr/bin/add_back_to_pool $inventory_hostname + action: command /usr/bin/add_back_to_pool {{ inventory_hostname }} delegate_to: 127.0.0.1 -Here is the same playbook as above, but using the shorthand syntax, -'local_action', for delegating to 127.0.0.1:: +These commands will run on 127.0.0.1, which is the machine running Ansible. There is also a shorthand syntax that +you can use on a per-task basis: 'local_action'. Here is the same playbook as above, but using the shorthand +syntax for delegating to 127.0.0.1:: --- # ... tasks: - name: take out of load balancer pool - local_action: command /usr/bin/take_out_of_pool $inventory_hostname + local_action: command /usr/bin/take_out_of_pool {{ inventory_hostname }} # ... - name: add back to load balancer pool - local_action: command /usr/bin/add_back_to_pool $inventory_hostname + local_action: command /usr/bin/add_back_to_pool {{ inventory_hostname }} + +A common pattern is to use a local action to call 'rsync' to recursively copy files to the managed servers. +Here is an example:: + + --- + # ... + tasks: + - name: recursively copy files from management server to target + local_action: command rsync -a /path/to/files {{ inventory_hostname }}:/path/to/target/ + +Note that you must have passphrase-less SSH keys or an ssh-agent configured for this to work, otherwise rsync +will need to ask for a passphrase. Fireball Mode ````````````` @@ -765,11 +860,11 @@ Fireball Mode Ansible's core connection types of 'local', 'paramiko', and 'ssh' are augmented in version 0.8 and later by a new extra-fast connection type called 'fireball'. It can only be used with playbooks and does require some additional setup -outside the lines of ansible's normal "no bootstrapping" philosophy. You are not required to use fireball mode +outside the lines of Ansible's normal "no bootstrapping" philosophy. You are not required to use fireball mode to use Ansible, though some users may appreciate it. Fireball mode works by launching a temporary 0mq daemon from SSH that by default lives for only 30 minutes before -shutting off. Fireball mode once running uses temporary AES keys to encrypt a session, and requires direct +shutting off. Fireball mode, once running, uses temporary AES keys to encrypt a session, and requires direct communication to given nodes on the configured port. The default is 5099. The fireball daemon runs as any user you set it down as. So it can run as you, root, or so on. If multiple users are running Ansible as the same batch of hosts, take care to use unique ports. @@ -791,7 +886,7 @@ if you have a large number of hosts:: - hosts: all connection: fireball tasks: - - action: shell echo "Hello ${item}" + - action: shell echo "Hello {{ item }}" with_items: - one - two @@ -806,7 +901,7 @@ any platform. You will also need gcc and zeromq-devel installed from your packa connection: ssh tasks: - action: easy_install name=pip - - action: pip name=$item state=present + - action: pip name={{ item }} state=present with_items: - pyzmq - pyasn1 @@ -821,30 +916,106 @@ Also see the module documentation section. Understanding Variable Precedence ````````````````````````````````` -You have already learned about inventory host and group variables, 'vars', and 'vars_files'. +You have already learned about inventory variables, 'vars', and 'vars_files'. In the +event the same variable name occurs in more than one place, what happens? There are really three tiers +of precedence, and within those tiers, some minor ordering rules that you probably won't even need to remember. +We'll explain them anyway though. -If a variable name is defined in more than one place with the same name, priority is as follows -to determine which place sets the value of the variable. Lower numbered items have the highest -priority. +Variables that are set during the execution of the play have highest priority. This includes registered +variables and facts, which are discovered pieces of information about remote hosts. -1. Any variables specified with --extra-vars (-e) on the ansible-playbook command line. +Descending in priority are variables defined in the playbook. 'vars_files' as defined in the playbook are next up, +followed by variables as passed to ansible-playbook via --extra-vars (-e), then variables defined in the 'vars' section. These +should all be taken to be basically the same thing -- good places to define constants about what the play does to all hosts +in the play. -2. Variables loaded from YAML files mentioned in 'vars_files' in a playbook. +Finally, inventory variables have the least priority. Variables about hosts override those about groups. +If a variable is defined in multiple groups and one group is a child of the other, the child group variable +will override the variable set in the parent. -3. facts, whether built in or custom, or variables assigned from the 'register' keyword. +This makes the 'group_vars/all' file the best place to define a default value you wish to override in another +group, or even in a playbook. For example, your organization might set a default ntp server in group_vars/all +and then override it based on a group based on a geographic region. However if you type 'ntpserver: asdf.example.com' +in a vars section of a playbook, you know from reading the playbook that THAT specific value is definitely the one +that is going to be used. You won't be fooled by some variable from inventory sneaking up on you. + +So, in short, if you want something easy to remember: facts beat playbook definitions, and +playbook definitions beat inventory variables. + + +Check Mode ("Dry Run") --check +``````````````````````````````` -4. variables passed to parameterized task include statements. +.. versionadded:: 1.1 -5. 'vars' as defined in the playbook. +When ansible-playbook is executed with --check it will not make any changes on remote systems. Instead, any module +instrumented to support 'check mode' (which contains the primary core modules, but it is not required that all modules do +this) will report what changes they would have made. Other modules that do not support check mode will also take no +action, but just will not report what changes they might have made. -6. Host variables from inventory. +Check mode is just a simulation, and if you have steps that use conditionals that depend on the results of prior commands, +it may be less useful for you. However it is great for one-node-at-time basic configuration management use cases. -7. Group variables from inventory in inheritance order. This means if a group includes a sub-group, the variables -in the subgroup have higher precedence. +Example:: + + ansible-playbook foo.yml --check + +Showing Differences with --diff +``````````````````````````````` + +.. versionadded:: 1.1 + +The --diff option to ansible-playbook works great with --check (detailed above) but can also be used by itself. When this flag is supplied, if any templated files on the remote system are changed, and the ansible-playbook CLI will report back +the textual changes made to the file (or, if used with --check, the changes that would have been made). Since the diff +feature produces a large amount of output, it is best used when checking a single host at a time, like so:: + + ansible-playbook foo.yml --check --diff --limit foo.example.com + +Dictionary & Nested (Complex) Arguments +``````````````````````````````````````` + +As a review, most tasks in Ansible are of this form:: + + tasks: + + - name: ensure the cobbler package is installed + yum: name=cobbler state=installed + +However, in some cases, it may be useful to feed arguments directly in from a hash (dictionary). In fact, a very small +number of modules (the CloudFormations module is one) actually require complex arguments. They work like this:: + + tasks: -Therefore, if you want to set a default value for something you wish to override somewhere else, the best -place to set such a default is in a group variable. The 'group_vars/all' file makes an excellent place to put global -variables that are true across your entire site, since everything has higher priority than these values. + - name: call a module that requires some complex arguments + foo_module: + fibonacci_list: + - 1 + - 1 + - 2 + - 3 + my_pets: + dogs: + - fido + - woof + fish: + - limpet + - nemo + - "{{ other_fish_name }}" + +You can of course use variables inside these, as noted above. + +If using local_action, you can do this:: + + - name: call a module that requires some complex arguments + local_action: + module: foo_module + arg1: 1234 + arg2: 'asdf' + +Which of course means, though more verbose, this is also technically legal syntax:: + + - name: foo + template: { src: '/templates/motd.j2', dest: '/etc/motd' } Style Points ```````````` diff --git a/docsite/rst/bestpractices.rst b/docsite/rst/bestpractices.rst deleted file mode 100644 index e4394745d89ca7..00000000000000 --- a/docsite/rst/bestpractices.rst +++ /dev/null @@ -1,143 +0,0 @@ -Best Practices -============== - -Here are some tips for making the most of Ansible. - -.. contents:: - :depth: 2 - :backlinks: top - -Always Mention State -++++++++++++++++++++ - -The 'state' parameter is optional to a lot of modules. Whether -'state=present' or 'state=absent', it's always best to leave that -parameter in your playbooks to make it clear, especially as some -modules support additional states. - -Group By Roles -++++++++++++++ - -A system can be in multiple groups. See :doc:`patterns`. Having groups named after things like -*webservers* and *dbservers* is repeated in the examples because it's a very powerful concept. - -This allows playbooks to target machines based on role, as well as to assign role specific variables -using the group variable system. - -Directory Organization -++++++++++++++++++++++ - -Playbooks should be organized like this:: - - # root of source control repository - ├── acme/ - │ ├── files/ - │ │ └── some_file_path_foo.conf - │ ├── handlers/ - │ │ └── main.yml - │ ├── tasks/ - │ │ ├── setup.yml - │ │ └── stop.yml - │ ├── templates/ - │ │ ├── etc_acme_conf_acme.conf - │ │ └── etc_other_conf_other.conf - │ ├── vars/ - │ │ └── main.yml - │ ├── setup.yml - │ └── stop.yml - └── global_vars.yml - -Any directories or files not needed can be omitted. Not all modules -may require 'vars' or 'files' sections, though most will require -'handlers', 'tasks', and 'templates'. To review what each of -these sections do, see :doc:`playbooks` and :doc:`playbooks2`. - -The acme/setup.yml playbook would be as simple as:: - - --- - - hosts: webservers - user: root - - vars_files: - - ../global_vars.yml - - vars/main.yml - tasks: - - include: tasks/setup.yml - handlers: - - include: handlers/main.yml - -The tasks are individually broken out in 'acme/tasks/setup.yml', and handlers, which are common to all task files, -are contained in 'acme/handlers/main.yml'. As a reminder, handlers are mostly just used to notify services to restart -when things change, and these are described in :doc:`playbooks`. - -Including more than one setup file or more than one handlers file is of course legal. - -Bundling Ansible Modules With Playbooks -+++++++++++++++++++++++++++++++++++++++ - -.. versionadded:: 0.5 - -If a playbook has a "./library" directory relative to it's YAML file, -this directory can be used to add ansible modules that will -automatically be in the ansible module path. This is a great way to -keep modules that go with a playbook together. - -Miscellaneous Tips -++++++++++++++++++ - -When you can do something simply, do something simply. Do not reach -to use every feature of Ansible together, all at once. Use what works -for you. For example, you should probably not need 'vars', -'vars_files', 'vars_prompt' and '--extra-vars' all at once, -while also using an external inventory file. - -Optimize for readability. Whitespace between sections of YAML -documents and in between tasks is strongly encouraged, as is usage of -YAML comments, which start with '#'. It is also useful to comment -at the top of each file the purpose of the individual file and the -author, including email address. - -It is possible to leave off the 'name' for a given task, though it -is recommended to provide a descriptive comment about why something is -being done instead. - -Use version control. Keep your playbooks and inventory file in git -(or another version control system), and commit when you make changes -to them. This way you have an audit trail describing when and why you -changed the rules automating your infrastructure. - -Resist the urge to write the same playbooks and configuration files -for heterogeneous distributions. While lots of software packages -claim to make this easy on you, the configuration files are often -quite different, to the point where it would be easier to treat them -as different playbooks. This is why, for example, Ansible has a -separate 'yum' and 'apt' module. Yum and apt have different -capabilities, and we don't want to code for the least common -denominator. - -Use variables for user tunable settings versus having constants in the -tasks file or templates, so that it is easy to reconfigure a playbook. -Think about this as exposing the knobs to things you would like to -tweak. - -Since a system can be in more than one group, if you have multiple -datacenters or sites, consider putting systems into groups by role, -but also different groups by geography. This allows you to assign -different variables to different geographies. - -.. seealso:: - - :doc:`YAMLSyntax` - Learn about YAML syntax - :doc:`playbooks` - Review the basic playbook features - :doc:`modules` - Learn about available modules - :doc:`moduledev` - Learn how to extend Ansible by writing your own modules - :doc:`patterns` - Learn about how to select hosts - `Github examples directory `_ - Complete playbook files from the github project source - `Mailing List `_ - Questions? Help? Ideas? Stop by the list on Google Groups diff --git a/docsite/rst/gettingstarted.rst b/docsite/rst/gettingstarted.rst deleted file mode 100644 index da72d1958b9275..00000000000000 --- a/docsite/rst/gettingstarted.rst +++ /dev/null @@ -1,249 +0,0 @@ -Getting Started -=============== - -.. contents:: - :depth: 2 - :backlinks: top - -Requirements -```````````` - -Requirements for Ansible are extremely minimal. - -Ansible is written for Python 2.6. If you are running Python 2.5 on an "Enterprise Linux" variant, -your distribution can easily install 2.6 (see instructions in the next section). Newer versions -of Linux and OS X should already have 2.6. - -In additon to Python 2.6, you will want the following Python modules (installed via pip or perhaps via your OS package manager via slightly different names): - -* ``paramiko`` -* ``PyYAML`` -* ``jinja2`` - -On the managed nodes, you only need Python 2.4 or later, but if you are are running less than Python 2.6 on them, you will -also need: - -* ``python-simplejson`` - -.. note:: - - Ansible's "raw" module (for executing commands in a quick and dirty - way) and the copy module -- some of the most basic features in - ansible -- don't even need that. So technically, you can use - Ansible to install python-simplejson using the raw module, which - then allows you to use everything else. (That's jumping ahead - though.) - -Python 2.6 EPEL instructions for RHEL and CentOS 5 -`````````````````````````````````````````````````` - -These distributions don't have Python 2.6 by default, but it is easily -installable. If you have not already done so, `configure EPEL -`_ - -.. code-block:: bash - - $ yum install python26 python26-PyYAML python26-paramiko python26-jinja2 - -Getting Ansible -``````````````` - -If you are interested in using all the latest features, you may wish to keep up to date -with the development branch of the git checkout. This also makes it easiest to contribute -back to the project. - -Instructions for installing from source are below. - -Ansible's release cycles are about one month long. Due to this -short release cycle, any bugs will generally be fixed in the next release versus maintaining -backports on the stable branch. - -You may also wish to follow the `Github project `_ if -you have a github account. This is also where we keep the issue tracker for sharing -bugs and feature ideas. - - -Running From Checkout -+++++++++++++++++++++ - -Ansible is trivially easy to run from a checkout, root permissions are not required -to use it: - -.. code-block:: bash - - $ git clone git://github.com/ansible/ansible.git - $ cd ./ansible - $ source ./hacking/env-setup - -You can optionally specify an inventory file (see :doc:`patterns`) other than /etc/ansible/hosts: - -.. code-block:: bash - - $ echo "127.0.0.1" > ~/ansible_hosts - $ export ANSIBLE_HOSTS=~/ansible_hosts - -Now let's test things: - -.. code-block:: bash - - $ ansible all -m ping --ask-pass - - -Make Install -++++++++++++ - -If you are not working from a distribution where Ansible is packaged yet, you can install Ansible -using "make install". This is done through `python-distutils`: - -.. code-block:: bash - - $ git clone git://github.com/ansible/ansible.git - $ cd ./ansible - $ sudo make install - - -Via RPM -+++++++ - -RPMs for the last Ansible release are available for `EPEL -`_ 6 and currently supported -Fedora distributions. Ansible itself can manage earlier operating -systems that contain python 2.4 or higher. - -.. code-block:: bash - - # install the epel-release RPM if needed on CentOS, RHEL, or Scientific Linux - $ sudo yum install ansible - -You can also use the ``make rpm`` command to build an RPM you can distribute and install. -Make sure you have ``rpm-build``, ``make``, and ``python2-devel`` installed. - -.. code-block:: bash - - $ git clone git://github.com/ansible/ansible.git - $ cd ./ansible - $ make rpm - $ sudo rpm -Uvh ~/rpmbuild/ansible-*.noarch.rpm - -Debian, Gentoo, Arch, Others -++++++++++++++++++++++++++++ - -Ubuntu builds are available `in a PPA here `_ - -Debian/Ubuntu package recipes can also be built from the source checkout, run: - -.. code-block:: bash - - $ make debian - -Gentoo eBuilds are available `on github here `_ - -An Arch PKGBUILD is available on `AUR `_ -If you have python3 installed on Arch, you probably want to symlink python to python2: - -.. code-block:: bash - - $ sudo ln -sf /usr/bin/python2 /usr/bin/python - -If you would like to package Ansible for Homebrew, BSD, or others, -please stop by the mailing list and say hi! - - -Tagged Releases -+++++++++++++++ - -Tagged releases are available as tar.gz files from the Ansible github -project page: - -* `Ansible/downloads `_ - -Choosing Between Paramiko and Native SSH -```````````````````````````````````````` - -By default, ansible uses paramiko to talk to managed nodes over SSH. Paramiko is fast, works -very transparently, requires no configuration, and is a good choice for most users. -However, it does not support some advanced SSH features that folks will want to use. - -.. versionadded:: 0.5 - -If you want to leverage more advanced SSH features (such as Kerberized -SSH or jump hosts), pass the flag "--connection=ssh" to any ansible -command, or set the ANSIBLE_TRANSPORT environment variable to -'ssh'. This will cause Ansible to use openssh tools instead. - -If ANSIBLE_SSH_ARGS are not set, ansible will try to use some sensible ControlMaster options -by default. You are free to override this environment variable, but should still pass ControlMaster -options to ensure performance of this transport. With ControlMaster in use, both transports -are roughly the same speed. Without CM, the binary ssh transport is signficantly slower. - -If none of this makes sense to you, the default paramiko option is probably fine. - -Your first commands -``````````````````` - -Now that you've installed Ansible, it's time to test it. - -Edit (or create) /etc/ansible/hosts and put one or more remote systems in it, for -which you have your SSH key in ``authorized_keys``:: - - 192.168.1.50 - aserver.example.org - bserver.example.org - -Set up SSH agent to avoid retyping passwords: - -.. code-block:: bash - - $ ssh-agent bash - $ ssh-add ~/.ssh/id_rsa - -(Depending on your setup, you may wish to ansible's --private-key option to specify a pem file instead) - -Now ping all your nodes: - -.. code-block:: bash - - $ ansible all -m ping - -Ansible will attempt to remote connect to the machines using your current -user name, just like SSH would. To override the remote user name, just use the '-u' parameter. - -If you would like to access sudo mode, there are also flags to do that: - -.. code-block:: bash - - # as bruce - $ ansible all -m ping -u bruce - # as bruce, sudoing to root - $ ansible all -m ping -u bruce --sudo - # as bruce, sudoing to batman - $ ansible all -m ping -u bruce --sudo --sudo-user batman - -(The sudo implementation is changeable in ansbile's configuration file if you happen to want to use a sudo -replacement. Flags passed dot sudo can also be set.) - -Now run a live command on all of your nodes: - -.. code-block:: bash - - $ ansible all -a "/bin/echo hello" - -Congratulations. You've just contacted your nodes with Ansible. It's -now time to read some of the more real-world :doc:`examples`, and explore -what you can do with different modules, as well as the Ansible -:doc:`playbooks` language. Ansible is not just about running commands, it -also has powerful configuration management and deployment features. There's more to -explore, but you already have a fully working infrastructure! - - -.. seealso:: - - :doc:`examples` - Examples of basic commands - :doc:`playbooks` - Learning ansible's configuration management language - `Mailing List `_ - Questions? Help? Ideas? Stop by the list on Google Groups - `irc.freenode.net `_ - #ansible IRC chat channel - diff --git a/docsite/rst/who_uses_ansible.rst b/docsite/rst/who_uses_ansible.rst deleted file mode 100644 index 66cde46a4d08d1..00000000000000 --- a/docsite/rst/who_uses_ansible.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. _who_uses_ansible: - -Who Uses Ansible -================ - -The following is an opt-in list of just some of the folks using Ansible -- to get your company details here, `Email Michael `_. - - * `Alta Language Services `_ - language testing, solutions, and translation services - * `Aspiro TV `_ - Media Streaming - * `Basho `_ - makers of NoSQL engine Riak - * `CatN Hosting `_ - scalable Cloud hosting - * `Cygate AB `_ - IT solutions from Malmo, Sweeden - * `D square NV `_ - Process industry data mining, Belgium - * `Dag IT Solutions `_ - Enterprise Linux & Beyond - * `Duke University Economics `_ - Research & Education - * `Eucalyptus Systems Inc. `_ - Makers of the AWS-compatible private cloud platform - * `The Fedora Project `_ - Produces the Popular Linux Distribution - * `Four Kitchens `_ - The Drupal Experts - * `HP (Germany) `_ - HP - * `Lizenfrei.at `_ - Open Source consulting and development, Austria - * `Schmooze Com `_ - Creators of Industry Leading PBX Platforms - * `Scientific Computing Center, Aristotle Univ. of Thessaloniki `_ - Grid/Cloud-Based Scientific Computing - * `Skylines.io `_ - Real time photo search engine - * `Steelhouse `_ - Behavioral commerce - * `Tranquil Hosting `_ - Managed Linux Hosting Services - * `Tomorrow Focus Technologies GmbH `_ - Running some of the biggest web sites in Europe - * `123i.com.br `_ - Find real estate in Brazil - * `Ginsys `_ - Linux infrastructure consulting from Belgium - * `Action.IO `_ - Action.IO Cloud Development Platform - diff --git a/examples/DOCUMENTATION.yaml b/examples/DOCUMENTATION.yaml index 55f7a1e0a60489..05e248edfdda10 100644 --- a/examples/DOCUMENTATION.yaml +++ b/examples/DOCUMENTATION.yaml @@ -7,7 +7,7 @@ short_description: This is a sentence describing the module description: - Longer description of the module - You might include instructions -version_added: 0.X +version_added: "X.Y" author: Your AWESOME name here notes: - Other things consumers of your module should know @@ -26,8 +26,4 @@ options: default: a string or the word null choices: [list, of, choices] aliases: [list, of, aliases] - version_added: 0.X -examples: -# One or more of the following: - - code: modulename opt1=arg1 opt2=arg2 - description: Optional words describing this example + version_added: 1.X diff --git a/examples/ansible.cfg b/examples/ansible.cfg index ab8dd20c4f4319..3b3c324f1237fa 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -1,109 +1,82 @@ -# config file for ansible -- http://ansible.github.com -# nearly all parameters can be overridden in ansible-playbook or with command line flags -# ansible will read ~/.ansible.cfg or /etc/ansible/ansible.cfg, whichever it finds first +# config file for ansible -- http://ansibleworks.com/ +# ================================================== -[defaults] - -# location of inventory file, eliminates need to specify -i - -hostfile = /etc/ansible/hosts - -# location of ansible library, eliminates need to specify --module-path - -library = /usr/share/ansible - -# default module name used in /usr/bin/ansible when -m is not specified - -module_name = command - -# home directory where temp files are stored on remote systems. Should -# almost always contain $HOME or be a directory writeable by all users - -remote_tmp = $HOME/.ansible/tmp - -# the default pattern for ansible-playbooks ("hosts:") - -pattern = * - -# the default number of forks (parallelism) to be used. Usually you -# can crank this up. - -forks=5 +# nearly all parameters can be overridden in ansible-playbook +# or with command line flags. ansible will read ~/.ansible.cfg, +# ansible.cfg in the current working directory or +# /etc/ansible/ansible.cfg, whichever it finds first -# the timeout used by various connection types. Usually this corresponds -# to an SSH timeout - -timeout=10 - -# when using --poll or "poll:" in an ansible playbook, and not specifying -# an explicit poll interval, use this interval - -poll_interval=15 - -# when specifying --sudo to /usr/bin/ansible or "sudo:" in a playbook, -# and not specifying "--sudo-user" or "sudo_user" respectively, sudo -# to this user account - -sudo_user=root - -# the following forces ansible to always ask for the sudo password (instead of having -# to add -K to the commandline). Or you can use the environment variable (ANSIBLE_ASK_SUDO_PASS) - -#ask_sudo_pass=True +[defaults] -# the following forces ansible to always ask for the ssh-password (-k) -# can also be set by the environment variable ANSIBLE_ASK_PASS +# some basic default values... -#ask_pass=True +hostfile = /etc/ansible/hosts +library = /usr/share/ansible +remote_tmp = $HOME/.ansible/tmp +pattern = * +forks = 5 +poll_interval = 15 +sudo_user = root +#ask_sudo_pass = True +#ask_pass = True +transport = smart +remote_port = 22 -# connection to use when -c is not specified +# uncomment this to disable SSH key host checking +#host_key_checking = False -transport=paramiko +# change this for alternative sudo implementations +sudo_exe = sudo -# remote SSH port to be used when --port or "port:" or an equivalent inventory -# variable is not specified. +# what flags to pass to sudo +#sudo_flags = -H -remote_port=22 +# SSH timeout +timeout = 10 -# if set, always run /usr/bin/ansible commands as this user, and assume this value -# if "user:" is not set in a playbook. If not set, use the current Unix user -# as the default +# default user to use for playbooks if user is not specified +# (/usr/bin/ansible will use current user as default) +#remote_user = root -#remote_user=root +# logging is off by default unless this path is defined +# if so defined, consider logrotate +#log_path = /var/log/ansible.log -# the default sudo executable. If a sudo alternative with a sudo-compatible interface -# is used, specify its executable name as the default +# default module name for /usr/bin/ansible +#module_name = command -sudo_exe=sudo +# use this shell for commands executed under sudo +# you may need to change this to bin/bash in rare instances +# if sudo is constrained +#executable = /bin/sh -# the default flags passed to sudo -# sudo_flags=-H +# if inventory variables overlap, does the higher precedence one win +# or are hash values merged together? The default is 'replace' but +# this can also be set to 'merge'. +#hash_behaviour = replace -# how to handle hash defined in several places -# hash can be merged, or replaced -# if you use replace, and have multiple hashes named 'x', the last defined -# will override the previously defined one -# if you use merge here, hash will cumulate their keys, but keys will still -# override each other -# replace is the default value, and is how ansible always handled hash variables -# -# hash_behaviour=replace +# How to handle variable replacement - as of 1.2, Jinja2 variable syntax is +# preferred, but we still support the old $variable replacement too. +# Turn off ${old_style} variables here if you like. +#legacy_playbook_variables = yes -# if set, always use this private key file for authentication, same as if passing -# --private-key to ansible or ansible-playbook +# list any Jinja2 extensions to enable here: +#jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n -#private_key_file=/path/to/file +# if set, always use this private key file for authentication, same as +# if passing --private-key to ansible or ansible-playbook +#private_key_file = /path/to/file -# format of string $ansible_managed available within Jinja2 templates, replacing -# {file}, {host} and {uid} with template filename, host and owner respectively. -# The resulting string is passed through strftime(3) so it may contain any -# time-formatting specifiers. -# -# Example: ansible_managed = DONT TOUCH {file}: call {uid} at {host} for changes +# format of string {{ ansible_managed }} available within Jinja2 +# templates indicates to users editing templates files will be replaced. +# replacing {file}, {host} and {uid} and strftime codes with proper values. ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host} -# additional plugin paths for non-core plugins +# if set, Ansible will raise errors when attempting to redeference Jinja2 +# variables that are not set in templates or action lines. +#error_on_undefined_vars = True +# set plugin path directories here, seperate with colons action_plugins = /usr/share/ansible_plugins/action_plugins callback_plugins = /usr/share/ansible_plugins/callback_plugins connection_plugins = /usr/share/ansible_plugins/connection_plugins @@ -111,20 +84,27 @@ lookup_plugins = /usr/share/ansible_plugins/lookup_plugins vars_plugins = /usr/share/ansible_plugins/vars_plugins filter_plugins = /usr/share/ansible_plugins/filter_plugins -[paramiko_connection] +# don't like cows? that's unfortunate. +# set to 1 if you don't want cowsay support or export ANSIBLE_NOCOWS=1 +#nocows = 1 -# nothing to configure yet +[paramiko_connection] -[ssh_connection] +# uncomment this line to cause the paramiko connection plugin to not record new host +# keys encountered. Increases performance on new host additions. Setting works independently of the +# host key checking setting above. -# if uncommented, sets the ansible ssh arguments to the following. Leaving off ControlPersist -# will result in poor performance, so use transport=paramiko on older platforms rather than -# removing it +#record_host_keys=False -ssh_args=-o PasswordAuthentication=no -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r +[ssh_connection] -# the following makes ansible use scp if the connection type is ssh (default is sftp) +# ssh arguments to use +# Leaving off ControlPersist will result in poor performance, so use +# paramiko on older platforms rather than removing it +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r -#scp_if_ssh=True +# if True, make ansible use scp if the connection type is ssh +# (default is sftp) +#scp_if_ssh = True diff --git a/examples/playbooks/README.md b/examples/playbooks/README.md new file mode 100644 index 00000000000000..1f75b9fc418fac --- /dev/null +++ b/examples/playbooks/README.md @@ -0,0 +1,8 @@ +Playbook Examples +================= + +Playbook examples have moved. + +See [the Ansible-Examples repo](https://github.com/ansible/ansible-examples/tree/master/language_features). + + diff --git a/examples/playbooks/ansible_pull.yml b/examples/playbooks/ansible_pull.yml deleted file mode 100644 index 2f8d0ab4bd1e26..00000000000000 --- a/examples/playbooks/ansible_pull.yml +++ /dev/null @@ -1,56 +0,0 @@ -# ansible-pull setup -# -# on remote hosts, set up ansible to run periodically using the latest code -# from a particular checkout, in pull based fashion, inverting Ansible's -# usual push-based operating mode. -# -# This particular pull based mode is ideal for: -# -# (A) massive scale out -# (B) continual system remediation -# -# DO NOT RUN THIS AGAINST YOUR HOSTS WITHOUT CHANGING THE repo_url -# TO SOMETHING YOU HAVE PERSONALLY VERIFIED -# -# ---- - -- hosts: pull_mode_hosts - user: root - - vars: - - # schedule is fed directly to cron - schedule: '*/15 * * * *' - - # User to run ansible-pull as from cron - cron_user: root - - # File that ansible will use for logs - logfile: /var/log/ansible-pull.log - - # Directory to where repository will be cloned - workdir: /var/lib/ansible/local - - # Repository to check out -- YOU MUST CHANGE THIS - # repo must contain a local.yml file at top level - #repo_url: git://github.com/sfromm/ansible-playbooks.git - repo_url: SUPPLY_YOUR_OWN_GIT_URL_HERE - - tasks: - - - name: Install ansible - action: yum pkg=ansible state=installed - - - name: Create local directory to work from - action: file path=$workdir state=directory owner=root group=root mode=0751 - - - name: Copy ansible inventory file to client - action: copy src=/etc/ansible/hosts dest=/etc/ansible/hosts - owner=root group=root mode=0644 - - - name: Create crontab entry to clone/pull git repository - action: template src=templates/etc_cron.d_ansible-pull.j2 dest=/etc/cron.d/ansible-pull owner=root group=root mode=0644 - - - name: Create logrotate entry for ansible-pull.log - action: template src=templates/etc_logrotate.d_ansible-pull.j2 dest=/etc/logrotate.d/ansible-pull owner=root group=root mode=0644 diff --git a/examples/playbooks/batch_size_control.yml b/examples/playbooks/batch_size_control.yml deleted file mode 100644 index 970bc2a2a19317..00000000000000 --- a/examples/playbooks/batch_size_control.yml +++ /dev/null @@ -1,19 +0,0 @@ -# ordinarily, without the 'serial' keyword set, ansible will control all of your machines in a play at once, in parallel. -# if you want to perform a rolling update, so that each play completes all the way through on a certain number of hosts -# before moving on to the remaining hosts, use the 'serial' keyword like so: - ---- -- hosts: all - serial: 3 - -# now each of the tasks below will complete on 3 hosts before moving on to the next 3, regardless of how many -# hosts are selected by the "hosts:" line - - tasks: - - - name: ping - action: ping - - name: ping2 - action: ping - - diff --git a/examples/playbooks/conditionals_part1.yml b/examples/playbooks/conditionals_part1.yml deleted file mode 100644 index 87b8350e101cdd..00000000000000 --- a/examples/playbooks/conditionals_part1.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# this is a demo of conditional imports. This is a powerful concept -# and can be used to use the same recipe for different types of hosts, -# based on variables that bubble up from the hosts from tools such -# as ohai or facter. -# -# Here's an example use case: -# -# what to do if the service for apache is named 'httpd' on CentOS -# but is named 'apache' on Debian? - - -# there is only one play in this playbook, it runs on all hosts -# as root - -- hosts: all - user: root - -# we have a common list of variables stored in /vars/external_vars.yml -# that we will always import - -# next, we want to import files that are different per operating system -# and if no per operating system file is found, load a defaults file. -# for instance, if the OS was "CentOS", we'd try to load vars/CentOS.yml. -# if that was found, we would immediately stop. However if that wasn't -# present, we'd try to load vars/defaults.yml. If that in turn was not -# found, we would fail immediately, because we had gotten to the end of -# the list without importing anything. - - vars_files: - - - - "vars/external_vars.yml" - - - [ "vars/$facter_operatingsystem.yml", "vars/defaults.yml" ] - -# and this is just a regular task line from a playbook, as we're used to. -# but with variables in it that come from above. Note that the variables -# from above are *also* available in templates - - tasks: - - - name: ensure apache is latest - action: $packager pkg=$apache state=latest - - name: ensure apache is running - action: service name=$apache state=running - - name: fail - action: command /bin/false - - - diff --git a/examples/playbooks/conditionals_part2.yml b/examples/playbooks/conditionals_part2.yml deleted file mode 100644 index 3f673548d343f4..00000000000000 --- a/examples/playbooks/conditionals_part2.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -# this is a demo of conditional executions using 'when' statements, which can skip -# certain tasks on machines/platforms/etc where they do not apply. - -- hosts: all - user: root - - vars: - favcolor: "red" - dog: "fido" - cat: "whiskers" - ssn: 8675309 - -# These are the types of when statemnets available -# when_set: $variable_name -# when_unset: $variable_name -# when_str: $x == "test" -# when_int: $y > 2 -# when_float: $z => 2.3 -# -# when using 'when', take care to make sure any variables given are surrounded by spaces -# as an example, $z>3 will not do what you want, use "$z > 3" -# -# note, if you are doing comparisons to variables that are stored as hashes or lists, -# you will need to use the older 'only_if', which is more free form. -# see conditionals_part_3.yml - - tasks: - - - name: "do this if my favcolor is blue, and my dog is named fido" - action: shell /bin/false - when_string: $favcolor == 'blue' and $dog == 'fido' - - - name: "do this if my favcolor is not blue, and my dog is named fido" - action: shell /bin/true - when_string: $favcolor != 'blue' and $dog == 'fido' - - - name: "do this if my SSN is over 9000" - action: shell /bin/true - when_integer: $ssn > 9000 - - - name: "do this if I have one of these SSNs" - action: shell /bin/true - when_integer: $ssn in [ 8675309, 8675310, 8675311 ] - - - name: "do this if a variable named hippo is NOT defined" - action: shell /bin/true - when_unset: $hippo - - - name: "do this if a variable named hippo is defined" - action: shell /bin/true - when_set: $hippo - - diff --git a/examples/playbooks/conditionals_part3.yml b/examples/playbooks/conditionals_part3.yml deleted file mode 100644 index 30306ef3e1e5e4..00000000000000 --- a/examples/playbooks/conditionals_part3.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -# this is a demo of conditional executions using 'only_if', which can skip -# certain tasks on machines/platforms/etc where they do not apply. This is -# the more 'raw' version of the 'when' statement, most users will be able to -# use 'when' directly. 'only_if' is an older feature, and useful for when -# you need more advanced expression control. - -- hosts: all - user: root - - vars: - favcolor: "red" - ssn: 8675309 - -# Below we're going to define some expressions. -# -# Not only can we assign variables for reuse, but we can also assign conditional -# expressions. By keeping these in 'vars', the task section remains -# extraordinarily clean, and not littered with programming language -# constructs -- so it's easily skimmed by humans. -# -# Remember to quote any variables if they are not numbers! -# -# Interesting fact: aside from the $variables, these expressions are actually -# tiny bits of Python. They are evaluated in the context of each host, so different -# steps can be skipped on different hosts! They should evaluate to either True -# or False - - is_favcolor_blue: "'$favcolor' == 'blue'" - is_centos: "'$facter_operatingsystem' == 'CentOS'" - -# NOTE: -# -# setup module values, facter and ohai variables can be used in only_if statements too -# ex: "'$facter_operatingsystem' == 'CentOS'", which bubble up automatically -# from the managed machines. This example doesn't do that though. - - tasks: - - - name: "do this if my favcolor is blue" - action: shell /bin/false - only_if: '$is_favcolor_blue' - - - name: "do this if my favcolor is not blue" - action: shell /bin/true - only_if: 'not ($is_favcolor_blue)' - diff --git a/examples/playbooks/custom_filters.yml b/examples/playbooks/custom_filters.yml deleted file mode 100644 index 2bc8feffa0ce65..00000000000000 --- a/examples/playbooks/custom_filters.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- - -- name: Demonstrate custom jinja2 filters - hosts: all - tasks: - - action: template src=templates/custom-filters.j2 dest=/tmp/custom-filters.txt diff --git a/examples/playbooks/delegation.yml b/examples/playbooks/delegation.yml deleted file mode 100644 index b1f9a93455f1d0..00000000000000 --- a/examples/playbooks/delegation.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- - -# this is an example of how we can perform actions on a given host on behalf of all the hosts -# in a play. -# -# The two main uses of this would be signalling an outage window for hosts that -# we are going to start upgrading, or to take a machine out of rotation by talking to a load -# balancer. -# -# This example cheats by replacing the load balancer script with the 'echo' command, -# leaving actual communication with the load balancer as an exercise to the reader. In reality, -# you could call anything you want, the main thing is that it should do something with -# $inventory_hostname - -# NOTE: see batch_size_control.yml for an example of the 'serial' keyword, which you almost certainly -# want to use in this kind of example. Here we have a mocked up example that does something to -# 5 hosts at a time - -- hosts: all - serial: 5 - - tasks: - - - name: take the machine out of rotation - action: command echo taking out of rotation $inventory_hostname - delegate_to: 127.0.0.1 - -# here's an alternate notation if you are delegating to 127.0.0.1, you can use 'local_action' -# instead of 'action' and leave off the 'delegate_to' part. -# -# - local_action: command echo taking out of rotation $inventory_hostname - - - name: do several things on the actual host - action: command echo hi mom $inventory_hostname - - - name: put machine back into rotation - action: command echo inserting into rotation $inventory_hostname - delegate_to: 127.0.0.1 - diff --git a/examples/playbooks/file_secontext.yml b/examples/playbooks/file_secontext.yml deleted file mode 100644 index 117a930dc0ad40..00000000000000 --- a/examples/playbooks/file_secontext.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -# 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 diff --git a/examples/playbooks/get_url.yml b/examples/playbooks/get_url.yml deleted file mode 100644 index 48004020953fc6..00000000000000 --- a/examples/playbooks/get_url.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- hosts: webservers - vars: - - jquery_directory: /var/www/html/javascript - - person: 'Susie%20Smith' - tasks: - - name: Create directory for jQuery - action: file dest=${jquery_directory} state=directory mode=0755 - - name: Grab a bunch of jQuery stuff - action: get_url url=http://code.jquery.com/$item dest=${jquery_directory} mode=0444 - with_items: - - jquery.min.js - - mobile/latest/jquery.mobile.min.js - - ui/jquery-ui-git.css - #- name: Pass urlencoded name to CGI - # action: get_url url=http://example.com/name.cgi?name='${person}' dest=/tmp/test diff --git a/examples/playbooks/group_by.yml b/examples/playbooks/group_by.yml deleted file mode 100644 index bc8548f2fcf868..00000000000000 --- a/examples/playbooks/group_by.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -# Example playbook to demonstrate the group_by action plugin. -# -# as we know, the setup module will automatically run in each play, and sets up various -# facts. We can then create temporary (in memory only) groups based on those facts, which -# are useful ways of selecting similar sets of hosts. -# -# Additionally, we can use the 'register' keyword in Ansible to set similar variables -# and use those for grouping. This is not shown in this example. - -- hosts: all - - tasks: - - - name: Create a group of all hosts by operating system - action: group_by key=${ansible_distribution}-${ansible_distribution_version} - -# the following host group does not exist in inventory and was created by the group_by -# module. - -- hosts: CentOS-6.2 - - tasks: - - - name: ping all CentOS 6.2 hosts - action: ping - -- hosts: CentOS-6.3 - - tasks: - - - name: ping all CentOS 6.3 hosts - action: ping - - diff --git a/examples/playbooks/group_commands.yml b/examples/playbooks/group_commands.yml deleted file mode 100644 index afb7be91e6817f..00000000000000 --- a/examples/playbooks/group_commands.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -# This is a demo of how the group command works. - -- hosts: all - user: root - sudo: yes - - tasks: - - # Walk through group creation, modification, and deletion - - name: create a group - action: group name=tset - - # You can only modify the group's gid - - action: group name=tset gid=7777 - - # And finally remove the group - - action: group name=tset state=absent diff --git a/examples/playbooks/handlers/handlers.yml b/examples/playbooks/handlers/handlers.yml deleted file mode 100644 index b9a25d9edea60e..00000000000000 --- a/examples/playbooks/handlers/handlers.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- - -# this is an example to show that handlers can be included from yaml files, -# to promote reuse between different plays or even playbooks. They work -# just like normal handlers. - -- name: restart apache - action: service name=httpd state=restarted -- name: restart memcached - action: service name=memcached state=restarted diff --git a/examples/playbooks/intermediate_example.yml b/examples/playbooks/intermediate_example.yml deleted file mode 100644 index 1ee124046d3e94..00000000000000 --- a/examples/playbooks/intermediate_example.yml +++ /dev/null @@ -1,91 +0,0 @@ ---- -# see examples.yml first! -# This file explains some more advanced features of playbooks. -# because of the comments it's less concise than it normally is. But feel -# free to comment your playbooks if you like. - -- hosts: all - - # we can define variables the normal way... - - vars: - release: 2.0 - - # but they can also come from other files. This can be a relative - # or absolute path. This is a good way to store 'secret' variable - # files but still keep the playbook in public source control - - vars_files: - - vars/external_vars.yml - - # as with before, every play has a list of tasks in it - - tasks: - - # tasks can be written the normal way... - - - name: arbitrary command - action: command /bin/true - - # or we can promote reuse and simplicity by including tasks - # from other files, for instance, to reuse common tasks - - - include: tasks/base.yml - - # we could also have done something like: - # - include: wordpress.yml user=timmy - # and had access to the template variable $user in the - # included file, if we wanted to. Variables from vars - # and vars_files are also available inside include files - - handlers: - - # handlers can also be included from files, to promote reuse - # and simpler recipes, you may wish to only have one - # handler file for all your plays and playbooks. This example really - # doesn't notify any handlers, it is just showing you how they would - # be included (see intro_example for usage). - - - include: handlers/handlers.yml - - # you can mix things that are directly in the file with things - # that are included. Order is executed as written, but only - # handlers that have been notified get executed - - - name: restart foo - action: service name=foo state=restarted - -# =============================================================== - -# Here's a second play in the same playbook. This will be run -# after the first playbook completes on all hosts. You may want -# a different play for each class of systems, or may want a different -# play for each stage in a complex multi-node deployment push -# process. How you use them are up to you. - -# any play in a playbook can be executed by a user other than root -# if you want. sudo support is coming too. - -- hosts: webservers - user: mdehaan - - # vars must be specified again for the next play in the playbook - # but can be reused by including from vars_files if you want - # you can use vars, vars_files, or both. vars_files overrides - # those set in vars. - - vars: - release: 2.0 - vars_files: - - vars/external_vars.yml - - - # these all runs as the user 'mdehaan'. If there were any handlers - # they would as well. - - tasks: - - - name: some random command - action: command /bin/true - - diff --git a/examples/playbooks/intro_example.yml b/examples/playbooks/intro_example.yml deleted file mode 100644 index b81d250d4454ca..00000000000000 --- a/examples/playbooks/intro_example.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- -# this is an annotated example of some features available in playbooks -# it shows how to make sure packages are updated, how to make sure -# services are running, and how to template files. It also demos -# change handlers that can restart things (or trigger other actions) -# when resources change. For more advanced examples, see example2.yml - -# on all hosts, run as the user root... - -- name: example play - hosts: all - user: root - -# could have also have done: -# user: mdehaan -# sudo: yes - - # make these variables available inside of templates - # for when we use the 'template' action/module later on... - - vars: - http_port: 80 - max_clients: 200 - - # define the tasks that are part of this play... - - tasks: - - # task #1 is to run an arbitrary command - # we'll simulate a long running task, wait for up to 45 seconds, poll every 5 - # obviously this does nothing useful but you get the idea - - - name: longrunner - action: command /bin/sleep 15 - async: 45 - poll: 5 - - # let's demo file operations. - # - # We can 'copy' files or 'template' them instead, using jinja2 - # as the templating engine. This is done using the variables - # from the vars section above mixed in with variables bubbled up - # automatically from tools like facter and ohai. 'copy' - # works just like 'template' but does not do variable subsitution. - # - # If and only if the file changes, restart apache at the very - # end of the playbook run - - - name: write some_random_foo configuration - action: template src=templates/foo.j2 dest=/etc/some_random_foo.conf - notify: - - restart apache - - # make sure httpd is installed at the latest version - - - name: install httpd - action: yum pkg=httpd state=latest - - # make sure httpd is running - - - name: httpd start - action: service name=httpd state=running - - # handlers are only run when things change, at the very end of each - # play. Let's define some. The names are significant and must - # match the 'notify' sections above - - handlers: - - # this particular handler is run when some_random_foo.conf - # is changed, and only then - - - name: restart apache - action: service name=httpd state=restarted - - diff --git a/examples/playbooks/loop_plugins.yml b/examples/playbooks/loop_plugins.yml deleted file mode 100644 index 75966714a4a8d6..00000000000000 --- a/examples/playbooks/loop_plugins.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- - -# in addition to loop_with_items, the loop that works over a variable, ansible can do more sophisticated looping. - -# developer types: these are powered by 'lookup_plugins' should you ever decide to write your own -# see lib/ansible/runner/lookup_plugins/fileglob.py -- they can do basically anything! - -- hosts: all - gather_facts: no - - tasks: - - # this will copy a bunch of config files over -- dir must be created first - - - file: dest=/etc/fooapp state=directory - - - copy: src=$item dest=/etc/fooapp/ owner=root mode=600 - with_fileglob: /playbooks/files/fooapp/* - - diff --git a/examples/playbooks/loop_with_items.yml b/examples/playbooks/loop_with_items.yml deleted file mode 100644 index a7533d6adf1ffd..00000000000000 --- a/examples/playbooks/loop_with_items.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# this is an example of how to run repeated task elements over lists -# of items, for example, installing multiple packages or configuring -# multiple users - -- hosts: all - user: root - - tasks: - - - name: install $item - action: yum pkg=$item state=installed - with_items: - - cobbler - - httpd - - - name: configure user $item - action: user name=$item state=present groups=wheel - with_items: - - testuser1 - - testuser2 - - - name: remove user $item - action: user name=$item state=absent - with_items: - - testuser1 - - testuser2 - - - diff --git a/examples/playbooks/mysql.yml b/examples/playbooks/mysql.yml deleted file mode 100644 index 27207a837aea9f..00000000000000 --- a/examples/playbooks/mysql.yml +++ /dev/null @@ -1,18 +0,0 @@ -## -# Example Ansible playbook that uses the MySQL module. -# - ---- -- hosts: all - user: root - - tasks: - - - name: Create database user - action: mysql_user user=bob password=12345 priv=*.*:ALL state=present - - - name: Create database - action: mysql_db db=bobdata state=present - - - name: Ensure no user named 'sally' exists and delete if found. - action: mysql_user user=sally state=absent diff --git a/examples/playbooks/nested_playbooks.yml b/examples/playbooks/nested_playbooks.yml deleted file mode 100644 index 3d43608a02a9dd..00000000000000 --- a/examples/playbooks/nested_playbooks.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# it is possible to have top level playbook files import other playbook -# files. For example, a playbook called could include three -# different playbooks, such as webservers, workers, dbservers, etc. -# -# Running the site playbook would run all playbooks, while individual -# playbooks could still be run directly. This is somewhat like -# the tag feature and can be used in conjunction for very fine grained -# control over what you want to target when running ansible. - -- name: this is a play at the top level of a file - hosts: all - user: root - tasks: - - name: say hi - tags: foo - action: shell echo "hi..." - -# and this is how we include another playbook, be careful and -# don't recurse infinitely or anything. Note you can't use -# any variables in the include path here. - -- include: intro_example.yml - -# and if we wanted, we can continue with more includes here, -# or more plays inline in this file diff --git a/examples/playbooks/postgresql.yml b/examples/playbooks/postgresql.yml deleted file mode 100644 index 8f79ccb80a6bfd..00000000000000 --- a/examples/playbooks/postgresql.yml +++ /dev/null @@ -1,41 +0,0 @@ -## -# Example Ansible playbook that uses the PostgreSQL module. -# -# This installs PostgreSQL on an Ubuntu system, creates a database called -# "myapp" and a user called "django" with password "mysupersecretpassword" -# with access to the "myapp" database. -# ---- -- hosts: webservers - sudo: yes - gather_facts: no - - tasks: - - name: ensure apt cache is up to date - action: apt update_cache=yes - - name: ensure packages are installed - action: apt pkg=$item - with_items: - - postgresql - - libpq-dev - - python-psycopg2 - -- hosts: webservers - sudo: yes - sudo_user: postgres - gather_facts: no - - vars: - dbname: myapp - dbuser: django - dbpassword: mysupersecreetpassword - - tasks: - - name: ensure database is created - action: postgresql_db db=$dbname - - - name: ensure user has access to database - action: postgresql_user db=$dbname user=$dbuser password=$dbpassword priv=ALL - - - name: ensure user does not have unnecessary privilege - action: postgresql_user user=$dbuser role_attr_flags=NOSUPERUSER,NOCREATEDB \ No newline at end of file diff --git a/examples/playbooks/prompts.yml b/examples/playbooks/prompts.yml deleted file mode 100644 index 564061ae123221..00000000000000 --- a/examples/playbooks/prompts.yml +++ /dev/null @@ -1,60 +0,0 @@ ---- - -# it is possible to ask for variables from the user at the start -# of a playbook run, for example, as part of a release script. - -- hosts: all - user: root - -# regular variables are a dictionary of keys and values - - vars: - this_is_a_regular_var: 'moo' - so_is_this: 'quack' - -# alternatively, they can ALSO be passed in from the outside: -# ansible-playbook foo.yml --extra-vars="foo=100 bar=101" -# or through external inventory scripts (see online API docs) - -# here's basic mode prompting. Specify a hash of variable names and a prompt for -# each. -# -# vars_prompt: -# release_version: "product release version" - -# prompts can also be specified like this, allowing for hiding the prompt as -# entered. In the future, this may also be used to support crypted variables - - vars_prompt: - - name: "some_password" - prompt: "Enter password" - private: yes - - - name: "release_version" - prompt: "Product release version" - default: "my_default_version" - private: no - - - name: "my_password2" - prompt: "Enter password2" - private: yes - encrypt: "md5_crypt" - confirm: yes - salt_size: 7 - salt: "foo" - -# this is just a simple example to show that vars_prompt works, but -# you might ask for a tag to use with the git module or perhaps -# a package version to use with the yum module. - - tasks: - - - name: imagine this did something interesting with $release_version - action: shell echo foo >> /tmp/$release_version-alpha - - - name: look we crypted a password - action: shell echo my password is $my_password2 - - - - diff --git a/examples/playbooks/register_logic.yml b/examples/playbooks/register_logic.yml deleted file mode 100644 index 704a8b4c6fe72d..00000000000000 --- a/examples/playbooks/register_logic.yml +++ /dev/null @@ -1,28 +0,0 @@ -# here's a cool advanced topic about how to perform conditional logic in ansible without resorting -# to writing your own module that defines facts. You can do that too, and it's easy to do, but -# often you just want to run a command and then decide whether to run some steps or not. That's -# easy to do, and here we'll show you how. - -- name: test playbook - user: root - hosts: all - - tasks: - - # it is possible to save the result of any command in a named register. This variable will be made - # available to tasks and templates made further down in the execution flow. - - - action: shell grep hi /etc/motd - ignore_errors: yes - register: motd_result - - # and here we access the register. Note that variable is structured data because - # it is a return from the command module. The shell module makes available variables such as - # as 'stdout', 'stderr', and 'rc'. - - # here we run the next action only if the previous grep returned true - - - action: shell echo "motd contains the word hi" - only_if: "${motd_result.rc} == 0" - - diff --git a/examples/playbooks/selective_file_sources.yml b/examples/playbooks/selective_file_sources.yml deleted file mode 100644 index 192c4c1915e25b..00000000000000 --- a/examples/playbooks/selective_file_sources.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -# this is an example of how to template a file over using some variables derived -# from the system. For instance, if you wanted to have different configuration -# templates by OS version, this is a neat way to do it. Any Ansible facts, facter facts, -# or ohai facts could be used to do this. - -- hosts: all - - tasks: - - - name: template a config file - action: template dest=/etc/imaginary_file.conf - first_available_file: - - # first see if we have a file for this specific host - - /srv/whatever/${ansible_hostname}.conf - - # next try to load something like CentOS6.2.conf - - /srv/whatever/${ansible_distribution}${ansible_distribution_version}.conf - - # next see if there's a CentOS.conf - - /srv/whatever/${ansible_distribution}.conf - - # finally give up and just use something generic - - /srv/whatever/default - - - diff --git a/examples/playbooks/tags.yml b/examples/playbooks/tags.yml deleted file mode 100644 index 797d5a532a430f..00000000000000 --- a/examples/playbooks/tags.yml +++ /dev/null @@ -1,44 +0,0 @@ ---- -# tags allow us to run all of a playbook or part of it. -# -# assume: ansible-playbook tags.yml --tags foo -# -# try this with: -# --tags foo -# --tags bar -# --tags extra -# -# the value of a 'tags:' element can be a string or list -# of tag names. Variables are not usable in tag names. - -- name: example play one - hosts: all - user: root - - # any tags applied to the play are shorthand to applying - # the tag to all tasks in it. Here, each task is given - # the tag extra - - tags: - - extra - - tasks: - - # this task will run if you don't specify any tags, - # if you specify 'foo' or if you specify 'extra' - - - name: hi - tags: foo - action: shell echo "first task ran" - -- name: example play two - hosts: all - user: root - tasks: - - name: hi - tags: - - bar - action: shell echo "second task ran" - - include: tasks/base.yml tags=base - - diff --git a/examples/playbooks/tasks/base.yml b/examples/playbooks/tasks/base.yml deleted file mode 100644 index 1232d9f87ecce8..00000000000000 --- a/examples/playbooks/tasks/base.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- - -# this is the example of an included tasks file. It contains a flat list of tasks -# they can notify other tasks, and have full access to variables from 'vars' -# or 'vars_files' directives. Further, if ohai or facter were installed on -# the remote machines, variables from those tools can be accessed on the 'action' -# line or in templates. Just prefix with 'facter_' or 'ohai_' before the particular -# variable. - -# possible uses for a included yaml file might be to represent a 'class' of a system -# like defining what makes up a webserver, or you might have a common 'base.yml' -# (like this) that might be applied to all your systems as well. - -- name: no selinux - action: command /usr/sbin/setenforce 0 - -- name: no iptables - action: service name=iptables state=stopped - -- name: made up task just to show variables work here - action: command /bin/echo release is $release diff --git a/examples/playbooks/templates/custom-filters.j2 b/examples/playbooks/templates/custom-filters.j2 deleted file mode 100644 index 08126f958a4fc7..00000000000000 --- a/examples/playbooks/templates/custom-filters.j2 +++ /dev/null @@ -1 +0,0 @@ -1 + 1 = {{ '1+1' | generate_answer }} diff --git a/examples/playbooks/templates/etc_cron.d_ansible-pull.j2 b/examples/playbooks/templates/etc_cron.d_ansible-pull.j2 deleted file mode 100644 index 99f7339120cdf6..00000000000000 --- a/examples/playbooks/templates/etc_cron.d_ansible-pull.j2 +++ /dev/null @@ -1,2 +0,0 @@ -# Cron job to git clone/pull a repo and then run locally -{{ schedule }} {{ cron_user }} ansible-pull -d {{ workdir }} -U {{ repo_url }} >>{{ logfile }} 2>&1 diff --git a/examples/playbooks/templates/etc_logrotate.d_ansible-pull.j2 b/examples/playbooks/templates/etc_logrotate.d_ansible-pull.j2 deleted file mode 100644 index e396f31a4e5ae2..00000000000000 --- a/examples/playbooks/templates/etc_logrotate.d_ansible-pull.j2 +++ /dev/null @@ -1,7 +0,0 @@ -{{ logfile }} { - rotate 7 - daily - compress - missingok - notifempty -} diff --git a/examples/playbooks/templates/foo.j2 b/examples/playbooks/templates/foo.j2 deleted file mode 100644 index 8ebc7736db93c7..00000000000000 --- a/examples/playbooks/templates/foo.j2 +++ /dev/null @@ -1,10 +0,0 @@ -# This is a very simple Jinja2 template representing an imaginary configuration file -# for an imaginary app. - -# this is an example of loading a fact from the setup module -system={{ ansible_system }} - -# here is a variable that could be set in a playbook or inventory file -http_port={{ http_port }} - - diff --git a/examples/playbooks/templates/hostvars.j2 b/examples/playbooks/templates/hostvars.j2 deleted file mode 100644 index ca8f51482ad987..00000000000000 --- a/examples/playbooks/templates/hostvars.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# example of how to get the ipaddress of every machine in the webservers group -# for use in a template - -{% for host in groups['webservers'] %} - HOST: {{ host }} IP: {{ hostvars[host]['ansible_all_ipv4_addresses'][0] }} -{% endfor %} - diff --git a/examples/playbooks/user_commands.yml b/examples/playbooks/user_commands.yml deleted file mode 100644 index 9d17a0998ab5ec..00000000000000 --- a/examples/playbooks/user_commands.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -# this is a demo of how the user commands work and how to reference salted passwords -# in vars sections. You could also use vars_files if you like (see other examples) - -- hosts: all - user: root - vars: - # created with: - # crypt.crypt('This is my Password', '$1$SomeSalt') - password: $1$SomeSalt$UqddPX3r4kH3UL5jq5/ZI. - - tasks: - - # Walk through account creation, modification, and deletion - - name: test basic user account creation - action: user name=tset comment=TsetUser group=users shell=/sbin/nologin createhome=no - - # the following is just a simple example of how you don't have to include - # the 'name' element for each task - - - action: user name=tset comment=NyetUser - - action: user name=tset password=$password - - # The following will add the user to supplementary groups. - - # Add the user to the groups dialout and uucp. - - action: user name=tset groups=dialout,uucp - - # Add the user to the groups dialout and wheel, - # This will remove tset from the group uucp. - - action: user name=tset groups=dialout,wheel - - # Add the user to the group uucp. Because append=yes, the user - # will not be removed from the groups dialout and wheel. - - action: user name=tset groups=uucp append=yes - - # Finally, remove the user. - - action: user name=tset state=absent diff --git a/examples/playbooks/vars/CentOS.yml b/examples/playbooks/vars/CentOS.yml deleted file mode 100644 index b75cf94e8a8f29..00000000000000 --- a/examples/playbooks/vars/CentOS.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -apache: httpd -packager: yum diff --git a/examples/playbooks/vars/defaults.yml b/examples/playbooks/vars/defaults.yml deleted file mode 100644 index 3a04c435bd16af..00000000000000 --- a/examples/playbooks/vars/defaults.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -packager: apt -apache: apache diff --git a/examples/playbooks/vars/external_vars.yml b/examples/playbooks/vars/external_vars.yml deleted file mode 100644 index ebc76961bf5057..00000000000000 --- a/examples/playbooks/vars/external_vars.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -alpha: one -beta: two diff --git a/examples/scripts/yaml_to_ini.py b/examples/scripts/yaml_to_ini.py old mode 100644 new mode 100755 diff --git a/hacking/README b/hacking/README deleted file mode 100644 index c3772269cdbc08..00000000000000 --- a/hacking/README +++ /dev/null @@ -1,8 +0,0 @@ -The 'env-setup' script modifies your environment to allow you to run -ansible from a git checkout. - -To use it from the root of a checkout: - - $ . ./hacking/env-setup - -Note the space between the '.' and the './' diff --git a/hacking/README.md b/hacking/README.md new file mode 100644 index 00000000000000..b068a98254ba96 --- /dev/null +++ b/hacking/README.md @@ -0,0 +1,48 @@ +'Hacking' directory tools +========================= + +Env-setup +--------- + +The 'env-setup' script modifies your environment to allow you to run +ansible from a git checkout using python 2.6+. (You may not use +python 3 at this time). + +First, set up your environment to run from the checkout: + + $ source ./hacking/env-setup + +You will need some basic prerequisites installed. If you do not already have them +and do not wish to install them from your operating system package manager, you +can install them from pip + + $ easy_install pip # if pip is not already available + $ pip install pyyaml jinja2 + +From there, follow ansible instructions on ansibleworks.com/docs as normal. + +Test-module +----------- + +'test-module' is a simple program that allows module developers (or testers) to run +a module outside of the ansible program, locally, on the current machine. + +Example: + + $ ./hacking/test-module -m library/shell -a "echo hi" + +This is a good way to insert a breakpoint into a module, for instance. + +Module-formatter +---------------- + +The module formatter is a script used to generate manpages and online +module documentation. This is used by the system makefiles and rarely +needs to be run directly. + +Authors +------- +'authors' is a simple script that generates a list of everyone who has +contributed code to the ansible repository. + + diff --git a/hacking/authors.sh b/hacking/authors.sh new file mode 100755 index 00000000000000..7c97840b2fbc83 --- /dev/null +++ b/hacking/authors.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# script from http://stackoverflow.com/questions/12133583 +set -e + +# Get a list of authors ordered by number of commits +# and remove the commit count column +AUTHORS=$(git --no-pager shortlog -nse | cut -f 2- | sort -f) +if [ -z "$AUTHORS" ] ; then + echo "Authors list was empty" + exit 1 +fi + +# Display the authors list and write it to the file +echo "$AUTHORS" | tee "$(git rev-parse --show-toplevel)/AUTHORS.TXT" diff --git a/hacking/env-setup b/hacking/env-setup index 78b05dd4838021..fcf8e4d049bf38 100755 --- a/hacking/env-setup +++ b/hacking/env-setup @@ -17,19 +17,26 @@ PREFIX_PYTHONPATH="$ANSIBLE_HOME/lib" PREFIX_PATH="$ANSIBLE_HOME/bin" PREFIX_MANPATH="$ANSIBLE_HOME/docs/man" -export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH -export PATH=$PREFIX_PATH:$PATH +[[ $PYTHONPATH != ${PREFIX_PYTHONPATH}* ]] && export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH +[[ $PATH != ${PREFIX_PATH}* ]] && export PATH=$PREFIX_PATH:$PATH export ANSIBLE_LIBRARY="$ANSIBLE_HOME/library" -export MANPATH=$PREFIX_MANPATH:$MANPATH +[[ $MANPATH != ${PREFIX_MANPATH}* ]] && export MANPATH=$PREFIX_MANPATH:$MANPATH # Print out values unless -q is set if [ $# -eq 0 -o "$1" != "-q" ] ; then + echo "" + echo "Setting up Ansible to run out of checkout..." + echo "" echo "PATH=$PATH" echo "PYTHONPATH=$PYTHONPATH" echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY" echo "MANPATH=$MANPATH" + echo "" - echo "Reminder: specify your host file with -i" - echo "Done." + echo "Remember, you may wish to specify your host file with -i" + echo "" + echo "Done!" + echo "" fi + diff --git a/hacking/module_formatter.py b/hacking/module_formatter.py index 71e8777a43c8ea..732acbdb0a8cf5 100755 --- a/hacking/module_formatter.py +++ b/hacking/module_formatter.py @@ -18,6 +18,7 @@ # import os +import glob import sys import yaml import codecs @@ -29,6 +30,7 @@ import time import datetime import subprocess +import cgi import ansible.utils import ansible.utils.module_docs as module_docs @@ -61,11 +63,15 @@ def latex_ify(text): def html_ify(text): - t = _ITALIC.sub("" + r"\1" + "", text) + # print "DEBUG: text=%s" % text + + t = cgi.escape(text) + t = _ITALIC.sub("" + r"\1" + "", t) t = _BOLD.sub("" + r"\1" + "", t) t = _MODULE.sub("" + r"\1" + "", t) t = _URL.sub("" + r"\1" + "", t) t = _CONST.sub("" + r"\1" + "", t) + return t def json_ify(text): @@ -104,9 +110,13 @@ def rst_ify(text): return t +_MARKDOWN = re.compile(r"[*_`]") + def markdown_ify(text): - t = _ITALIC.sub("_" + r"\1" + "_", text) + t = cgi.escape(text) + t = _MARKDOWN.sub(r"\\\g<0>", t) + t = _ITALIC.sub("_" + r"\1" + "_", t) t = _BOLD.sub("**" + r"\1" + "**", t) t = _MODULE.sub("*" + r"\1" + "*", t) t = _URL.sub("[" + r"\1" + "](" + r"\1" + ")", t) @@ -127,7 +137,7 @@ def load_examples_section(text): def return_data(text, options, outputname, module): if options.output_dir is not None: f = open(os.path.join(options.output_dir, outputname % module), 'w') - f.write(text) + f.write(text.encode('utf-8')) f.close() else: print text @@ -135,8 +145,25 @@ def return_data(text, options, outputname, module): def boilerplate(): if not os.path.exists(EXAMPLE_YAML): print >>sys.stderr, "Missing example boiler plate: %S" % EXAMPLE_YAML + print "DOCUMENTATION = '''" print file(EXAMPLE_YAML).read() - + print "'''" + print "" + +def list_modules(module_dir): + categories = {} + files = glob.glob("%s/*" % module_dir) + for d in files: + if os.path.isdir(d): + files2 = glob.glob("%s/*" % d) + for f in files2: + tokens = f.split("/") + module = tokens[-1] + category = tokens[-2] + if not category in categories: + categories[category] = {} + categories[category][module] = f + return categories def main(): @@ -164,7 +191,7 @@ def main(): p.add_option("-t", "--type", action='store', dest='type', - choices=['html', 'latex', 'man', 'rst', 'json', 'markdown'], + choices=['html', 'latex', 'man', 'rst', 'json', 'markdown', 'js'], default='latex', help="Output type") p.add_option("-m", "--module", @@ -202,6 +229,14 @@ def main(): if options.do_boilerplate: boilerplate() + + print "" + print "EXAMPLES = '''" + print "# example of doing ___ from a playbook" + print "your_module: some_arg=1 other_arg=2" + print "'''" + print "" + sys.exit(0) if not options.module_dir: @@ -274,73 +309,104 @@ def main(): # Temporary variable required to genrate aggregated content in 'js' format. js_data = [] - for module in sorted(os.listdir(options.module_dir)): - if len(options.module_list): - if not module in options.module_list: + + categories = list_modules(options.module_dir) + last_category = None + category_names = categories.keys() + category_names.sort() + + for category in category_names: + module_map = categories[category] + + category = category.replace("_"," ") + category = category.title() + + modules = module_map.keys() + modules.sort() + + for module in modules: + fname = module_map[module] + + if len(options.module_list): + if not module in options.module_list: + continue + + # fname = os.path.join(options.module_dir, module) + + extra = os.path.join("inc", "%s.tex" % module) + + # probably could just throw out everything with extensions + if fname.endswith(".swp") or fname.endswith(".orig") or fname.endswith(".rej"): continue - fname = os.path.join(options.module_dir, module) - extra = os.path.join("inc", "%s.tex" % module) + # print " processing module source ---> %s" % fname - # probably could just throw out everything with extensions - if fname.endswith(".swp") or fname.endswith(".orig") or fname.endswith(".rej"): - continue + if options.type == 'js': + if fname.endswith(".json"): + f = open(fname) + j = json.load(f) + f.close() + js_data.append(j) + continue - print " processing module source ---> %s" % fname + doc, examples = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose) - if options.type == 'js': - if fname.endswith(".json"): - f = open(fname) - j = json.load(f) - f.close() - js_data.append(j) - continue + if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES: + print " while processing module source ---> %s" % fname + sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s ***\n" % module) + #sys.exit(1) - doc = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose) + if not doc is None: + + all_keys = [] + for (k,v) in doc['options'].iteritems(): + all_keys.append(k) + all_keys = sorted(all_keys) + doc['option_keys'] = all_keys - if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES: - sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s ***\n" % module) - #sys.exit(1) + doc['filename'] = fname + doc['docuri'] = doc['module'].replace('_', '-') + doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + doc['ansible_version'] = options.ansible_version + doc['plainexamples'] = examples #plain text - if not doc is None: + # BOOKMARK: here is where we build the table of contents... - all_keys = [] - for (k,v) in doc['options'].iteritems(): - all_keys.append(k) - all_keys = sorted(all_keys) - doc['option_keys'] = all_keys + if options.includes_file is not None and includefmt != "": - doc['filename'] = fname - doc['docuri'] = doc['module'].replace('_', '-') - doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') - doc['ansible_version'] = options.ansible_version + if last_category != category: + incfile.write("\n\n") + incfile.write(category) + incfile.write("\n") + incfile.write('`' * len(category)) + incfile.write("\n\n") + last_category = category - if options.includes_file is not None and includefmt != "": - incfile.write(includefmt % module) + incfile.write(includefmt % module) - if options.verbose: - print json.dumps(doc, indent=4) + if options.verbose: + print json.dumps(doc, indent=4) - if options.type == 'latex': - if os.path.exists(extra): - f = open(extra) - extradata = f.read() - f.close() - doc['extradata'] = extradata + if options.type == 'latex': + if os.path.exists(extra): + f = open(extra) + extradata = f.read() + f.close() + doc['extradata'] = extradata - if options.type == 'json': - text = json.dumps(doc, indent=2) - else: - text = template.render(doc) + if options.type == 'json': + text = json.dumps(doc, indent=2) + else: + text = template.render(doc) - return_data(text, options, outputname, module) + return_data(text, options, outputname, module) - if options.type == 'js': - docs = {} - docs['json'] = json.dumps(js_data, indent=2) - text = template.render(docs) - return_data(text, options, outputname, 'modules') + if options.type == 'js': + docs = {} + docs['json'] = json.dumps(js_data, indent=2) + text = template.render(docs) + return_data(text, options, outputname, 'modules') if __name__ == '__main__': main() diff --git a/hacking/templates/man.j2 b/hacking/templates/man.j2 index 36764bd4e0f0fa..90e429742b56c9 100644 --- a/hacking/templates/man.j2 +++ b/hacking/templates/man.j2 @@ -56,6 +56,13 @@ .fi {% endfor %} {% endif %} +." ------ PLAINEXAMPLES +{% if plainexamples is defined %} +.nf +@{ plainexamples }@ +.fi +{% endif %} + ." ------- AUTHOR {% if author is defined %} .SH AUTHOR diff --git a/hacking/templates/markdown.j2 b/hacking/templates/markdown.j2 index 2c65af0fa3da50..6f9bb0b3d8804d 100644 --- a/hacking/templates/markdown.j2 +++ b/hacking/templates/markdown.j2 @@ -1,4 +1,4 @@ -## @{ module }@ +## @{ module | jpfunc }@ {# ------------------------------------------ # @@ -11,7 +11,8 @@ New in version @{ version_added }@. {% endif %} {% for desc in description -%} -@{ desc | jpfunc }@ +@{ desc | jpfunc }@ + {% endfor %} {% if options -%} @@ -35,6 +36,10 @@ New in version @{ version_added }@. {% endif %} +{% if examples or plainexamples %} +#### Examples +{% endif %} + {% for example in examples %} {% if example['description'] %} * @{ example['description'] | jpfunc }@ @@ -44,7 +49,11 @@ New in version @{ version_added }@. @{ example['code'] }@ ``` {% endfor %} - +{% if plainexamples -%} +``` +@{ plainexamples }@ +``` +{% endif %} {% if notes %} #### Notes diff --git a/hacking/templates/rst.j2 b/hacking/templates/rst.j2 index a619f6a298a3f3..b4afcd1cab3453 100644 --- a/hacking/templates/rst.j2 +++ b/hacking/templates/rst.j2 @@ -1,7 +1,7 @@ .. _@{ module }@: @{ module }@ -`````````````````````````````` +++++++++++++++++++++++++++++++++++++++ {# ------------------------------------------ # @@ -35,13 +35,29 @@ @{ k }@ {% if v.get('required', False) %}yes{% else %}no{% endif %} {% if v['default'] %}@{ v['default'] }@{% endif %} + {% if v.get('type', 'not_bool') == 'bool' %} +
    • yes
    • no
    + {% else %}
      {% for choice in v.get('choices',[]) -%}
    • @{ choice }@
    • {% endfor -%}
    + {% endif %} {% for desc in v.description -%}@{ desc | html_ify }@{% endfor -%}{% if v['version_added'] %} (added in Ansible @{v['version_added']}@){% endif %} {% endfor %} {% endif %} +{% if requirements %} +.. raw:: html + +

    + Requirements: + {% for req in requirements %} + @{ req | html_ify }@ + {% endfor %} +

    + +{% endif %} + .. raw:: html {% for example in examples %} @@ -54,6 +70,15 @@ {% endfor %}
    +{% if plainexamples %} +.. raw:: html + +
    +@{ plainexamples | escape | indent(4, True) }@
    +    
    +{% endif %} + + {% if notes %} .. raw:: html diff --git a/hacking/test-module b/hacking/test-module index b7069cec8640b6..ef75eb3d911b74 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -63,10 +63,13 @@ def parse(): else: return options, args -def write_argsfile(argstring): +def write_argsfile(argstring, json=False): """ Write args to a file for old-style module's use. """ argspath = os.path.expanduser("~/.ansible_test_module_arguments") argsfile = open(argspath, 'w') + if json: + args = utils.parse_kv(argstring) + argstring = utils.jsonify(args) argsfile.write(argstring) argsfile.close() return argspath @@ -82,11 +85,13 @@ def boilerplate_module(modfile, args): if included_boilerplate: module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON) - encoded_args = "\"\"\"%s\"\"\"" % args.replace("\"","\\\"") + encoded_args = repr(str(args)) module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args) - encoded_lang = "\"\"\"%s\"\"\"" % C.DEFAULT_MODULE_LANG + encoded_lang = repr(C.DEFAULT_MODULE_LANG) + empty_complex = repr("{}") module_data = module_data.replace(module_common.REPLACER_LANG, encoded_lang) module_data = module_data.replace('syslog.LOG_USER', "syslog.%s" % C.DEFAULT_SYSLOG_FACILITY) + module_data = module_data.replace(module_common.REPLACER_COMPLEX, empty_complex) modfile2_path = os.path.expanduser("~/.ansible_module_generated") print "* including generated source, if any, saving to: %s" % modfile2_path @@ -95,10 +100,16 @@ def boilerplate_module(modfile, args): modfile2.write(module_data) modfile2.close() modfile = modfile2_path - return (modfile2_path, included_boilerplate) + + return (modfile2_path, included_boilerplate, False) else: + + old_style_but_json = False + if 'WANT_JSON' in module_data: + old_style_but_json = True + print "* module boilerplate substitution not requested in module, line numbers will be unaltered" - return (modfile, included_boilerplate) + return (modfile, included_boilerplate, old_style_but_json) def runtest( modfile, argspath): """Test run a module, piping it's output for reporting.""" @@ -140,10 +151,14 @@ def rundebug(debugger, modfile, argspath): def main(): options, args = parse() - (modfile, is_new_style) = boilerplate_module(options.module_path, options.module_args) + (modfile, is_new_style, old_style_but_json) = boilerplate_module(options.module_path, options.module_args) + argspath=None if not is_new_style: - argspath = write_argsfile(options.module_args) + if old_style_but_json: + argspath = write_argsfile(options.module_args, json=True) + else: + argspath = write_argsfile(options.module_args, json=False) if options.debugger: rundebug(options.debugger, modfile, argspath) else: diff --git a/lib/ansible/__init__.py b/lib/ansible/__init__.py index b5f3ae6155714f..1bd375792282c4 100644 --- a/lib/ansible/__init__.py +++ b/lib/ansible/__init__.py @@ -1,4 +1,4 @@ -# (c) 2012, Michael DeHaan +# (c) 2012-2013, Michael DeHaan # # This file is part of Ansible # @@ -14,5 +14,5 @@ # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -__version__ = '1.1' +__version__ = '1.3' __author__ = 'Michael DeHaan' diff --git a/lib/ansible/callback_plugins/noop.py b/lib/ansible/callback_plugins/noop.py index b0f48ef47fb6ce..54a2b254fc7050 100644 --- a/lib/ansible/callback_plugins/noop.py +++ b/lib/ansible/callback_plugins/noop.py @@ -1,4 +1,4 @@ -# (C) 2012, Michael DeHaan, +# (C) 2012-2013, Michael DeHaan, # This file is part of Ansible # @@ -63,10 +63,10 @@ def playbook_on_start(self): def playbook_on_notify(self, host, handler): pass - def on_no_hosts_matched(self): + def playbook_on_no_hosts_matched(self): pass - def on_no_hosts_remaining(self): + def playbook_on_no_hosts_remaining(self): pass def playbook_on_task_start(self, name, is_conditional): diff --git a/lib/ansible/callbacks.py b/lib/ansible/callbacks.py index 619613fb333680..0e0fa441289c28 100644 --- a/lib/ansible/callbacks.py +++ b/lib/ansible/callbacks.py @@ -1,4 +1,4 @@ -# (C) 2012, Michael DeHaan, +# (C) 2012-2013, Michael DeHaan, # This file is part of Ansible # @@ -20,24 +20,123 @@ import getpass import os import subprocess -import os.path +import random +import fnmatch +import tempfile +import fcntl +import constants from ansible.color import stringc -import ansible.constants as C -cowsay = None -if os.getenv("ANSIBLE_NOCOWS") is not None: +import logging +if constants.DEFAULT_LOG_PATH != '': + path = constants.DEFAULT_LOG_PATH + + if (os.path.exists(path) and not os.access(path, os.W_OK)) or not os.access(os.path.dirname(path), os.W_OK): + sys.stderr.write("log file at %s is not writeable, aborting\n" % path) + sys.exit(1) + + + logging.basicConfig(filename=path, level=logging.DEBUG, format='%(asctime)s %(name)s %(message)s') + mypid = str(os.getpid()) + user = getpass.getuser() + logger = logging.getLogger("p=%s u=%s | " % (mypid, user)) + +callback_plugins = [] + +def load_callback_plugins(): + global callback_plugins + callback_plugins = [x for x in utils.plugins.callback_loader.all()] + +def get_cowsay_info(): + if constants.ANSIBLE_NOCOWS is not None: + return (None, None) cowsay = None -elif os.path.exists("/usr/bin/cowsay"): - cowsay = "/usr/bin/cowsay" -elif os.path.exists("/usr/games/cowsay"): - cowsay = "/usr/games/cowsay" -elif os.path.exists("/usr/local/bin/cowsay"): - # BSD path for cowsay - cowsay = "/usr/local/bin/cowsay" + if os.getenv("ANSIBLE_NOCOWS") is not None: + cowsay = None + elif os.path.exists("/usr/bin/cowsay"): + cowsay = "/usr/bin/cowsay" + elif os.path.exists("/usr/games/cowsay"): + cowsay = "/usr/games/cowsay" + elif os.path.exists("/usr/local/bin/cowsay"): + # BSD path for cowsay + cowsay = "/usr/local/bin/cowsay" + elif os.path.exists("/opt/local/bin/cowsay"): + # MacPorts path for cowsay + cowsay = "/opt/local/bin/cowsay" + + noncow = os.getenv("ANSIBLE_COW_SELECTION",None) + if cowsay and noncow == 'random': + cmd = subprocess.Popen([cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = cmd.communicate() + cows = out.split() + cows.append(False) + noncow = random.choice(cows) + return (cowsay, noncow) + +cowsay, noncow = get_cowsay_info() + +def log_lockfile(): + tempdir = tempfile.gettempdir() + uid = os.getuid() + path = os.path.join(tempdir, ".ansible-lock.%s" % uid) + return path + +LOG_LOCK = open(log_lockfile(), 'w') + +def log_flock(runner): + fcntl.lockf(LOG_LOCK, fcntl.LOCK_EX) + if runner is not None: + try: + fcntl.lockf(runner.output_lockfile, fcntl.LOCK_EX) + except OSError, e: + # already got closed? + pass + +def log_unflock(runner): + fcntl.lockf(LOG_LOCK, fcntl.LOCK_UN) + if runner is not None: + try: + fcntl.lockf(runner.output_lockfile, fcntl.LOCK_UN) + except OSError, e: + # already got closed? + pass + +def set_play(callback, play): + ''' used to notify callback plugins of context ''' + callback.play = play + for callback_plugin in callback_plugins: + callback_plugin.play = play + +def set_task(callback, task): + ''' used to notify callback plugins of context ''' + callback.task = task + for callback_plugin in callback_plugins: + callback_plugin.task = task + +def display(msg, color=None, stderr=False, screen_only=False, log_only=False, runner=None): + # prevent a very rare case of interlaced multiprocess I/O + log_flock(runner) + msg2 = msg + if color: + msg2 = stringc(msg, color) + if not log_only: + if not stderr: + print msg2 + else: + print >>sys.stderr, msg2 + if constants.DEFAULT_LOG_PATH != '': + while msg.startswith("\n"): + msg = msg.replace("\n","") + if not screen_only: + if color == 'red': + logger.error(msg) + else: + logger.info(msg) + log_unflock(runner) def call_callback_module(method_name, *args, **kwargs): - for callback_plugin in utils.plugins.callback_loader.all(): + for callback_plugin in callback_plugins: methods = [ getattr(callback_plugin, method_name, None), getattr(callback_plugin, 'on_any', None) @@ -55,9 +154,9 @@ def vvv(msg, host=None): def verbose(msg, host=None, caplevel=2): if utils.VERBOSITY > caplevel: if host is None: - print stringc(msg, 'blue') + display(msg, color='blue') else: - print stringc("<%s> %s" % (host, msg), 'blue') + display("<%s> %s" % (host, msg), color='blue') class AggregateStats(object): ''' holds stats about per-host activity during playbook runs ''' @@ -121,15 +220,37 @@ def regular_generic_msg(hostname, result, oneline, caption): return "%s | %s >> %s\n" % (hostname, caption, utils.jsonify(result)) -def banner(msg): +def banner_cowsay(msg): - if cowsay != None: - cmd = subprocess.Popen([cowsay, "-W", "60", msg], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - return "%s\n" % out - else: - return "\n%s ********************* " % msg + if msg.find(": [") != -1: + msg = msg.replace("[","") + if msg.endswith("]"): + msg = msg[:-1] + runcmd = [cowsay,"-W", "60"] + if noncow: + runcmd.append('-f') + runcmd.append(noncow) + runcmd.append(msg) + cmd = subprocess.Popen(runcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = cmd.communicate() + return "%s\n" % out + +def banner_normal(msg): + + width = 78 - len(msg) + if width < 3: + width = 3 + filler = "*" * width + return "\n%s %s " % (msg, filler) + +def banner(msg): + if cowsay: + try: + return banner_cowsay(msg) + except OSError: + # somebody cleverly deleted cowsay or something during the PB run. heh. + return banner_normal(msg) + return banner_normal(msg) def command_generic_msg(hostname, result, oneline, caption): ''' output the result of a command run ''' @@ -161,17 +282,17 @@ def host_report_msg(hostname, module_name, result, oneline): ''' summarize the JSON results for a particular host ''' failed = utils.is_failed(result) - msg = '' + msg = ('', None) if module_name in [ 'command', 'shell', 'raw' ] and 'ansible_job_id' not in result and result.get('parsed',True) != False: if not failed: - msg = command_generic_msg(hostname, result, oneline, 'success') + msg = (command_generic_msg(hostname, result, oneline, 'success'), 'green') else: - msg = command_generic_msg(hostname, result, oneline, 'FAILED') + msg = (command_generic_msg(hostname, result, oneline, 'FAILED'), 'red') else: if not failed: - msg = regular_generic_msg(hostname, result, oneline, 'success') + msg = (regular_generic_msg(hostname, result, oneline, 'success'), 'green') else: - msg = regular_generic_msg(hostname, result, oneline, 'FAILED') + msg = (regular_generic_msg(hostname, result, oneline, 'FAILED'), 'red') return msg ############################################### @@ -209,6 +330,9 @@ def on_async_ok(self, host, res, jid): def on_async_failed(self, host, res, jid): call_callback_module('runner_on_async_failed', host, res, jid) + def on_file_diff(self, host, diff): + call_callback_module('runner_on_file_diff', diff) + ######################################################################## class CliRunnerCallbacks(DefaultRunnerCallbacks): @@ -224,13 +348,17 @@ def on_failed(self, host, res, ignore_errors=False): super(CliRunnerCallbacks, self).on_failed(host, res, ignore_errors=ignore_errors) def on_ok(self, host, res): + # hide magic variables used for ansible-playbook + res.pop('verbose_override', None) + res.pop('verbose_always', None) + self._on_any(host,res) super(CliRunnerCallbacks, self).on_ok(host, res) def on_unreachable(self, host, res): if type(res) == dict: res = res.get('msg','') - print "%s | FAILED => %s" % (host, res) + display("%s | FAILED => %s" % (host, res), stderr=True, color='red', runner=self.runner) if self.options.tree: utils.write_tree_file( self.options.tree, host, @@ -239,15 +367,15 @@ def on_unreachable(self, host, res): super(CliRunnerCallbacks, self).on_unreachable(host, res) def on_skipped(self, host, item=None): - print "%s | skipped" % (host) + display("%s | skipped" % (host), runner=self.runner) super(CliRunnerCallbacks, self).on_skipped(host, item) def on_error(self, host, err): - print >>sys.stderr, "err: [%s] => %s\n" % (host, err) + display("err: [%s] => %s\n" % (host, err), stderr=True, runner=self.runner) super(CliRunnerCallbacks, self).on_error(host, err) def on_no_hosts(self): - print >>sys.stderr, "no hosts matched\n" + display("no hosts matched\n", stderr=True, runner=self.runner) super(CliRunnerCallbacks, self).on_no_hosts() def on_async_poll(self, host, res, jid, clock): @@ -255,24 +383,29 @@ def on_async_poll(self, host, res, jid, clock): self._async_notified[jid] = clock + 1 if self._async_notified[jid] > clock: self._async_notified[jid] = clock - print " polling, %ss remaining"%(jid, clock) + display(" polling, %ss remaining" % (jid, clock), runner=self.runner) super(CliRunnerCallbacks, self).on_async_poll(host, res, jid, clock) def on_async_ok(self, host, res, jid): - print " finished on %s => %s"%(jid, host, utils.jsonify(res,format=True)) + display(" finished on %s => %s"%(jid, host, utils.jsonify(res,format=True)), runner=self.runner) super(CliRunnerCallbacks, self).on_async_ok(host, res, jid) def on_async_failed(self, host, res, jid): - print " FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)) + display(" FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True)), color='red', stderr=True, runner=self.runner) super(CliRunnerCallbacks, self).on_async_failed(host,res,jid) def _on_any(self, host, result): result2 = result.copy() result2.pop('invocation', None) - print host_report_msg(host, self.options.module_name, result2, self.options.one_line) + (msg, color) = host_report_msg(host, self.options.module_name, result2, self.options.one_line) + display(msg, color=color, runner=self.runner) if self.options.tree: utils.write_tree_file(self.options.tree, host, utils.jsonify(result2,format=True)) + def on_file_diff(self, host, diff): + display(utils.get_diff(diff), runner=self.runner) + super(CliRunnerCallbacks, self).on_file_diff(host, diff) + ######################################################################## class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): @@ -291,11 +424,12 @@ def on_unreachable(self, host, results): msg = "fatal: [%s] => (item=%s) => %s" % (host, item, results) else: msg = "fatal: [%s] => %s" % (host, results) - print stringc(msg, 'red') + display(msg, color='red', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_unreachable(host, results) def on_failed(self, host, results, ignore_errors=False): + results2 = results.copy() results2.pop('invocation', None) @@ -312,25 +446,27 @@ def on_failed(self, host, results, ignore_errors=False): msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results2)) else: msg = "failed: [%s] => %s" % (host, utils.jsonify(results2)) - print stringc(msg, 'red') + display(msg, color='red', runner=self.runner) if stderr: - print stringc("stderr: %s" % stderr, 'red') + display("stderr: %s" % stderr, color='red', runner=self.runner) if stdout: - print stringc("stdout: %s" % stdout, 'red') + display("stdout: %s" % stdout, color='red', runner=self.runner) if returned_msg: - print stringc("msg: %s" % returned_msg, 'red') + display("msg: %s" % returned_msg, color='red', runner=self.runner) if not parsed and module_msg: - print stringc("invalid output was: %s" % module_msg, 'red') + display("invalid output was: %s" % module_msg, color='red', runner=self.runner) if ignore_errors: - print stringc("...ignoring", 'cyan') + display("...ignoring", color='cyan', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_failed(host, results, ignore_errors=ignore_errors) def on_ok(self, host, host_result): + item = host_result.get('item', None) host_result2 = host_result.copy() host_result2.pop('invocation', None) + verbose_always = host_result2.pop('verbose_always', None) changed = host_result.get('changed', False) ok_or_changed = 'ok' if changed: @@ -338,7 +474,8 @@ def on_ok(self, host, host_result): # show verbose output for non-setup module results if --verbose is used msg = '' - if not self.verbose or host_result2.get("verbose_override",None) is not None: + if (not self.verbose or host_result2.get("verbose_override",None) is not + None) and verbose_always is None: if item: msg = "%s: [%s] => (item=%s)" % (ok_or_changed, host, item) else: @@ -354,9 +491,9 @@ def on_ok(self, host, host_result): if msg != '': if not changed: - print stringc(msg, 'green') + display(msg, color='green', runner=self.runner) else: - print stringc(msg, 'yellow') + display(msg, color='yellow', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_ok(host, host_result) def on_error(self, host, err): @@ -368,8 +505,7 @@ def on_error(self, host, err): else: msg = "err: [%s] => %s" % (host, err) - msg = stringc(msg, 'red') - print >>sys.stderr, msg + display(msg, color='red', stderr=True, runner=self.runner) super(PlaybookRunnerCallbacks, self).on_error(host, err) def on_skipped(self, host, item=None): @@ -378,11 +514,11 @@ def on_skipped(self, host, item=None): msg = "skipping: [%s] => (item=%s)" % (host, item) else: msg = "skipping: [%s]" % host - print stringc(msg, 'cyan') + display(msg, color='cyan', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_skipped(host, item) def on_no_hosts(self): - print stringc("FATAL: no hosts matched or all hosts have already failed -- aborting\n", 'red') + display("FATAL: no hosts matched or all hosts have already failed -- aborting\n", color='red', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_no_hosts() def on_async_poll(self, host, res, jid, clock): @@ -391,19 +527,22 @@ def on_async_poll(self, host, res, jid, clock): if self._async_notified[jid] > clock: self._async_notified[jid] = clock msg = " polling, %ss remaining"%(jid, clock) - print stringc(msg, 'cyan') + display(msg, color='cyan', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_async_poll(host,res,jid,clock) def on_async_ok(self, host, res, jid): msg = " finished on %s"%(jid, host) - print stringc(msg, 'cyan') + display(msg, color='cyan', runner=self.runner) super(PlaybookRunnerCallbacks, self).on_async_ok(host, res, jid) def on_async_failed(self, host, res, jid): - msg = " FAILED on %s"%(jid, host) - print stringc(msg, 'red') + msg = " FAILED on %s" % (jid, host) + display(msg, color='red', stderr=True, runner=self.runner) super(PlaybookRunnerCallbacks, self).on_async_failed(host,res,jid) + def on_file_diff(self, host, diff): + display(utils.get_diff(diff), runner=self.runner) + super(PlaybookRunnerCallbacks, self).on_file_diff(host, diff) ######################################################################## @@ -421,23 +560,48 @@ def on_notify(self, host, handler): call_callback_module('playbook_on_notify', host, handler) def on_no_hosts_matched(self): - print stringc("skipping: no hosts matched", 'cyan') + display("skipping: no hosts matched", color='cyan') call_callback_module('playbook_on_no_hosts_matched') def on_no_hosts_remaining(self): - print stringc("\nFATAL: all hosts have already failed -- aborting", 'red') + display("\nFATAL: all hosts have already failed -- aborting", color='red') call_callback_module('playbook_on_no_hosts_remaining') def on_task_start(self, name, is_conditional): msg = "TASK: [%s]" % name if is_conditional: msg = "NOTIFIED: [%s]" % name - print banner(msg) + + if hasattr(self, 'start_at'): + if name == self.start_at or fnmatch.fnmatch(name, self.start_at): + # we found out match, we can get rid of this now + del self.start_at + + if hasattr(self, 'start_at'): # we still have start_at so skip the task + self.skip_task = True + elif hasattr(self, 'step') and self.step: + msg = ('Perform task: %s (y/n/c): ' % name).encode(sys.stdout.encoding) + resp = raw_input(msg) + if resp.lower() in ['y','yes']: + self.skip_task = False + display(banner(msg)) + elif resp.lower() in ['c', 'continue']: + self.skip_task = False + self.step = False + display(banner(msg)) + else: + self.skip_task = True + else: + self.skip_task = False + display(banner(msg)) + call_callback_module('playbook_on_task_start', name, is_conditional) def on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): - if prompt: + if prompt and default: + msg = "%s [%s]: " % (prompt, default) + elif prompt: msg = "%s: " % prompt else: msg = 'input for %s: ' % varname @@ -454,7 +618,7 @@ def prompt(prompt, private): second = prompt("confirm " + msg, private) if result == second: break - print "***** VALUES ENTERED DO NOT MATCH ****" + display("***** VALUES ENTERED DO NOT MATCH ****") else: result = prompt(msg, private) @@ -473,21 +637,21 @@ def prompt(prompt, private): return result def on_setup(self): - print banner("GATHERING FACTS") + display(banner("GATHERING FACTS")) call_callback_module('playbook_on_setup') def on_import_for_host(self, host, imported_file): msg = "%s: importing %s" % (host, imported_file) - print stringc(msg, 'cyan') + display(msg, color='cyan') call_callback_module('playbook_on_import_for_host', host, imported_file) def on_not_import_for_host(self, host, missing_file): msg = "%s: not importing file: %s" % (host, missing_file) - print stringc(msg, 'cyan') + display(msg, color='cyan') call_callback_module('playbook_on_not_import_for_host', host, missing_file) def on_play_start(self, pattern): - print banner("PLAY [%s]" % pattern) + display(banner("PLAY [%s]" % pattern)) call_callback_module('playbook_on_play_start', pattern) def on_stats(self, stats): diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index b43986eb096d65..0d1e27c9cc55b7 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -20,7 +20,25 @@ import sys import ConfigParser -def get_config(p, section, key, env_var, default): +# copied from utils, avoid circular reference fun :) +def mk_boolean(value): + val = str(value) + if val.lower() in [ "true", "t", "y", "1", "yes" ]: + return True + else: + return False + +def get_config(p, section, key, env_var, default, boolean=False, integer=False): + ''' return a configuration variable with casting ''' + value = _get_config(p, section, key, env_var, default) + if boolean: + return mk_boolean(value) + if integer: + return int(value) + return value + +def _get_config(p, section, key, env_var, default, boolean=True): + ''' helper function for get_config ''' if env_var is not None: value = os.environ.get(env_var, None) if value is not None: @@ -70,8 +88,8 @@ def shell_expand_path(path): DEFAULTS='defaults' # configurable things -DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'hostfile', 'ANSIBLE_HOSTS', '/etc/ansible/hosts')) -DEFAULT_MODULE_PATH = shell_expand_path(get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', DIST_MODULE_PATH)) +DEFAULT_HOST_LIST = shell_expand_path(get_config(p, DEFAULTS, 'hostfile', 'ANSIBLE_HOSTS', '/etc/ansible/hosts')) +DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', DIST_MODULE_PATH) DEFAULT_REMOTE_TMP = shell_expand_path(get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp')) DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command') DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, '*') @@ -81,31 +99,42 @@ def shell_expand_path(path): DEFAULT_TIMEOUT = get_config(p, DEFAULTS, 'timeout', 'ANSIBLE_TIMEOUT', 10) DEFAULT_POLL_INTERVAL = get_config(p, DEFAULTS, 'poll_interval', 'ANSIBLE_POLL_INTERVAL', 15) DEFAULT_REMOTE_USER = get_config(p, DEFAULTS, 'remote_user', 'ANSIBLE_REMOTE_USER', active_user) -DEFAULT_ASK_PASS = get_config(p, DEFAULTS, 'ask_pass', 'ANSIBLE_ASK_PASS', False) +DEFAULT_ASK_PASS = get_config(p, DEFAULTS, 'ask_pass', 'ANSIBLE_ASK_PASS', False, boolean=True) DEFAULT_PRIVATE_KEY_FILE = shell_expand_path(get_config(p, DEFAULTS, 'private_key_file', 'ANSIBLE_PRIVATE_KEY_FILE', None)) DEFAULT_SUDO_USER = get_config(p, DEFAULTS, 'sudo_user', 'ANSIBLE_SUDO_USER', 'root') -DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False) +DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False, boolean=True) DEFAULT_REMOTE_PORT = int(get_config(p, DEFAULTS, 'remote_port', 'ANSIBLE_REMOTE_PORT', 22)) -DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'paramiko') -DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False) +DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'smart') +DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False, boolean=True) DEFAULT_MANAGED_STR = get_config(p, DEFAULTS, 'ansible_managed', None, 'Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}') DEFAULT_SYSLOG_FACILITY = get_config(p, DEFAULTS, 'syslog_facility', 'ANSIBLE_SYSLOG_FACILITY', 'LOG_USER') DEFAULT_KEEP_REMOTE_FILES = get_config(p, DEFAULTS, 'keep_remote_files', 'ANSIBLE_KEEP_REMOTE_FILES', '0') +DEFAULT_SUDO = get_config(p, DEFAULTS, 'sudo', 'ANSIBLE_SUDO', False, boolean=True) DEFAULT_SUDO_EXE = get_config(p, DEFAULTS, 'sudo_exe', 'ANSIBLE_SUDO_EXE', 'sudo') DEFAULT_SUDO_FLAGS = get_config(p, DEFAULTS, 'sudo_flags', 'ANSIBLE_SUDO_FLAGS', '-H') DEFAULT_HASH_BEHAVIOUR = get_config(p, DEFAULTS, 'hash_behaviour', 'ANSIBLE_HASH_BEHAVIOUR', 'replace') +DEFAULT_LEGACY_PLAYBOOK_VARIABLES = get_config(p, DEFAULTS, 'legacy_playbook_variables', 'ANSIBLE_LEGACY_PLAYBOOK_VARIABLES', True, boolean=True) +DEFAULT_JINJA2_EXTENSIONS = get_config(p, DEFAULTS, 'jinja2_extensions', 'ANSIBLE_JINJA2_EXTENSIONS', None) +DEFAULT_EXECUTABLE = get_config(p, DEFAULTS, 'executable', 'ANSIBLE_EXECUTABLE', '/bin/sh') + +DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '/usr/share/ansible_plugins/action_plugins') +DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '/usr/share/ansible_plugins/callback_plugins') +DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '/usr/share/ansible_plugins/connection_plugins') +DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '/usr/share/ansible_plugins/lookup_plugins') +DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '/usr/share/ansible_plugins/vars_plugins') +DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '/usr/share/ansible_plugins/filter_plugins') +DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', '')) + +ANSIBLE_NOCOWS = get_config(p, DEFAULTS, 'nocows', 'ANSIBLE_NOCOWS', None) +ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) +PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) +ZEROMQ_PORT = int(get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099)) -DEFAULT_ACTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '/usr/share/ansible_plugins/action_plugins')) -DEFAULT_CALLBACK_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '/usr/share/ansible_plugins/callback_plugins')) -DEFAULT_CONNECTION_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '/usr/share/ansible_plugins/connection_plugins')) -DEFAULT_LOOKUP_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '/usr/share/ansible_plugins/lookup_plugins')) -DEFAULT_VARS_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '/usr/share/ansible_plugins/vars_plugins')) -DEFAULT_FILTER_PLUGIN_PATH = shell_expand_path(get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '/usr/share/ansible_plugins/filter_plugins')) +DEFAULT_UNDEFINED_VAR_BEHAVIOR = get_config(p, DEFAULTS, 'error_on_undefined_vars', 'ANSIBLE_ERROR_ON_UNDEFINED_VARS', False, boolean=True) +HOST_KEY_CHECKING = get_config(p, DEFAULTS, 'host_key_checking', 'ANSIBLE_HOST_KEY_CHECKING', True, boolean=True) # non-configurable things DEFAULT_SUDO_PASS = None DEFAULT_REMOTE_PASS = None DEFAULT_SUBSET = None -ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) -ZEROMQ_PORT = int(get_config(p, 'fireball', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099)) diff --git a/lib/ansible/errors.py b/lib/ansible/errors.py index 29562b7f658470..de0e6e38f3c44a 100644 --- a/lib/ansible/errors.py +++ b/lib/ansible/errors.py @@ -32,3 +32,6 @@ class AnsibleConnectionFailed(AnsibleError): class AnsibleYAMLValidationFailed(AnsibleError): pass + +class AnsibleUndefinedVariable(AnsibleError): + pass diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index 076bc7087684ae..f85add0ad73065 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -25,6 +25,7 @@ import ansible.constants as C from ansible.inventory.ini import InventoryParser from ansible.inventory.script import InventoryScript +from ansible.inventory.dir import InventoryDirectory from ansible.inventory.group import Group from ansible.inventory.host import Host from ansible import errors @@ -35,8 +36,9 @@ class Inventory(object): Host inventory for ansible. """ - __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script', - 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list'] + __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', + 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache', '_groups_list', + '_vars_plugins', '_playbook_basedir'] def __init__(self, host_list=C.DEFAULT_HOST_LIST): @@ -52,6 +54,9 @@ def __init__(self, host_list=C.DEFAULT_HOST_LIST): self._hosts_cache = {} self._groups_list = {} + # to be set by calling set_playbook_basedir by ansible-playbook + self._playbook_basedir = None + # the inventory object holds a list of groups self.groups = [] @@ -60,37 +65,40 @@ def __init__(self, host_list=C.DEFAULT_HOST_LIST): self._also_restriction = None self._subset = None - # whether the inventory file is a script - self._is_script = False - - if type(host_list) in [ str, unicode ]: - if host_list.find(",") != -1: + if isinstance(host_list, basestring): + if "," in host_list: host_list = host_list.split(",") host_list = [ h for h in host_list if h and h.strip() ] - else: - utils.plugins.vars_loader.add_directory(self.basedir()) - - if type(host_list) == list: + if isinstance(host_list, list): + self.parser = None all = Group('all') self.groups = [ all ] for x in host_list: - if x.find(":") != -1: - tokens = x.split(":",1) + if ":" in x: + tokens = x.split(":", 1) all.add_host(Host(tokens[0], tokens[1])) else: all.add_host(Host(x)) - elif utils.is_executable(host_list): - self._is_script = True - self.parser = InventoryScript(filename=host_list) - self.groups = self.parser.groups.values() - else: - data = file(host_list).read() - if not data.startswith("---"): - self.parser = InventoryParser(filename=host_list) + elif os.path.exists(host_list): + if os.path.isdir(host_list): + # Ensure basedir is inside the directory + self.host_list = os.path.join(self.host_list, "") + self.parser = InventoryDirectory(filename=host_list) + self.groups = self.parser.groups.values() + elif utils.is_executable(host_list): + self.parser = InventoryScript(filename=host_list) self.groups = self.parser.groups.values() else: - raise errors.AnsibleError("YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout") + self.parser = InventoryParser(filename=host_list) + self.groups = self.parser.groups.values() + + utils.plugins.vars_loader.add_directory(self.basedir(), with_subdir=True) + else: + raise errors.AnsibleError("Unable to find an inventory file, specify one with -i ?") + + self._vars_plugins = [ x for x in utils.plugins.vars_loader.all(self) ] + def _match(self, str, pattern_str): if pattern_str.startswith('~'): @@ -128,6 +136,11 @@ def _get_hosts(self, patterns): finds hosts that match a list of patterns. Handles negative matches as well as intersection matches. """ + try: + if patterns[0].startswith("!"): + patterns.insert(0, "all") + except IndexError: + pass hosts = set() for p in patterns: @@ -224,7 +237,8 @@ def groups_list(self): 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()] + if a.name not in groups: + groups[a.name] = [h.name for h in a.get_hosts()] self._groups_list = groups return self._groups_list @@ -237,10 +251,15 @@ def get_host(self, hostname): return self._hosts_cache[hostname] def _get_host(self, hostname): - for group in self.groups: - for host in group.get_hosts(): - if hostname == host.name: + if hostname in ['localhost','127.0.0.1']: + for host in self.get_group('all').get_hosts(): + if host.name in ['localhost', '127.0.0.1']: return host + else: + for group in self.groups: + for host in group.get_hosts(): + if hostname == host.name: + return host return None def get_group(self, groupname): @@ -272,25 +291,19 @@ def _get_variables(self, hostname): raise errors.AnsibleError("host not found: %s" % hostname) vars = {} - for updated in map(lambda x: x.run(host), utils.plugins.vars_loader.all(self)): + vars_results = [ plugin.run(host) for plugin in self._vars_plugins ] + for updated in vars_results: if updated is not None: vars.update(updated) vars.update(host.get_variables()) - if self._is_script: - cmd = [self.host_list,"--host",hostname] - try: - sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError, e: - raise errors.AnsibleError("problem running %s (%s)" % (' '.join(cmd), e)) - (out, err) = sp.communicate() - results = utils.parse_json(out) - - vars.update(results) + if self.parser is not None: + vars.update(self.parser.get_host_variables(host)) return vars def add_group(self, group): self.groups.append(group) + self._groups_list = None # invalidate internal cache def list_hosts(self, pattern="all"): return [ h.name for h in self.get_hosts(pattern) ] @@ -308,7 +321,7 @@ def restrict_to(self, restriction): to exclude failed hosts in main playbook code, don't use this for other reasons. """ - if type(restriction) != list: + if not isinstance(restriction, list): restriction = [ restriction ] self._restriction = restriction @@ -317,7 +330,7 @@ def also_restrict_to(self, restriction): Works like restict_to but offers an additional restriction. Playbooks use this to implement serial behavior. """ - if type(restriction) != list: + if not isinstance(restriction, list): restriction = [ restriction ] self._also_restriction = restriction @@ -332,7 +345,17 @@ def subset(self, subset_pattern): self._subset = None else: subset_pattern = subset_pattern.replace(',',':') - self._subset = subset_pattern.replace(";",":").split(":") + subset_pattern = subset_pattern.replace(";",":").split(":") + results = [] + # allow Unix style @filename data + for x in subset_pattern: + if x.startswith("@"): + fd = open(x[1:]) + results.extend(fd.read().split("\n")) + fd.close() + else: + results.append(x) + self._subset = results def lift_restriction(self): """ Do not restrict list operations """ @@ -352,4 +375,21 @@ def basedir(self): """ if inventory came from a file, what's the directory? """ if not self.is_file(): return None - return os.path.dirname(self.host_list) + dname = os.path.dirname(self.host_list) + if dname is None or dname == '': + cwd = os.getcwd() + return cwd + return dname + + def playbook_basedir(self): + """ returns the directory of the current playbook """ + return self._playbook_basedir + + def set_playbook_basedir(self, dir): + """ + sets the base directory of the playbook so inventory plugins can use it to find + variable files and other things. + """ + self._playbook_basedir = dir + + diff --git a/lib/ansible/inventory/dir.py b/lib/ansible/inventory/dir.py new file mode 100644 index 00000000000000..46997d9be89c80 --- /dev/null +++ b/lib/ansible/inventory/dir.py @@ -0,0 +1,95 @@ +# (c) 2013, Daniel Hokka Zakrisson +# +# 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 . + +############################################# + +import os +import ansible.constants as C +from ansible.inventory.host import Host +from ansible.inventory.group import Group +from ansible.inventory.ini import InventoryParser +from ansible.inventory.script import InventoryScript +from ansible import utils +from ansible import errors + +class InventoryDirectory(object): + ''' Host inventory parser for ansible using a directory of inventories. ''' + + def __init__(self, filename=C.DEFAULT_HOST_LIST): + self.names = os.listdir(filename) + self.names.sort() + self.directory = filename + self.parsers = [] + self.hosts = {} + self.groups = {} + + for i in self.names: + + if i.endswith("~") or i.endswith(".orig") or i.endswith(".bak"): + continue + if i.endswith(".ini"): + # configuration file for an inventory script + continue + if i.endswith(".retry"): + # this file is generated on a failed playbook and should only be + # used when run specifically + continue + # Skip hidden files + if i.startswith('.') and not i.startswith('./'): + continue + # These are things inside of an inventory basedir + if i in ("host_vars", "group_vars", "vars_plugins"): + continue + fullpath = os.path.join(self.directory, i) + if os.path.isdir(fullpath): + parser = InventoryDirectory(filename=fullpath) + elif utils.is_executable(fullpath): + parser = InventoryScript(filename=fullpath) + else: + parser = InventoryParser(filename=fullpath) + self.parsers.append(parser) + # This takes a lot of code because we can't directly use any of the objects, as they have to blend + for name, group in parser.groups.iteritems(): + if name not in self.groups: + self.groups[name] = group + else: + # group is already there, copy variables + # note: depth numbers on duplicates may be bogus + for k, v in group.get_variables().iteritems(): + self.groups[name].set_variable(k, v) + for host in group.get_hosts(): + if host.name not in self.hosts: + self.hosts[host.name] = host + else: + # host is already there, copy variables + # note: depth numbers on duplicates may be bogus + for k, v in host.vars.iteritems(): + self.hosts[host.name].set_variable(k, v) + self.groups[name].add_host(self.hosts[host.name]) + + # This needs to be a second loop to ensure all the parent groups exist + for name, group in parser.groups.iteritems(): + for ancestor in group.get_ancestors(): + self.groups[ancestor.name].add_child_group(self.groups[name]) + + def get_host_variables(self, host): + """ Gets additional host variables from all inventories """ + vars = {} + for i in self.parsers: + vars.update(i.get_host_variables(host)) + return vars + diff --git a/lib/ansible/inventory/group.py b/lib/ansible/inventory/group.py index a460b483efa4f1..61ef1342bd22ce 100644 --- a/lib/ansible/inventory/group.py +++ b/lib/ansible/inventory/group.py @@ -35,9 +35,12 @@ def add_child_group(self, group): if self == group: raise Exception("can't add group to itself") - self.child_groups.append(group) - group.depth = group.depth + 1 - group.parent_groups.append(self) + + # don't add if it's already there + if not group in self.child_groups: + self.child_groups.append(group) + group.depth = max([self.depth+1, group.depth]) + group.parent_groups.append(self) def add_host(self, host): diff --git a/lib/ansible/inventory/ini.py b/lib/ansible/inventory/ini.py index a5da915572781a..1c7ae58bf15ef8 100644 --- a/lib/ansible/inventory/ini.py +++ b/lib/ansible/inventory/ini.py @@ -33,11 +33,11 @@ class InventoryParser(object): def __init__(self, filename=C.DEFAULT_HOST_LIST): - fh = open(filename) - self.lines = fh.readlines() - self.groups = {} - self.hosts = {} - self._parse() + with open(filename) as fh: + self.lines = fh.readlines() + self.groups = {} + self.hosts = {} + self._parse() def _parse(self): @@ -65,11 +65,12 @@ def _parse_base_groups(self): for line in self.lines: if line.startswith("["): - active_group_name = line.replace("[","").replace("]","").strip() + active_group_name = line.split(" #")[0].replace("[","").replace("]","").strip() if line.find(":vars") != -1 or line.find(":children") != -1: active_group_name = active_group_name.rsplit(":", 1)[0] if active_group_name not in self.groups: - self.groups[active_group_name] = Group(name=active_group_name) + new_group = self.groups[active_group_name] = Group(name=active_group_name) + all.add_child_group(new_group) active_group_name = None elif active_group_name not in self.groups: new_group = self.groups[active_group_name] = Group(name=active_group_name) @@ -77,7 +78,7 @@ def _parse_base_groups(self): elif line.startswith("#") or line == '': pass elif active_group_name: - tokens = shlex.split(line) + tokens = shlex.split(line.split(" #")[0]) if len(tokens) == 0: continue hostname = tokens[0] @@ -109,6 +110,8 @@ def _parse_base_groups(self): self.hosts[hn] = host if len(tokens) > 1: for t in tokens[1:]: + if t.startswith('#'): + break (k,v) = t.split("=") host.set_variable(k,v) self.groups[active_group_name].add_host(host) @@ -171,3 +174,6 @@ def _parse_group_variables(self): group.set_variable(k, re.sub(r"^['\"]|['\"]$", '', v)) else: group.set_variable(k, v) + + def get_host_variables(self, host): + return {} diff --git a/lib/ansible/inventory/script.py b/lib/ansible/inventory/script.py index b665d4322dc7db..915aaa18035281 100644 --- a/lib/ansible/inventory/script.py +++ b/lib/ansible/inventory/script.py @@ -17,49 +17,72 @@ ############################################# +import os import subprocess import ansible.constants as C from ansible.inventory.host import Host from ansible.inventory.group import Group from ansible import utils from ansible import errors +import sys class InventoryScript(object): ''' Host inventory parser for ansible using external inventory scripts. ''' def __init__(self, filename=C.DEFAULT_HOST_LIST): - cmd = [ filename, "--list" ] + # Support inventory scripts that are not prefixed with some + # path information but happen to be in the current working + # directory when '.' is not in PATH. + self.filename = os.path.abspath(filename) + cmd = [ self.filename, "--list" ] try: sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError, e: raise errors.AnsibleError("problem running %s (%s)" % (' '.join(cmd), e)) (stdout, stderr) = sp.communicate() self.data = stdout - self.groups = self._parse() + self.groups = self._parse(stderr) + + def _parse(self, err): - def _parse(self): all_hosts = {} + self.raw = utils.parse_json(self.data) + all = Group('all') + groups = dict(all=all) + group = None + + if 'failed' in self.raw: + sys.stderr.write(err + "\n") + raise errors.AnsibleError("failed to parse executable inventory script results: %s" % self.raw) - self.raw = utils.parse_json(self.data) - all=Group('all') - groups = dict(all=all) - group = None for (group_name, data) in self.raw.items(): + group = groups[group_name] = Group(group_name) host = None + if not isinstance(data, dict): data = {'hosts': data} + elif not any(k in data for k in ('hosts','vars')): + data = {'hosts': [group_name], 'vars': data} + if 'hosts' in data: + for hostname in data['hosts']: if not hostname in all_hosts: all_hosts[hostname] = Host(hostname) host = all_hosts[hostname] group.add_host(host) + if 'vars' in data: for k, v in data['vars'].iteritems(): - group.set_variable(k, v) - all.add_child_group(group) + if group.name == all.name: + all.set_variable(k, v) + else: + group.set_variable(k, v) + if group.name != all.name: + all.add_child_group(group) + # Separate loop to ensure all groups are defined for (group_name, data) in self.raw.items(): if isinstance(data, dict) and 'children' in data: @@ -67,3 +90,13 @@ def _parse(self): if child_name in groups: groups[group_name].add_child_group(groups[child_name]) return groups + + def get_host_variables(self, host): + """ Runs