├── README.md ├── ansible-dist ├── .gitignore ├── CHANGELOG.md ├── COPYING ├── MANIFEST.in ├── Makefile ├── README.md ├── VERSION ├── bin │ ├── ansible │ ├── ansible-playbook │ └── ansible-pull ├── hacking │ ├── README │ ├── env-setup │ └── test-module ├── lib │ └── ansible │ │ ├── __init__.py │ │ ├── callbacks.py │ │ ├── constants.py │ │ ├── errors.py │ │ ├── inventory │ │ ├── __init__.py │ │ ├── group.py │ │ ├── host.py │ │ ├── ini.py │ │ ├── script.py │ │ └── yaml.py │ │ ├── playbook │ │ ├── __init__.py │ │ ├── play.py │ │ └── task.py │ │ ├── runner │ │ ├── __init__.py │ │ ├── connection │ │ │ ├── __init__.py │ │ │ ├── local.py │ │ │ ├── paramiko_ssh.py │ │ │ └── ssh.py │ │ └── poller.py │ │ └── utils.py ├── library │ ├── apt │ ├── assemble │ ├── async_status │ ├── async_wrapper │ ├── authorized_key │ ├── command │ ├── copy │ ├── facter │ ├── failtest │ ├── fetch │ ├── file │ ├── git │ ├── group │ ├── ohai │ ├── ping │ ├── raw │ ├── service │ ├── setup │ ├── shell │ ├── slurp │ ├── template │ ├── user │ ├── virt │ └── yum ├── packaging │ ├── arch │ │ └── PKGBUILD │ ├── debian │ │ ├── README.txt │ │ ├── ansible.dirs │ │ ├── ansible.install │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── docs │ │ ├── pycompat │ │ └── rules │ ├── gentoo │ │ └── README.md │ └── rpm │ │ └── ansible.spec └── setup.py ├── hosts ├── localhost.yaml ├── node-runner.cf ├── node-runner.sh └── node ├── mutt.yaml └── templates ├── mutt.rc.in └── whazzup.in /README.md: -------------------------------------------------------------------------------- 1 | # n-repo 2 | 3 | An example repository with shell scripts for using [Ansible](http://ansible.github.com/) in pull-mode. 4 | 5 | Read about it [here][1]. 6 | 7 | [1]: http://jpmens.net/2012/07/14/ansible-pull-instead-of-push/ 8 | -------------------------------------------------------------------------------- /ansible-dist/.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | build 3 | # Emacs backup files... 4 | *~ 5 | .\#* 6 | # (s)rpm building stuff 7 | MANIFEST 8 | dist 9 | rpm-build 10 | # Eclipse/PyDev stuff... 11 | .project 12 | .pydevproject 13 | -------------------------------------------------------------------------------- /ansible-dist/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Ansible Changes By Release 2 | ========================== 3 | 4 | 0.6 "Cabo" ------------ pending 5 | 6 | * groups variable available a hash to return the hosts in each group name 7 | * fetch module now does not fail a system when requesting file paths (ex: logs) that don't exist 8 | * apt module now takes an optional install-recommends=yes|no (default yes) 9 | * fixes to the return codes of the copy module 10 | * copy module takes a remote md5sum to avoid large file transfer 11 | * when sudoing to root, still use /etc/ansible/setup as the metadata path, as if root 12 | * support to tag tasks and includes and use --tags in playbook CLI 13 | * various user and group module fixes (error handling, etc) 14 | * apt module now takes an optional force parameter 15 | * slightly better psychic service status handling for the service module 16 | 17 | 0.5 "Amsterdam" ------- July 04, 2012 18 | 19 | * Service module gets more accurate service states when running with upstart 20 | * Jinja2 usage in playbooks (not templates), reinstated, supports %include directive 21 | * support for --connection ssh (supports Kerberos, bastion hosts, etc), requires ControlMaster 22 | * misc tracebacks replaced with error messages 23 | * various API/internals refactoring 24 | * vars can be built from other variables 25 | * support for exclusion of hosts/groups with "!groupname" 26 | * various changes to support md5 tool differences for FreeBSD nodes & OS X clients 27 | * "unparseable" command output shows in command output for easier debugging 28 | * mktemp is no longer required on remotes (not available on BSD) 29 | * support for older versions of python-apt in the apt module 30 | * a new "assemble" module, for constructing files from pieces of files (inspired by Puppet "fragments" idiom) 31 | * ability to override most default values with ANSIBLE_FOO environment variables 32 | * --module-path parameter can support multiple directories seperated with the OS path seperator 33 | * with_items can take a variable of type list 34 | * ansible_python_interpreter variable available for systems with more than one Python 35 | * BIOS and VMware "fact" upgrades 36 | * cowsay is used by ansible-playbook if installed to improve output legibility (try installing it) 37 | * authorized_key module 38 | * SELinux facts now sourced from the python selinux library 39 | * removed module debug option -D 40 | * added --verbose, which shows output from successful playbook operations 41 | * print the output of the raw command inside /usr/bin/ansible as with command/shell 42 | * basic setup module support for Solaris 43 | * ./library relative to the playbook is always in path so modules can be included in tarballs with playbooks 44 | 45 | 0.4 "Unchained" ------- May 23, 2012 46 | 47 | Internals/Core 48 | * internal inventory API now more object oriented, parsers decoupled 49 | * async handling improvements 50 | * misc fixes for running ansible on OS X (overlord only) 51 | * sudo improvements, now works much more smoothly 52 | * sudo to a particular user with -U/--sudo-user, or using 'sudo_user: foo' in a playbook 53 | * --private-key CLI option to work with pem files 54 | 55 | Inventory 56 | * can use -i host1,host2,host3:port to specify hosts not in inventory (replaces --override-hosts) 57 | * ansible INI style format can do groups of groups [groupname:children] and group vars [groupname:vars] 58 | * groups and users module takes an optional system=yes|no on creation (default no) 59 | * list of hosts in playbooks can be expressed as a YAML list in addition to ; delimited 60 | 61 | Playbooks 62 | * variables can be replaced like ${foo.nested_hash_key.nested_subkey[array_index]} 63 | * unicode now ok in templates (assumes utf8) 64 | * able to pass host specifier or group name in to "hosts:" with --extra-vars 65 | * ansible-pull script and example playbook (extreme scaling, remediation) 66 | * inventory_hostname variable available that contains the value of the host as ansible knows it 67 | * variables in the 'all' section can be used to define other variables based on those values 68 | * 'group_names' is now a variable made available to templates 69 | * first_available_file feature, see selective_file_sources.yml in examples/playbooks for info 70 | * --extra-vars="a=2 b=3" etc, now available to inject parameters into playbooks from CLI 71 | 72 | Incompatible Changes 73 | * jinja2 is only usable in templates, not playbooks, use $foo instead 74 | * --override-hosts removed, can use -i with comma notation (-i "ahost,bhost") 75 | * modules can no longer include stderr output (paramiko limitation from sudo) 76 | 77 | Module Changes 78 | * tweaks to SELinux implementation for file module 79 | * fixes for yum module corner cases on EL5 80 | * file module now correctly returns the mode in octal 81 | * fix for symlink handling in the file module 82 | * service takes an enable=yes|no which works with chkconfig or updates-rc.d as appropriate 83 | * service module works better on Ubuntu 84 | * git module now does resets and such to work more smoothly on updates 85 | * modules all now log to syslog 86 | * enabled=yes|no on a service can be used to toggle chkconfig & updates-rc.d states 87 | * git module supports branch= 88 | * service fixes to better detect status using return codes of the service script 89 | * custom facts provided by the setup module mean no dependency on Ruby, facter, or ohai 90 | * service now has a state=reloaded 91 | * raw module for bootstrapping and talking to routers w/o Python, etc 92 | 93 | Misc Bugfixes 94 | * fixes for variable parsing in only_if lines 95 | * misc fixes to key=value parsing 96 | * variables with mixed case now legal 97 | * fix to internals of hacking/test-module development script 98 | 99 | 100 | 0.3 "Baluchitherium" -- April 23, 2012 101 | 102 | * Packaging for Debian, Gentoo, and Arch 103 | * Improvements to the apt and yum modules 104 | * A virt module 105 | * SELinux support for the file module 106 | * Ability to use facts from other systems in templates (aka exported 107 | resources like support) 108 | * Built in Ansible facts so you don't need ohai, facter, or Ruby 109 | * tempdir selections that work with noexec mounted /tmp 110 | * templates happen locally, not remotely, so no dependency on 111 | python-jinja2 for remote computers 112 | * advanced inventory format in YAML allows more control over variables 113 | per host and per group 114 | * variables in playbooks can be structured/nested versus just a flat namespace 115 | * manpage upgrades (docs) 116 | * various bugfixes 117 | * can specify a default --user for playbooks rather than specifying it 118 | in the playbook file 119 | * able to specify ansible port in ansible host file (see docs) 120 | * refactored Inventory API to make it easier to write scripts using Ansible 121 | * looping capability for playbooks (with_items) 122 | * support for using sudo with a password 123 | * module arguments can be unicode 124 | * A local connection type, --connection=local, for use with cron or 125 | in kickstarts 126 | * better module debugging with -D 127 | * fetch module for pulling in files from remote hosts 128 | * command task supports creates=foo for idempotent semantics, won't 129 | run if file foo already exists 130 | 131 | 0.0.2 and 0.0.1 132 | 133 | * Initial stages of project 134 | 135 | -------------------------------------------------------------------------------- /ansible-dist/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md packaging/rpm/ansible.spec COPYING 2 | include examples/hosts 3 | include packaging/distutils/setup.py 4 | recursive-include docs * 5 | recursive-include library * 6 | include Makefile 7 | -------------------------------------------------------------------------------- /ansible-dist/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | 3 | ######################################################## 4 | # Makefile for Ansible 5 | # 6 | # useful targets: 7 | # make sdist ---------------- produce a tarball 8 | # make rpm ----------------- produce RPMs 9 | # make debian --------------- produce a dpkg (FIXME?) 10 | # make docs ----------------- rebuild the manpages (results are checked in) 11 | # make tests ---------------- run the tests 12 | # make pyflakes, make pep8 -- source code checks 13 | 14 | ######################################################## 15 | # variable section 16 | 17 | NAME = "ansible" 18 | 19 | # Manpages are currently built with asciidoc -- would like to move to markdown 20 | # This doesn't evaluate until it's called. The -D argument is the 21 | # directory of the target file ($@), kinda like `dirname`. 22 | ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $< 23 | ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml 24 | MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 25 | 26 | SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") 27 | 28 | # VERSION file provides one place to update the software version 29 | VERSION := $(shell cat VERSION) 30 | 31 | # RPM build parameters 32 | RPMSPECDIR= packaging/rpm 33 | RPMSPEC = $(RPMSPECDIR)/ansible.spec 34 | RPMVERSION := $(shell awk '/Version/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) 35 | RPMRELEASE := $(shell awk '/Release/{print $$2; exit}' < $(RPMSPEC) | cut -d "%" -f1) 36 | RPMDIST = $(shell rpm --eval '%dist') 37 | RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)" 38 | 39 | ######################################################## 40 | 41 | all: clean python 42 | 43 | tests: 44 | PYTHONPATH=./lib nosetests -v 45 | 46 | # To force a rebuild of the docs run 'touch VERSION && make docs' 47 | docs: $(MANPAGES) 48 | 49 | # Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more 50 | # recently than %.1.asciidoc. 51 | %.1.asciidoc: %.1.asciidoc.in 52 | sed "s/%VERSION%/$(VERSION)/" $< > $@ 53 | 54 | # Regenerate %.1 if %.1.asciidoc or VERSION has been modified more 55 | # recently than %.1. (Implicitly runs the %.1.asciidoc recipe) 56 | %.1: %.1.asciidoc VERSION 57 | $(ASCII2MAN) 58 | 59 | loc: 60 | sloccount lib library bin 61 | 62 | pep8: 63 | @echo "#############################################" 64 | @echo "# Running PEP8 Compliance Tests" 65 | @echo "#############################################" 66 | pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261 lib/ bin/ 67 | 68 | pyflakes: 69 | pyflakes lib/ansible/*.py bin/* 70 | 71 | clean: 72 | @echo "Cleaning up distutils stuff" 73 | rm -rf build 74 | rm -rf dist 75 | @echo "Cleaning up byte compiled python stuff" 76 | find . -type f -regex ".*\.py[co]$$" -delete 77 | @echo "Cleaning up editor backup files" 78 | find . -type f \( -name "*~" -or -name "#*" \) -delete 79 | find . -type f \( -name "*.swp" \) -delete 80 | @echo "Cleaning up asciidoc to man transformations and results" 81 | find ./docs/man -type f -name "*.xml" -delete 82 | find ./docs/man -type f -name "*.asciidoc" -delete 83 | @echo "Cleaning up output from test runs" 84 | rm -rf test/test_data 85 | @echo "Cleaning up RPM building stuff" 86 | rm -rf MANIFEST rpm-build 87 | @echo "Cleaning up Debian building stuff" 88 | rm -rf debian 89 | rm -rf deb-build 90 | 91 | python: 92 | python setup.py build 93 | 94 | install: 95 | mkdir -p /usr/share/ansible 96 | cp ./library/* /usr/share/ansible/ 97 | python setup.py install 98 | 99 | sdist: clean 100 | python setup.py sdist -t MANIFEST.in 101 | 102 | rpmcommon: sdist 103 | @mkdir -p rpm-build 104 | @cp dist/*.gz rpm-build/ 105 | 106 | srpm: rpmcommon 107 | @rpmbuild --define "_topdir %(pwd)/rpm-build" \ 108 | --define "_builddir %{_topdir}" \ 109 | --define "_rpmdir %{_topdir}" \ 110 | --define "_srcrpmdir %{_topdir}" \ 111 | --define "_specdir $(RPMSPECDIR)" \ 112 | --define "_sourcedir %{_topdir}" \ 113 | -bs $(RPMSPEC) 114 | @echo "#############################################" 115 | @echo "Ansible SRPM is built:" 116 | @echo " rpm-build/$(RPMNVR).src.rpm" 117 | @echo "#############################################" 118 | 119 | rpm: rpmcommon 120 | @rpmbuild --define "_topdir %(pwd)/rpm-build" \ 121 | --define "_builddir %{_topdir}" \ 122 | --define "_rpmdir %{_topdir}" \ 123 | --define "_srcrpmdir %{_topdir}" \ 124 | --define "_specdir $(RPMSPECDIR)" \ 125 | --define "_sourcedir %{_topdir}" \ 126 | -ba $(RPMSPEC) 127 | @echo "#############################################" 128 | @echo "Ansible RPM is built:" 129 | @echo " rpm-build/noarch/$(RPMNVR).noarch.rpm" 130 | @echo "#############################################" 131 | 132 | debian: sdist 133 | deb: debian 134 | cp -r packaging/debian ./ 135 | chmod 755 debian/rules 136 | fakeroot debian/rules clean 137 | fakeroot dh_install 138 | fakeroot debian/rules binary 139 | 140 | # for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory 141 | 142 | -------------------------------------------------------------------------------- /ansible-dist/README.md: -------------------------------------------------------------------------------- 1 | # Don't use this -- example only 2 | 3 | This is a copy of https://github.com/ansible/ansible made on 2012-07-14. It contains 4 | a single change on line 33 of lib/ansible/runner/connection__init__.py which comments out loading paramiko_ssh for local connections. 5 | 6 | I've also removed the directories: 7 | 8 | test/ 9 | examples/ 10 | docs/ 11 | -------------------------------------------------------------------------------- /ansible-dist/VERSION: -------------------------------------------------------------------------------- 1 | 0.6 2 | -------------------------------------------------------------------------------- /ansible-dist/bin/ansible: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | ######################################################## 21 | 22 | import sys 23 | import getpass 24 | 25 | from ansible.runner import Runner 26 | import ansible.constants as C 27 | from ansible import utils 28 | from ansible import errors 29 | from ansible import callbacks 30 | from ansible import inventory 31 | 32 | ######################################################## 33 | 34 | class Cli(object): 35 | ''' code behind bin/ansible ''' 36 | 37 | # ---------------------------------------------- 38 | 39 | def __init__(self): 40 | self.stats = callbacks.AggregateStats() 41 | self.callbacks = callbacks.CliRunnerCallbacks() 42 | 43 | # ---------------------------------------------- 44 | 45 | def parse(self): 46 | ''' create an options parser for bin/ansible ''' 47 | 48 | parser = utils.base_parser(constants=C, runas_opts=True, async_opts=True, 49 | output_opts=True, connect_opts=True, usage='%prog [options]') 50 | parser.add_option('-a', '--args', dest='module_args', 51 | help="module arguments", default=C.DEFAULT_MODULE_ARGS) 52 | parser.add_option('-m', '--module-name', dest='module_name', 53 | help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME, 54 | default=C.DEFAULT_MODULE_NAME) 55 | options, args = parser.parse_args() 56 | self.callbacks.options = options 57 | 58 | if len(args) == 0 or len(args) > 1: 59 | parser.print_help() 60 | sys.exit(1) 61 | return (options, args) 62 | 63 | # ---------------------------------------------- 64 | 65 | def run(self, options, args): 66 | ''' use Runner lib to do SSH things ''' 67 | 68 | pattern = args[0] 69 | 70 | inventory_manager = inventory.Inventory(options.inventory) 71 | hosts = inventory_manager.list_hosts(pattern) 72 | if len(hosts) == 0: 73 | print >>sys.stderr, "No hosts matched" 74 | sys.exit(1) 75 | 76 | sshpass = None 77 | sudopass = None 78 | if options.ask_pass: 79 | sshpass = getpass.getpass(prompt="SSH password: ") 80 | if options.ask_sudo_pass: 81 | sudopass = getpass.getpass(prompt="sudo password: ") 82 | options.sudo = True 83 | if options.sudo_user: 84 | options.sudo = True 85 | options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER 86 | if options.tree: 87 | utils.prepare_writeable_dir(options.tree) 88 | 89 | runner = Runner( 90 | module_name=options.module_name, module_path=options.module_path, 91 | module_args=options.module_args, 92 | remote_user=options.remote_user, remote_pass=sshpass, 93 | inventory=inventory_manager, timeout=options.timeout, 94 | private_key_file=options.private_key_file, 95 | forks=options.forks, 96 | pattern=pattern, 97 | callbacks=self.callbacks, sudo=options.sudo, 98 | sudo_pass=sudopass,sudo_user=options.sudo_user, 99 | transport=options.connection, verbose=options.verbose 100 | ) 101 | 102 | if options.seconds: 103 | print "background launch...\n\n" 104 | results, poller = runner.run_async(options.seconds) 105 | results = self.poll_while_needed(poller, options) 106 | else: 107 | results = runner.run() 108 | 109 | return (runner, results) 110 | 111 | # ---------------------------------------------- 112 | 113 | def poll_while_needed(self, poller, options): 114 | ''' summarize results from Runner ''' 115 | 116 | # BACKGROUND POLL LOGIC when -B and -P are specified 117 | if options.seconds and options.poll_interval > 0: 118 | poller.wait(options.seconds, options.poll_interval) 119 | 120 | return poller.results 121 | 122 | 123 | ######################################################## 124 | 125 | if __name__ == '__main__': 126 | cli = Cli() 127 | (options, args) = cli.parse() 128 | try: 129 | (runner, results) = cli.run(options, args) 130 | except errors.AnsibleError, e: 131 | # Generic handler for ansible specific errors 132 | print "ERROR: %s" % str(e) 133 | sys.exit(1) 134 | 135 | -------------------------------------------------------------------------------- /ansible-dist/bin/ansible-playbook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (C) 2012, Michael DeHaan, 3 | 4 | # This file is part of Ansible 5 | # 6 | # Ansible is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Ansible is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Ansible. If not, see . 18 | 19 | ####################################################### 20 | 21 | import sys 22 | import getpass 23 | 24 | import ansible.playbook 25 | import ansible.constants as C 26 | from ansible import errors 27 | from ansible import callbacks 28 | from ansible import utils 29 | 30 | def main(args): 31 | ''' run ansible-playbook operations ''' 32 | 33 | # create parser for CLI options 34 | usage = "%prog playbook.yml" 35 | parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, runas_opts=True) 36 | parser.add_option('-e', '--extra-vars', dest="extra_vars", default=None, 37 | help="set additional key=value variables from the CLI") 38 | parser.add_option('-t', '--tags', dest='tags', default='all', 39 | help="only run plays and tasks tagged with these values") 40 | 41 | options, args = parser.parse_args(args) 42 | 43 | if len(args) == 0: 44 | parser.print_help(file=sys.stderr) 45 | return 1 46 | 47 | sshpass = None 48 | sudopass = None 49 | if options.ask_pass: 50 | sshpass = getpass.getpass(prompt="SSH password: ") 51 | if options.ask_sudo_pass: 52 | sudopass = getpass.getpass(prompt="sudo password: ") 53 | options.sudo = True 54 | if options.sudo_user: 55 | options.sudo = True 56 | options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER 57 | extra_vars = utils.parse_kv(options.extra_vars) 58 | only_tags = options.tags.split(",") 59 | 60 | # run all playbooks specified on the command line 61 | for playbook in args: 62 | 63 | stats = callbacks.AggregateStats() 64 | playbook_cb = callbacks.PlaybookCallbacks(verbose=options.verbose) 65 | runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=options.verbose) 66 | 67 | pb = ansible.playbook.PlayBook( 68 | playbook=playbook, 69 | module_path=options.module_path, 70 | host_list=options.inventory, 71 | forks=options.forks, 72 | verbose=options.verbose, 73 | remote_user=options.remote_user, 74 | remote_pass=sshpass, 75 | callbacks=playbook_cb, 76 | runner_callbacks=runner_cb, 77 | stats=stats, 78 | timeout=options.timeout, 79 | transport=options.connection, 80 | sudo=options.sudo, 81 | sudo_user=options.sudo_user, 82 | sudo_pass=sudopass, 83 | extra_vars=extra_vars, 84 | private_key_file=options.private_key_file, 85 | only_tags=only_tags, 86 | ) 87 | try: 88 | 89 | pb.run() 90 | hosts = sorted(pb.stats.processed.keys()) 91 | print callbacks.banner("PLAY RECAP") 92 | for h in hosts: 93 | t = pb.stats.summarize(h) 94 | print "%-30s : ok=%-4s changed=%-4s unreachable=%-4s failed=%-4s " % (h, 95 | t['ok'], t['changed'], t['unreachable'], t['failures'] 96 | ) 97 | print "\n" 98 | 99 | except errors.AnsibleError, e: 100 | print >>sys.stderr, "ERROR: %s" % e 101 | return 1 102 | 103 | return 0 104 | 105 | 106 | if __name__ == "__main__": 107 | try: 108 | sys.exit(main(sys.argv[1:])) 109 | except errors.AnsibleError, e: 110 | print >>sys.stderr, "ERROR: %s" % e 111 | sys.exit(1) 112 | 113 | -------------------------------------------------------------------------------- /ansible-dist/bin/ansible-pull: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ansible-pull is a script that runs ansible in local mode 4 | # after checking out a playbooks directory from git. There is an 5 | # example playbook to bootstrap this script in the examples/ dir which 6 | # installs ansible and sets it up to run on cron. 7 | # 8 | # usage: 9 | # ansible-pull -d /var/ansible/local -U http://wherever/content.git -C production 10 | # 11 | # the git repo must contain a playbook named 'local.yml' 12 | 13 | # (c) 2012, Stephen Fromm 14 | # 15 | # Ansible is free software: you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation, either version 3 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # Ansible is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with Ansible. If not, see . 27 | 28 | import os 29 | import subprocess 30 | import sys 31 | from optparse import OptionParser 32 | 33 | DEFAULT_PLAYBOOK = 'local.yml' 34 | 35 | def _run(cmd): 36 | cmd = subprocess.Popen(cmd, shell=True, 37 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 38 | (out, err) = cmd.communicate() 39 | print out 40 | if cmd.returncode != 0: 41 | print err 42 | return cmd.returncode 43 | 44 | def main(args): 45 | """ Set up and run a local playbook """ 46 | usage = "%prog [options]" 47 | parser = OptionParser() 48 | parser.add_option('-d', '--directory', dest='dest', default=None, 49 | help='Directory to checkout git repository') 50 | parser.add_option('-U', '--url', dest='url', 51 | default=None, 52 | help='URL of git repository') 53 | parser.add_option('-C', '--checkout', dest='checkout', 54 | default="HEAD", 55 | help='Branch/Tag/Commit to checkout. Defaults to HEAD.') 56 | options, args = parser.parse_args(args) 57 | 58 | git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout) 59 | cmd = 'ansible all -c local -m git -a "%s"' % git_opts 60 | print "cmd=%s" % cmd 61 | rc = _run(cmd) 62 | if rc != 0: 63 | return rc 64 | 65 | os.chdir(options.dest) 66 | cmd = 'ansible-playbook -c local %s' % DEFAULT_PLAYBOOK 67 | rc = _run(cmd) 68 | return rc 69 | 70 | if __name__ == '__main__': 71 | try: 72 | sys.exit(main(sys.argv[1:])) 73 | except KeyboardInterrupt, e: 74 | print >>sys.stderr, "Exit on user request.\n" 75 | sys.exit(1) 76 | -------------------------------------------------------------------------------- /ansible-dist/hacking/README: -------------------------------------------------------------------------------- 1 | The 'env-setup' script modifies your environment to allow you to run 2 | ansible from a git checkout. 3 | 4 | To use it from the root of a checkout: 5 | 6 | $ . ./hacking/env-setup 7 | 8 | Note the space between the '.' and the './' 9 | -------------------------------------------------------------------------------- /ansible-dist/hacking/env-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: source ./hacking/env-setup 3 | # modifies environment for running Ansible from checkout 4 | 5 | PREFIX_PYTHONPATH="$PWD/lib" 6 | PREFIX_PATH="$PWD/bin" 7 | PREFIX_MANPATH="$PWD/docs/man" 8 | 9 | export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH 10 | export PATH=$PREFIX_PATH:$PATH 11 | export ANSIBLE_LIBRARY="$PWD/library" 12 | export MANPATH=$PREFIX_MANPATH:$MANPATH 13 | 14 | echo "PATH=$PATH" 15 | echo "PYTHONPATH=$PYTHONPATH" 16 | echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY" 17 | echo "MANPATH=$MANPATH" 18 | 19 | echo "Reminder: specify your host file with -i" 20 | echo "Done." 21 | -------------------------------------------------------------------------------- /ansible-dist/hacking/test-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | # this script is for testing modules without running through the 22 | # entire guts of ansible, and is very helpful for when developing 23 | # modules 24 | # 25 | # example: 26 | # test-module ../library/command /bin/sleep 3 27 | # test-module ../library/service name=httpd ensure=restarted 28 | 29 | import sys 30 | import os 31 | import subprocess 32 | import traceback 33 | from ansible import utils 34 | 35 | try: 36 | import json 37 | except ImportError: 38 | import simplejson as json 39 | 40 | modfile = None 41 | 42 | if len(sys.argv) == 1: 43 | print >>sys.stderr, "usage: test-module ./library/command [key=value ...]" 44 | sys.exit(1) 45 | 46 | modfile = sys.argv[1] 47 | if len(sys.argv) > 1: 48 | args = " ".join(sys.argv[2:]) 49 | else: 50 | args = "" 51 | 52 | argspath = os.path.expanduser("~/.ansible_test_module_arguments") 53 | argsfile = open(argspath, 'w') 54 | argsfile.write(args) 55 | argsfile.close() 56 | 57 | os.system("chmod +x %s" % modfile) 58 | cmd = subprocess.Popen("%s %s" % (modfile, argspath), 59 | shell=True, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE) 62 | (out, err) = cmd.communicate() 63 | 64 | if err and err != '': 65 | print "***********************************" 66 | print "RECIEVED DATA ON STDERR, THIS WILL CRASH YOUR MODULE" 67 | print err 68 | sys.exit(1) 69 | 70 | try: 71 | print "***********************************" 72 | print "RAW OUTPUT" 73 | print out 74 | results = utils.parse_json(out) 75 | 76 | except: 77 | print "***********************************" 78 | print "INVALID OUTPUT FORMAT" 79 | print out 80 | traceback.print_exc() 81 | sys.exit(1) 82 | 83 | print "***********************************" 84 | print "PARSED OUTPUT" 85 | 86 | print utils.bigjson(results) 87 | 88 | sys.exit(0) 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | __version__ = '0.6' 18 | __author__ = 'Michael DeHaan' 19 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/callbacks.py: -------------------------------------------------------------------------------- 1 | # (C) 2012, Michael DeHaan, 2 | 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ####################################################### 19 | 20 | import utils 21 | import sys 22 | import getpass 23 | import os 24 | import subprocess 25 | 26 | ####################################################### 27 | 28 | class AggregateStats(object): 29 | ''' holds stats about per-host activity during playbook runs ''' 30 | 31 | def __init__(self): 32 | self.processed = {} 33 | self.failures = {} 34 | self.ok = {} 35 | self.dark = {} 36 | self.changed = {} 37 | self.skipped = {} 38 | 39 | def _increment(self, what, host): 40 | ''' helper function to bump a statistic ''' 41 | 42 | self.processed[host] = 1 43 | prev = (getattr(self, what)).get(host, 0) 44 | getattr(self, what)[host] = prev+1 45 | 46 | def compute(self, runner_results, setup=False, poll=False): 47 | ''' walk through all results and increment stats ''' 48 | 49 | for (host, value) in runner_results.get('contacted', {}).iteritems(): 50 | if ('failed' in value and bool(value['failed'])) or ('rc' in value and value['rc'] != 0): 51 | self._increment('failures', host) 52 | elif 'skipped' in value and bool(value['skipped']): 53 | self._increment('skipped', host) 54 | elif 'changed' in value and bool(value['changed']): 55 | if not setup and not poll: 56 | self._increment('changed', host) 57 | self._increment('ok', host) 58 | else: 59 | if not poll or ('finished' in value and bool(value['finished'])): 60 | self._increment('ok', host) 61 | 62 | for (host, value) in runner_results.get('dark', {}).iteritems(): 63 | self._increment('dark', host) 64 | 65 | 66 | def summarize(self, host): 67 | ''' return information about a particular host ''' 68 | 69 | return dict( 70 | ok = self.ok.get(host, 0), 71 | failures = self.failures.get(host, 0), 72 | unreachable = self.dark.get(host,0), 73 | changed = self.changed.get(host, 0), 74 | skipped = self.skipped.get(host, 0) 75 | ) 76 | 77 | ######################################################################## 78 | 79 | def banner(msg): 80 | res = "" 81 | global COWSAY 82 | if os.path.exists("/usr/bin/cowsay"): 83 | COWSAY = "/usr/bin/cowsay" 84 | elif os.path.exists("/usr/games/cowsay"): 85 | COWSAY = "/usr/games/cowsay" 86 | else: 87 | COWSAY = None 88 | 89 | if COWSAY != None: 90 | cmd = subprocess.Popen("%s -W 60 \"%s\"" % (COWSAY, msg), 91 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 92 | (out, err) = cmd.communicate() 93 | res = "%s\n" % out 94 | else: 95 | res = "\n%s ********************* " % msg 96 | return res 97 | 98 | 99 | class DefaultRunnerCallbacks(object): 100 | ''' no-op callbacks for API usage of Runner() if no callbacks are specified ''' 101 | 102 | def __init__(self): 103 | pass 104 | 105 | def on_failed(self, host, res): 106 | pass 107 | 108 | def on_ok(self, host, res): 109 | pass 110 | 111 | def on_error(self, host, msg): 112 | pass 113 | 114 | def on_skipped(self, host): 115 | pass 116 | 117 | def on_unreachable(self, host, res): 118 | pass 119 | 120 | def on_no_hosts(self): 121 | pass 122 | 123 | def on_async_poll(self, host, res, jid, clock): 124 | pass 125 | 126 | def on_async_ok(self, host, res, jid): 127 | pass 128 | 129 | def on_async_failed(self, host, res, jid): 130 | pass 131 | 132 | ######################################################################## 133 | 134 | class CliRunnerCallbacks(DefaultRunnerCallbacks): 135 | ''' callbacks for use by /usr/bin/ansible ''' 136 | 137 | def __init__(self): 138 | # set by /usr/bin/ansible later 139 | self.options = None 140 | self._async_notified = {} 141 | 142 | def on_failed(self, host, res): 143 | self._on_any(host,res) 144 | 145 | def on_ok(self, host, res): 146 | self._on_any(host,res) 147 | 148 | def on_unreachable(self, host, res): 149 | if type(res) == dict: 150 | res = res.get('msg','') 151 | print "%s | FAILED => %s" % (host, res) 152 | if self.options.tree: 153 | utils.write_tree_file(self.options.tree, host, utils.bigjson(dict(failed=True, msg=res))) 154 | 155 | def on_skipped(self, host): 156 | pass 157 | 158 | def on_error(self, host, err): 159 | print >>sys.stderr, "err: [%s] => %s\n" % (host, err) 160 | 161 | def on_no_hosts(self): 162 | print >>sys.stderr, "no hosts matched\n" 163 | 164 | def on_async_poll(self, host, res, jid, clock): 165 | if jid not in self._async_notified: 166 | self._async_notified[jid] = clock + 1 167 | if self._async_notified[jid] > clock: 168 | self._async_notified[jid] = clock 169 | print " polling, %ss remaining"%(jid, clock) 170 | 171 | def on_async_ok(self, host, res, jid): 172 | print " finished on %s => %s"%(jid, host, utils.bigjson(res)) 173 | 174 | def on_async_failed(self, host, res, jid): 175 | print " FAILED on %s => %s"%(jid, host, utils.bigjson(res)) 176 | 177 | def _on_any(self, host, result): 178 | print utils.host_report_msg(host, self.options.module_name, result, self.options.one_line) 179 | if self.options.tree: 180 | utils.write_tree_file(self.options.tree, host, utils.bigjson(result)) 181 | 182 | ######################################################################## 183 | 184 | class PlaybookRunnerCallbacks(DefaultRunnerCallbacks): 185 | ''' callbacks used for Runner() from /usr/bin/ansible-playbook ''' 186 | 187 | def __init__(self, stats, verbose=False): 188 | self.stats = stats 189 | self._async_notified = {} 190 | self.verbose = verbose 191 | 192 | def on_unreachable(self, host, msg): 193 | print "fatal: [%s] => %s" % (host, msg) 194 | 195 | def on_failed(self, host, results): 196 | print "failed: [%s] => %s\n" % (host, utils.smjson(results)) 197 | 198 | def on_ok(self, host, host_result): 199 | # show verbose output for non-setup module results if --verbose is used 200 | if not self.verbose or host_result.get("verbose_override",None) is not None: 201 | print "ok: [%s]" % (host) 202 | else: 203 | print "ok: [%s] => %s" % (host, utils.smjson(host_result)) 204 | 205 | def on_error(self, host, err): 206 | print >>sys.stderr, "err: [%s] => %s\n" % (host, err) 207 | 208 | def on_skipped(self, host): 209 | print "skipping: [%s]\n" % host 210 | 211 | def on_no_hosts(self): 212 | print "no hosts matched or remaining\n" 213 | 214 | def on_async_poll(self, host, res, jid, clock): 215 | if jid not in self._async_notified: 216 | self._async_notified[jid] = clock + 1 217 | if self._async_notified[jid] > clock: 218 | self._async_notified[jid] = clock 219 | print " polling, %ss remaining"%(jid, clock) 220 | 221 | def on_async_ok(self, host, res, jid): 222 | print " finished on %s"%(jid, host) 223 | 224 | def on_async_failed(self, host, res, jid): 225 | print " FAILED on %s"%(jid, host) 226 | 227 | ######################################################################## 228 | 229 | class PlaybookCallbacks(object): 230 | ''' playbook.py callbacks used by /usr/bin/ansible-playbook ''' 231 | 232 | def __init__(self, verbose=False): 233 | self.verbose = verbose 234 | 235 | def on_start(self): 236 | pass 237 | 238 | def on_notify(self, host, handler): 239 | pass 240 | 241 | def on_task_start(self, name, is_conditional): 242 | print banner(utils.task_start_msg(name, is_conditional)) 243 | 244 | def on_vars_prompt(self, varname, private=True): 245 | msg = 'input for %s: ' % varname 246 | if private: 247 | return getpass.getpass(msg) 248 | return raw_input(msg) 249 | 250 | def on_setup_primary(self): 251 | print banner("SETUP PHASE") 252 | 253 | def on_setup_secondary(self): 254 | print banner("VARIABLE IMPORT PHASE") 255 | 256 | def on_import_for_host(self, host, imported_file): 257 | print "%s: importing %s" % (host, imported_file) 258 | 259 | def on_not_import_for_host(self, host, missing_file): 260 | print "%s: not importing file: %s" % (host, missing_file) 261 | 262 | def on_play_start(self, pattern): 263 | print banner("PLAY [%s]" % pattern) 264 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/constants.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | import os 20 | 21 | 22 | DEFAULT_HOST_LIST = os.environ.get('ANSIBLE_HOSTS', 23 | '/etc/ansible/hosts') 24 | DEFAULT_MODULE_PATH = os.environ.get('ANSIBLE_LIBRARY', 25 | '/usr/share/ansible') 26 | DEFAULT_REMOTE_TMP = os.environ.get('ANSIBLE_REMOTE_TMP', 27 | '$HOME/.ansible/tmp') 28 | 29 | DEFAULT_MODULE_NAME = 'command' 30 | DEFAULT_PATTERN = '*' 31 | DEFAULT_FORKS = os.environ.get('ANSIBLE_FORKS',5) 32 | DEFAULT_MODULE_ARGS = os.environ.get('ANSIBLE_MODULE_ARGS','') 33 | DEFAULT_TIMEOUT = os.environ.get('ANSIBLE_TIMEOUT',10) 34 | DEFAULT_POLL_INTERVAL = os.environ.get('ANSIBLE_POLL_INTERVAL',15) 35 | DEFAULT_REMOTE_USER = os.environ.get('ANSIBLE_REMOTE_USER','root') 36 | DEFAULT_REMOTE_PASS = None 37 | DEFAULT_PRIVATE_KEY_FILE = os.environ.get('ANSIBLE_PRIVATE_KEY_FILE',None) 38 | DEFAULT_SUDO_PASS = None 39 | DEFAULT_SUDO_USER = os.environ.get('ANSIBLE_SUDO_USER','root') 40 | DEFAULT_REMOTE_PORT = 22 41 | DEFAULT_TRANSPORT = os.environ.get('ANSIBLE_TRANSPORT','paramiko') 42 | DEFAULT_TRANSPORT_OPTS = ['local', 'paramiko', 'ssh'] 43 | 44 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/errors.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | 19 | class AnsibleError(Exception): 20 | """ 21 | The base Ansible exception from which all others should subclass. 22 | """ 23 | 24 | def __init__(self, msg): 25 | self.msg = msg 26 | 27 | def __str__(self): 28 | return self.msg 29 | 30 | 31 | class AnsibleFileNotFound(AnsibleError): 32 | pass 33 | 34 | class AnsibleConnectionFailed(AnsibleError): 35 | pass 36 | 37 | 38 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | import fnmatch 21 | import os 22 | 23 | import subprocess 24 | import ansible.constants as C 25 | from ansible.inventory.ini import InventoryParser 26 | from ansible.inventory.yaml import InventoryParserYaml 27 | from ansible.inventory.script import InventoryScript 28 | from ansible.inventory.group import Group 29 | from ansible.inventory.host import Host 30 | from ansible import errors 31 | from ansible import utils 32 | 33 | class Inventory(object): 34 | """ 35 | Host inventory for ansible. 36 | """ 37 | 38 | def __init__(self, host_list=C.DEFAULT_HOST_LIST): 39 | 40 | # the host file file, or script path, or list of hosts 41 | # if a list, inventory data will NOT be loaded 42 | self.host_list = host_list 43 | 44 | # the inventory object holds a list of groups 45 | self.groups = [] 46 | 47 | # a list of host(names) to contain current inquiries to 48 | self._restriction = None 49 | 50 | # whether the inventory file is a script 51 | self._is_script = False 52 | 53 | if type(host_list) in [ str, unicode ]: 54 | if host_list.find(",") != -1: 55 | host_list = host_list.split(",") 56 | 57 | if type(host_list) == list: 58 | all = Group('all') 59 | self.groups = [ all ] 60 | for x in host_list: 61 | if x.find(":") != -1: 62 | tokens = x.split(":",1) 63 | all.add_host(Host(tokens[0], tokens[1])) 64 | else: 65 | all.add_host(Host(x)) 66 | elif os.access(host_list, os.X_OK): 67 | self._is_script = True 68 | self.parser = InventoryScript(filename=host_list) 69 | self.groups = self.parser.groups.values() 70 | else: 71 | data = file(host_list).read() 72 | if not data.startswith("---"): 73 | self.parser = InventoryParser(filename=host_list) 74 | self.groups = self.parser.groups.values() 75 | else: 76 | self.parser = InventoryParserYaml(filename=host_list) 77 | self.groups = self.parser.groups.values() 78 | 79 | def _match(self, str, pattern_str): 80 | return fnmatch.fnmatch(str, pattern_str) 81 | 82 | def get_hosts(self, pattern="all"): 83 | """ Get all host objects matching the pattern """ 84 | hosts = {} 85 | patterns = pattern.replace(";",":").split(":") 86 | 87 | groups = self.get_groups() 88 | for pat in patterns: 89 | if pat.startswith("!"): 90 | pat = pat[1:] 91 | inverted = True 92 | else: 93 | inverted = False 94 | for group in groups: 95 | for host in group.get_hosts(): 96 | if group.name == pat or pat == 'all' or self._match(host.name, pat): 97 | #must test explicitly for None because [] means no hosts allowed 98 | if self._restriction==None or host.name in self._restriction: 99 | if inverted: 100 | if host.name in hosts: 101 | del hosts[host.name] 102 | else: 103 | hosts[host.name] = host 104 | return sorted(hosts.values(), key=lambda x: x.name) 105 | 106 | def get_groups(self): 107 | return self.groups 108 | 109 | def get_host(self, hostname): 110 | for group in self.groups: 111 | for host in group.get_hosts(): 112 | if hostname == host.name: 113 | return host 114 | return None 115 | 116 | def get_group(self, groupname): 117 | for group in self.groups: 118 | if group.name == groupname: 119 | return group 120 | return None 121 | 122 | def get_group_variables(self, groupname): 123 | group = self.get_group(groupname) 124 | if group is None: 125 | raise Exception("group not found: %s" % groupname) 126 | return group.get_variables() 127 | 128 | def get_variables(self, hostname): 129 | 130 | if self._is_script: 131 | # TODO: move this to inventory_script.py 132 | host = self.get_host(hostname) 133 | cmd = subprocess.Popen( 134 | [self.host_list,"--host",hostname], 135 | stdout=subprocess.PIPE, 136 | stderr=subprocess.PIPE 137 | ) 138 | (out, err) = cmd.communicate() 139 | results = utils.parse_json(out) 140 | results['inventory_hostname'] = hostname 141 | groups = [ g.name for g in host.get_groups() if g.name != 'all' ] 142 | results['group_names'] = sorted(groups) 143 | return results 144 | 145 | host = self.get_host(hostname) 146 | if host is None: 147 | raise Exception("host not found: %s" % hostname) 148 | return host.get_variables() 149 | 150 | def add_group(self, group): 151 | self.groups.append(group) 152 | 153 | def list_hosts(self, pattern="all"): 154 | return [ h.name for h in self.get_hosts(pattern) ] 155 | 156 | def list_groups(self): 157 | return [ g.name for g in self.groups ] 158 | 159 | def get_restriction(self): 160 | return self._restriction 161 | 162 | def restrict_to(self, restriction, append_missing=False): 163 | """ Restrict list operations to the hosts given in restriction """ 164 | 165 | if type(restriction) != list: 166 | restriction = [ restriction ] 167 | self._restriction = restriction 168 | 169 | def lift_restriction(self): 170 | """ Do not restrict list operations """ 171 | 172 | self._restriction = None 173 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/group.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | # from ansible import errors 21 | 22 | class Group(object): 23 | """ 24 | Group of ansible hosts 25 | """ 26 | 27 | def __init__(self, name=None): 28 | self.name = name 29 | self.hosts = [] 30 | self.vars = {} 31 | self.child_groups = [] 32 | self.parent_groups = [] 33 | if self.name is None: 34 | raise Exception("group name is required") 35 | 36 | def add_child_group(self, group): 37 | if self == group: 38 | raise Exception("can't add group to itself") 39 | self.child_groups.append(group) 40 | group.parent_groups.append(self) 41 | 42 | def add_host(self, host): 43 | self.hosts.append(host) 44 | host.add_group(self) 45 | 46 | def set_variable(self, key, value): 47 | self.vars[key] = value 48 | 49 | def get_hosts(self): 50 | hosts = [] 51 | for kid in self.child_groups: 52 | hosts.extend(kid.get_hosts()) 53 | hosts.extend(self.hosts) 54 | return hosts 55 | 56 | def get_variables(self): 57 | vars = {} 58 | # FIXME: verify this variable override order is what we want 59 | for ancestor in self.get_ancestors(): 60 | vars.update(ancestor.get_variables()) 61 | vars.update(self.vars) 62 | return vars 63 | 64 | def _get_ancestors(self): 65 | results = {} 66 | for g in self.parent_groups: 67 | results[g.name] = g 68 | results.update(g._get_ancestors()) 69 | return results 70 | 71 | def get_ancestors(self): 72 | return self._get_ancestors().values() 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/host.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | from ansible import errors 21 | import ansible.constants as C 22 | 23 | class Host(object): 24 | """ 25 | Group of ansible hosts 26 | """ 27 | 28 | def __init__(self, name=None, port=None): 29 | self.name = name 30 | self.vars = {} 31 | self.groups = [] 32 | if port and port != C.DEFAULT_REMOTE_PORT: 33 | self.set_variable('ansible_ssh_port', int(port)) 34 | 35 | if self.name is None: 36 | raise Exception("host name is required") 37 | 38 | def add_group(self, group): 39 | self.groups.append(group) 40 | 41 | def set_variable(self, key, value): 42 | self.vars[key]=value; 43 | 44 | def get_groups(self): 45 | groups = {} 46 | for g in self.groups: 47 | groups[g.name] = g 48 | ancestors = g.get_ancestors() 49 | for a in ancestors: 50 | groups[a.name] = a 51 | return groups.values() 52 | 53 | def get_variables(self): 54 | results = {} 55 | for group in self.groups: 56 | results.update(group.get_variables()) 57 | results.update(self.vars) 58 | results['inventory_hostname'] = self.name 59 | groups = self.get_groups() 60 | results['group_names'] = sorted([ g.name for g in groups if g.name != 'all']) 61 | return results 62 | 63 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/ini.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | import fnmatch 21 | import os 22 | import subprocess 23 | 24 | import ansible.constants as C 25 | from ansible.inventory.host import Host 26 | from ansible.inventory.group import Group 27 | from ansible import errors 28 | from ansible import utils 29 | 30 | class InventoryParser(object): 31 | """ 32 | Host inventory for ansible. 33 | """ 34 | 35 | def __init__(self, filename=C.DEFAULT_HOST_LIST): 36 | 37 | fh = open(filename) 38 | self.lines = fh.readlines() 39 | self.groups = {} 40 | self.hosts = {} 41 | self._parse() 42 | 43 | def _parse(self): 44 | 45 | self._parse_base_groups() 46 | self._parse_group_children() 47 | self._parse_group_variables() 48 | return self.groups 49 | 50 | # [webservers] 51 | # alpha 52 | # beta:2345 53 | # gamma sudo=True user=root 54 | # delta asdf=jkl favcolor=red 55 | 56 | def _parse_base_groups(self): 57 | 58 | ungrouped = Group(name='ungrouped') 59 | all = Group(name='all') 60 | all.add_child_group(ungrouped) 61 | 62 | self.groups = dict(all=all, ungrouped=ungrouped) 63 | active_group_name = 'ungrouped' 64 | 65 | for line in self.lines: 66 | if line.startswith("["): 67 | active_group_name = line.replace("[","").replace("]","").strip() 68 | if line.find(":vars") != -1 or line.find(":children") != -1: 69 | active_group_name = None 70 | else: 71 | new_group = self.groups[active_group_name] = Group(name=active_group_name) 72 | all.add_child_group(new_group) 73 | elif line.startswith("#") or line == '': 74 | pass 75 | elif active_group_name: 76 | tokens = line.split() 77 | if len(tokens) == 0: 78 | continue 79 | hostname = tokens[0] 80 | port = C.DEFAULT_REMOTE_PORT 81 | if hostname.find(":") != -1: 82 | tokens2 = hostname.split(":") 83 | hostname = tokens2[0] 84 | port = tokens2[1] 85 | host = None 86 | if hostname in self.hosts: 87 | host = self.hosts[hostname] 88 | else: 89 | host = Host(name=hostname, port=port) 90 | self.hosts[hostname] = host 91 | if len(tokens) > 1: 92 | for t in tokens[1:]: 93 | (k,v) = t.split("=") 94 | host.set_variable(k,v) 95 | self.groups[active_group_name].add_host(host) 96 | 97 | # [southeast:children] 98 | # atlanta 99 | # raleigh 100 | 101 | def _parse_group_children(self): 102 | group = None 103 | 104 | for line in self.lines: 105 | line = line.strip() 106 | if line is None or line == '': 107 | continue 108 | if line.startswith("[") and line.find(":children]") != -1: 109 | line = line.replace("[","").replace(":children]","") 110 | group = self.groups.get(line, None) 111 | if group is None: 112 | group = self.groups[line] = Group(name=line) 113 | elif line.startswith("#"): 114 | pass 115 | elif line.startswith("["): 116 | group = None 117 | elif group: 118 | kid_group = self.groups.get(line, None) 119 | if kid_group is None: 120 | raise errors.AnsibleError("child group is not defined: (%s)" % line) 121 | else: 122 | group.add_child_group(kid_group) 123 | 124 | 125 | # [webservers:vars] 126 | # http_port=1234 127 | # maxRequestsPerChild=200 128 | 129 | def _parse_group_variables(self): 130 | group = None 131 | for line in self.lines: 132 | line = line.strip() 133 | if line.startswith("[") and line.find(":vars]") != -1: 134 | line = line.replace("[","").replace(":vars]","") 135 | group = self.groups.get(line, None) 136 | if group is None: 137 | raise errors.AnsibleError("can't add vars to undefined group: %s" % line) 138 | elif line.startswith("#"): 139 | pass 140 | elif line.startswith("["): 141 | group = None 142 | elif line == '': 143 | pass 144 | elif group: 145 | if line.find("=") == -1: 146 | raise errors.AnsibleError("variables assigned to group must be in key=value form") 147 | else: 148 | (k,v) = line.split("=") 149 | group.set_variable(k,v) 150 | 151 | 152 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/script.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | import os 21 | import subprocess 22 | import ansible.constants as C 23 | from ansible.inventory.host import Host 24 | from ansible.inventory.group import Group 25 | from ansible import errors 26 | from ansible import utils 27 | 28 | class InventoryScript(object): 29 | """ 30 | Host inventory parser for ansible using external inventory scripts. 31 | """ 32 | 33 | def __init__(self, filename=C.DEFAULT_HOST_LIST): 34 | 35 | cmd = [ filename, "--list" ] 36 | sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 37 | (stdout, stderr) = sp.communicate() 38 | self.data = stdout 39 | self.groups = self._parse() 40 | 41 | def _parse(self): 42 | groups = {} 43 | self.raw = utils.parse_json(self.data) 44 | all=Group('all') 45 | self.groups = dict(all=all) 46 | group = None 47 | for (group_name, hosts) in self.raw.items(): 48 | group = groups[group_name] = Group(group_name) 49 | host = None 50 | for hostname in hosts: 51 | host = Host(hostname) 52 | group.add_host(host) 53 | # FIXME: hack shouldn't be needed 54 | all.add_host(host) 55 | all.add_child_group(group) 56 | return groups 57 | 58 | 59 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/inventory/yaml.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | import ansible.constants as C 21 | from ansible.inventory.host import Host 22 | from ansible.inventory.group import Group 23 | from ansible import errors 24 | from ansible import utils 25 | 26 | class InventoryParserYaml(object): 27 | """ 28 | Host inventory for ansible. 29 | """ 30 | 31 | def __init__(self, filename=C.DEFAULT_HOST_LIST): 32 | 33 | fh = open(filename) 34 | data = fh.read() 35 | fh.close() 36 | self._hosts = {} 37 | self._parse(data) 38 | 39 | def _make_host(self, hostname): 40 | if hostname in self._hosts: 41 | return self._hosts[hostname] 42 | else: 43 | host = Host(hostname) 44 | self._hosts[hostname] = host 45 | return host 46 | 47 | # see file 'test/yaml_hosts' for syntax 48 | 49 | def _parse(self, data): 50 | 51 | all = Group('all') 52 | ungrouped = Group('ungrouped') 53 | all.add_child_group(ungrouped) 54 | 55 | self.groups = dict(all=all, ungrouped=ungrouped) 56 | grouped_hosts = [] 57 | 58 | yaml = utils.parse_yaml(data) 59 | 60 | # first add all groups 61 | for item in yaml: 62 | if type(item) == dict and 'group' in item: 63 | group = Group(item['group']) 64 | 65 | for subresult in item.get('hosts',[]): 66 | 67 | if type(subresult) in [ str, unicode ]: 68 | host = self._make_host(subresult) 69 | group.add_host(host) 70 | grouped_hosts.append(host) 71 | elif type(subresult) == dict: 72 | host = self._make_host(subresult['host']) 73 | vars = subresult.get('vars',{}) 74 | if type(vars) == list: 75 | for subitem in vars: 76 | for (k,v) in subitem.items(): 77 | host.set_variable(k,v) 78 | elif type(vars) == dict: 79 | for (k,v) in subresult.get('vars',{}).items(): 80 | host.set_variable(k,v) 81 | else: 82 | raise errors.AnsibleError("unexpected type for variable") 83 | group.add_host(host) 84 | grouped_hosts.append(host) 85 | 86 | vars = item.get('vars',{}) 87 | if type(vars) == dict: 88 | for (k,v) in item.get('vars',{}).items(): 89 | group.set_variable(k,v) 90 | elif type(vars) == list: 91 | for subitem in vars: 92 | if type(subitem) != dict: 93 | raise errors.AnsibleError("expected a dictionary") 94 | for (k,v) in subitem.items(): 95 | group.set_variable(k,v) 96 | 97 | self.groups[group.name] = group 98 | all.add_child_group(group) 99 | 100 | # add host definitions 101 | for item in yaml: 102 | if type(item) in [ str, unicode ]: 103 | host = self._make_host(item) 104 | if host not in grouped_hosts: 105 | ungrouped.add_host(host) 106 | 107 | elif type(item) == dict and 'host' in item: 108 | host = self._make_host(item['host']) 109 | vars = item.get('vars', {}) 110 | if type(vars)==list: 111 | varlist, vars = vars, {} 112 | for subitem in varlist: 113 | vars.update(subitem) 114 | for (k,v) in vars.items(): 115 | host.set_variable(k,v) 116 | if host not in grouped_hosts: 117 | ungrouped.add_host(host) 118 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/playbook/play.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | from ansible import utils 21 | from ansible import errors 22 | from ansible.playbook.task import Task 23 | import shlex 24 | import os 25 | 26 | class Play(object): 27 | 28 | __slots__ = [ 29 | 'hosts', 'name', 'vars', 'vars_prompt', 'vars_files', 30 | 'handlers', 'remote_user', 'remote_port', 31 | 'sudo', 'sudo_user', 'transport', 'playbook', 32 | '_ds', '_handlers', '_tasks' 33 | ] 34 | 35 | # ************************************************* 36 | 37 | def __init__(self, playbook, ds): 38 | ''' constructor loads from a play datastructure ''' 39 | 40 | # TODO: more error handling 41 | 42 | 43 | hosts = ds.get('hosts') 44 | if hosts is None: 45 | raise errors.AnsibleError('hosts declaration is required') 46 | elif isinstance(hosts, list): 47 | hosts = ';'.join(hosts) 48 | hosts = utils.template(hosts, playbook.extra_vars, {}) 49 | 50 | self._ds = ds 51 | self.playbook = playbook 52 | self.hosts = hosts 53 | self.name = ds.get('name', self.hosts) 54 | self.vars = ds.get('vars', {}) 55 | self.vars_files = ds.get('vars_files', []) 56 | self.vars_prompt = ds.get('vars_prompt', {}) 57 | self.vars = self._get_vars(self.playbook.basedir) 58 | self._tasks = ds.get('tasks', []) 59 | self._handlers = ds.get('handlers', []) 60 | self.remote_user = ds.get('user', self.playbook.remote_user) 61 | self.remote_port = ds.get('port', self.playbook.remote_port) 62 | self.sudo = ds.get('sudo', self.playbook.sudo) 63 | self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user) 64 | self.transport = ds.get('connection', self.playbook.transport) 65 | self._tasks = self._load_tasks(self._ds, 'tasks') 66 | self._handlers = self._load_tasks(self._ds, 'handlers') 67 | 68 | if self.sudo_user != 'root': 69 | self.sudo = True 70 | 71 | # ************************************************* 72 | 73 | def _load_tasks(self, ds, keyname): 74 | ''' handle task and handler include statements ''' 75 | 76 | tasks = ds.get(keyname, []) 77 | results = [] 78 | for x in tasks: 79 | task_vars = self.vars.copy() 80 | if 'include' in x: 81 | tokens = shlex.split(x['include']) 82 | 83 | for t in tokens[1:]: 84 | (k,v) = t.split("=", 1) 85 | task_vars[k]=v 86 | include_file = tokens[0] 87 | data = utils.parse_yaml_from_file(utils.path_dwim(self.playbook.basedir, include_file)) 88 | elif type(x) == dict: 89 | data = [x] 90 | else: 91 | raise Exception("unexpected task type") 92 | for y in data: 93 | items = y.get('with_items',None) 94 | if items is None: 95 | items = [ '' ] 96 | elif isinstance(items, basestring): 97 | items = utils.varLookup(items, task_vars) 98 | for item in items: 99 | mv = task_vars.copy() 100 | mv['item'] = item 101 | results.append(Task(self,y,module_vars=mv)) 102 | return results 103 | 104 | # ************************************************* 105 | 106 | def tasks(self): 107 | ''' return task objects for this play ''' 108 | return self._tasks 109 | 110 | def handlers(self): 111 | ''' return handler objects for this play ''' 112 | return self._handlers 113 | 114 | # ************************************************* 115 | 116 | def _get_vars(self, dirname): 117 | ''' load the vars section from a play, accounting for all sorts of variable features 118 | including loading from yaml files, prompting, and conditional includes of the first 119 | file found in a list. ''' 120 | 121 | if self.vars is None: 122 | self.vars = {} 123 | 124 | if type(self.vars) not in [dict, list]: 125 | raise errors.AnsibleError("'vars' section must contain only key/value pairs") 126 | 127 | vars = self.playbook.global_vars 128 | 129 | # translate a list of vars into a dict 130 | if type(self.vars) == list: 131 | for item in self.vars: 132 | k, v = item.items()[0] 133 | vars[k] = v 134 | else: 135 | vars.update(self.vars) 136 | 137 | if type(self.vars_prompt) != dict: 138 | raise errors.AnsibleError("'vars_prompt' section must contain only key/value pairs") 139 | for vname in self.vars_prompt: 140 | vars[vname] = self.playbook.callbacks.on_vars_prompt(vname) 141 | 142 | results = self.playbook.extra_vars.copy() 143 | results.update(vars) 144 | return results 145 | 146 | # ************************************************* 147 | 148 | def update_vars_files(self, hosts): 149 | ''' calculate vars_files, which requires that setup runs first so ansible facts can be mixed in ''' 150 | for h in hosts: 151 | self._update_vars_files_for_host(h) 152 | 153 | # ************************************************* 154 | 155 | def should_run(self, tags): 156 | ''' does the play match any of the tags? ''' 157 | 158 | if len(self._tasks) == 0: 159 | return False 160 | 161 | for task in self._tasks: 162 | for task_tag in task.tags: 163 | if task_tag in tags: 164 | return True 165 | return False 166 | 167 | # ************************************************* 168 | 169 | def _update_vars_files_for_host(self, host): 170 | 171 | if not host in self.playbook.SETUP_CACHE: 172 | # no need to process failed hosts or hosts not in this play 173 | return 174 | 175 | for filename in self.vars_files: 176 | 177 | if type(filename) == list: 178 | 179 | # loop over all filenames, loading the first one, and failing if # none found 180 | found = False 181 | sequence = [] 182 | for real_filename in filename: 183 | filename2 = utils.template(real_filename, self.playbook.SETUP_CACHE[host]) 184 | filename2 = utils.template(filename2, self.vars) 185 | filename2 = utils.path_dwim(self.playbook.basedir, filename2) 186 | sequence.append(filename2) 187 | if os.path.exists(filename2): 188 | found = True 189 | data = utils.parse_yaml_from_file(filename2) 190 | self.playbook.SETUP_CACHE[host].update(data) 191 | self.playbook.callbacks.on_import_for_host(host, filename2) 192 | break 193 | else: 194 | self.playbook.callbacks.on_not_import_for_host(host, filename2) 195 | if not found: 196 | raise errors.AnsibleError( 197 | "%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence) 198 | ) 199 | 200 | else: 201 | 202 | filename2 = utils.template(filename, self.playbook.SETUP_CACHE[host]) 203 | filename2 = utils.template(filename2, self.vars) 204 | fpath = utils.path_dwim(self.playbook.basedir, filename2) 205 | new_vars = utils.parse_yaml_from_file(fpath) 206 | if new_vars: 207 | self.playbook.SETUP_CACHE[host].update(new_vars) 208 | #else: could warn if vars file contains no vars. 209 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/playbook/task.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | 18 | ############################################# 19 | 20 | from ansible import errors 21 | from ansible import utils 22 | 23 | class Task(object): 24 | 25 | __slots__ = [ 26 | 'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval', 27 | 'notify', 'module_name', 'module_args', 'module_vars', 28 | 'play', 'notified_by', 'tags' 29 | ] 30 | 31 | def __init__(self, play, ds, module_vars=None): 32 | ''' constructor loads from a task or handler datastructure ''' 33 | 34 | # TODO: more error handling 35 | # include task specific vars 36 | 37 | self.module_vars = module_vars 38 | 39 | self.play = play 40 | self.name = ds.get('name', None) 41 | self.action = ds.get('action', '') 42 | self.tags = [ 'all' ] 43 | 44 | self.notified_by = [] 45 | 46 | if self.name is None: 47 | self.name = self.action 48 | 49 | self.only_if = ds.get('only_if', 'True') 50 | self.async_seconds = int(ds.get('async', 0)) # not async by default 51 | self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds 52 | self.notify = ds.get('notify', []) 53 | if isinstance(self.notify, basestring): 54 | self.notify = [ self.notify ] 55 | 56 | tokens = self.action.split(None, 1) 57 | if len(tokens) < 1: 58 | raise errors.AnsibleError("invalid/missing action in task") 59 | 60 | self.module_name = tokens[0] 61 | self.module_args = '' 62 | if len(tokens) > 1: 63 | self.module_args = tokens[1] 64 | 65 | import_tags = [] 66 | if 'tags' in self.module_vars: 67 | import_tags = self.module_vars['tags'].split(",") 68 | 69 | self.name = utils.template(self.name, self.module_vars) 70 | self.action = utils.template(self.name, self.module_vars) 71 | 72 | 73 | if 'first_available_file' in ds: 74 | self.module_vars['first_available_file'] = ds.get('first_available_file') 75 | 76 | # tags allow certain parts of a playbook to be run without 77 | # running the whole playbook 78 | apply_tags = ds.get('tags', None) 79 | if apply_tags is not None: 80 | if type(apply_tags) in [ str, unicode ]: 81 | self.tags.append(apply_tags) 82 | elif type(apply_tags) == list: 83 | self.tags.extend(apply_tags) 84 | self.tags.extend(import_tags) 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/runner/connection/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | ################################################ 20 | 21 | import warnings 22 | import traceback 23 | import os 24 | import time 25 | import re 26 | import shutil 27 | import subprocess 28 | import pipes 29 | import socket 30 | import random 31 | 32 | import local 33 | import paramiko_ssh 34 | import ssh 35 | 36 | class Connection(object): 37 | ''' Handles abstract connections to remote hosts ''' 38 | 39 | def __init__(self, runner, transport,sudo_user): 40 | self.runner = runner 41 | self.transport = transport 42 | self.sudo_user = sudo_user 43 | def connect(self, host, port=None): 44 | conn = None 45 | if self.transport == 'local': 46 | conn = local.LocalConnection(self.runner, host) 47 | elif self.transport == 'paramiko': 48 | conn = paramiko_ssh.ParamikoConnection(self.runner, host, port) 49 | elif self.transport == 'ssh': 50 | conn = ssh.SSHConnection(self.runner, host, port) 51 | if conn is None: 52 | raise Exception("unsupported connection type") 53 | return conn.connect() 54 | 55 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/runner/connection/local.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | ################################################ 20 | 21 | import warnings 22 | import traceback 23 | import os 24 | import time 25 | import re 26 | import shutil 27 | import subprocess 28 | import pipes 29 | import socket 30 | import random 31 | 32 | from ansible import errors 33 | 34 | class LocalConnection(object): 35 | ''' Local based connections ''' 36 | 37 | def __init__(self, runner, host): 38 | self.runner = runner 39 | self.host = host 40 | 41 | def connect(self, port=None): 42 | ''' connect to the local host; nothing to do here ''' 43 | 44 | return self 45 | 46 | def exec_command(self, cmd, tmp_path,sudo_user,sudoable=False): 47 | ''' run a command on the local host ''' 48 | if self.runner.sudo and sudoable: 49 | cmd = "sudo -s %s" % cmd 50 | if self.runner.sudo_pass: 51 | # NOTE: if someone wants to add sudo w/ password to the local connection type, they are welcome 52 | # to do so. The primary usage of the local connection is for crontab and kickstart usage however 53 | # so this doesn't seem to be a huge priority 54 | raise errors.AnsibleError("sudo with password is presently only supported on the paramiko (SSH) connection type") 55 | 56 | p = subprocess.Popen(cmd, shell=True, stdin=None, 57 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 58 | stdout, stderr = p.communicate() 59 | return ("", stdout, stderr) 60 | 61 | def put_file(self, in_path, out_path): 62 | ''' transfer a file from local to local ''' 63 | if not os.path.exists(in_path): 64 | raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) 65 | try: 66 | shutil.copyfile(in_path, out_path) 67 | except shutil.Error: 68 | traceback.print_exc() 69 | raise errors.AnsibleError("failed to copy: %s and %s are the same" % (in_path, out_path)) 70 | except IOError: 71 | traceback.print_exc() 72 | raise errors.AnsibleError("failed to transfer file to %s" % out_path) 73 | 74 | def fetch_file(self, in_path, out_path): 75 | ''' fetch a file from local to local -- for copatibility ''' 76 | self.put_file(in_path, out_path) 77 | 78 | def close(self): 79 | ''' terminate the connection; nothing to do here ''' 80 | 81 | pass 82 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/runner/connection/paramiko_ssh.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | ################################################ 20 | 21 | import warnings 22 | import traceback 23 | import os 24 | import time 25 | import re 26 | import shutil 27 | import subprocess 28 | import pipes 29 | import socket 30 | import random 31 | 32 | from ansible import errors 33 | # prevent paramiko warning noise 34 | # see http://stackoverflow.com/questions/3920502/ 35 | HAVE_PARAMIKO=False 36 | with warnings.catch_warnings(): 37 | warnings.simplefilter("ignore") 38 | try: 39 | import paramiko 40 | HAVE_PARAMIKO=True 41 | except ImportError: 42 | pass 43 | 44 | class ParamikoConnection(object): 45 | ''' SSH based connections with Paramiko ''' 46 | 47 | def __init__(self, runner, host, port=None): 48 | self.ssh = None 49 | self.runner = runner 50 | self.host = host 51 | self.port = port 52 | if port is None: 53 | self.port = self.runner.remote_port 54 | 55 | def _get_conn(self): 56 | 57 | if not HAVE_PARAMIKO: 58 | raise errors.AnsibleError("paramiko is not installed") 59 | 60 | user = self.runner.remote_user 61 | 62 | ssh = paramiko.SSHClient() 63 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 64 | 65 | try: 66 | ssh.connect( 67 | self.host, 68 | username=user, 69 | allow_agent=True, 70 | look_for_keys=True, 71 | key_filename=self.runner.private_key_file, 72 | password=self.runner.remote_pass, 73 | timeout=self.runner.timeout, 74 | port=self.port 75 | ) 76 | except Exception, e: 77 | msg = str(e) 78 | if "PID check failed" in msg: 79 | raise errors.AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible") 80 | elif "Private key file is encrypted" in msg: 81 | msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u .' % ( 82 | user, self.host, self.port, msg) 83 | raise errors.AnsibleConnectionFailed(msg) 84 | else: 85 | raise errors.AnsibleConnectionFailed(msg) 86 | 87 | return ssh 88 | 89 | def connect(self): 90 | ''' connect to the remote host ''' 91 | 92 | self.ssh = self._get_conn() 93 | return self 94 | 95 | def exec_command(self, cmd, tmp_path,sudo_user,sudoable=False): 96 | 97 | ''' run a command on the remote host ''' 98 | bufsize = 4096 99 | chan = self.ssh.get_transport().open_session() 100 | chan.get_pty() 101 | 102 | if not self.runner.sudo or not sudoable: 103 | quoted_command = '"$SHELL" -c ' + pipes.quote(cmd) 104 | chan.exec_command(quoted_command) 105 | else: 106 | # Rather than detect if sudo wants a password this time, -k makes 107 | # sudo always ask for a password if one is required. The "--" 108 | # tells sudo that this is the end of sudo options and the command 109 | # follows. Passing a quoted compound command to sudo (or sudo -s) 110 | # directly doesn't work, so we shellquote it with pipes.quote() 111 | # and pass the quoted string to the user's shell. We loop reading 112 | # output until we see the randomly-generated sudo prompt set with 113 | # the -p option. 114 | randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) 115 | prompt = '[sudo via ansible, key=%s] password: ' % randbits 116 | sudocmd = 'sudo -k && sudo -p "%s" -u %s -- "$SHELL" -c %s' % ( 117 | prompt, sudo_user, pipes.quote(cmd)) 118 | sudo_output = '' 119 | try: 120 | chan.exec_command(sudocmd) 121 | if self.runner.sudo_pass: 122 | while not sudo_output.endswith(prompt): 123 | chunk = chan.recv(bufsize) 124 | if not chunk: 125 | raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') 126 | sudo_output += chunk 127 | chan.sendall(self.runner.sudo_pass + '\n') 128 | except socket.timeout: 129 | raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output) 130 | 131 | stdin = chan.makefile('wb', bufsize) 132 | stdout = chan.makefile('rb', bufsize) 133 | stderr = '' # stderr goes to stdout when using a pty, so this will never output anything. 134 | return stdin, stdout, stderr 135 | 136 | def put_file(self, in_path, out_path): 137 | ''' transfer a file from local to remote ''' 138 | if not os.path.exists(in_path): 139 | raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) 140 | sftp = self.ssh.open_sftp() 141 | try: 142 | sftp.put(in_path, out_path) 143 | except IOError: 144 | traceback.print_exc() 145 | raise errors.AnsibleError("failed to transfer file to %s" % out_path) 146 | sftp.close() 147 | 148 | def fetch_file(self, in_path, out_path): 149 | sftp = self.ssh.open_sftp() 150 | try: 151 | sftp.get(in_path, out_path) 152 | except IOError: 153 | traceback.print_exc() 154 | raise errors.AnsibleError("failed to transfer file from %s" % in_path) 155 | sftp.close() 156 | 157 | def close(self): 158 | ''' terminate the connection ''' 159 | 160 | self.ssh.close() 161 | 162 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/runner/connection/ssh.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | ################################################ 20 | 21 | import os 22 | import time 23 | import subprocess 24 | import shlex 25 | import pipes 26 | import random 27 | import select 28 | import fcntl 29 | 30 | from ansible import errors 31 | 32 | class SSHConnection(object): 33 | ''' ssh based connections ''' 34 | 35 | def __init__(self, runner, host, port): 36 | self.runner = runner 37 | self.host = host 38 | self.port = port 39 | 40 | def connect(self): 41 | ''' connect to the remote host ''' 42 | self.common_args = [] 43 | extra_args = os.getenv("ANSIBLE_SSH_ARGS", None) 44 | if extra_args is not None: 45 | self.common_args += shlex.split(extra_args) 46 | else: 47 | self.common_args += ["-o", "ControlMaster=auto", 48 | "-o", "ControlPersist=60s", 49 | "-o", "ControlPath=/tmp/ansible-ssh-%h-%p-%r"] 50 | self.common_args += ["-o", "StrictHostKeyChecking=no"] 51 | if self.port is not None: 52 | self.common_args += ["-o", "Port=%d" % (self.port)] 53 | if self.runner.private_key_file is not None: 54 | self.common_args += ["-o", "IdentityFile="+self.runner.private_key_file] 55 | self.common_args += ["-o", "User="+self.runner.remote_user] 56 | 57 | return self 58 | 59 | def exec_command(self, cmd, tmp_path,sudo_user,sudoable=False): 60 | ''' run a command on the remote host ''' 61 | 62 | ssh_cmd = ["ssh", "-tt", "-q"] + self.common_args + [self.host] 63 | if self.runner.sudo and sudoable: 64 | # Rather than detect if sudo wants a password this time, -k makes 65 | # sudo always ask for a password if one is required. The "--" 66 | # tells sudo that this is the end of sudo options and the command 67 | # follows. Passing a quoted compound command to sudo (or sudo -s) 68 | # directly doesn't work, so we shellquote it with pipes.quote() 69 | # and pass the quoted string to the user's shell. We loop reading 70 | # output until we see the randomly-generated sudo prompt set with 71 | # the -p option. 72 | randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) 73 | prompt = '[sudo via ansible, key=%s] password: ' % randbits 74 | sudocmd = 'sudo -k && sudo -p "%s" -u %s -- "$SHELL" -c %s' % ( 75 | prompt, sudo_user, pipes.quote(cmd)) 76 | sudo_output = '' 77 | ssh_cmd.append(sudocmd) 78 | p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, 79 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 80 | if self.runner.sudo_pass: 81 | fcntl.fcntl(p.stdout, fcntl.F_SETFL, 82 | fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK) 83 | while not sudo_output.endswith(prompt): 84 | rfd, wfd, efd = select.select([p.stdout], [], 85 | [p.stdout], self.runner.timeout) 86 | if p.stdout in rfd: 87 | chunk = p.stdout.read() 88 | if not chunk: 89 | raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt') 90 | sudo_output += chunk 91 | else: 92 | stdout = p.communicate() 93 | raise errors.AnsibleError('ssh connection error waiting for sudo password prompt') 94 | p.stdin.write(self.runner.sudo_pass + '\n') 95 | fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK) 96 | else: 97 | ssh_cmd.append(cmd) 98 | p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, 99 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 100 | 101 | # We can't use p.communicate here because the ControlMaster may have stdout open as well 102 | p.stdin.close() 103 | stdout = '' 104 | while p.poll() is None: 105 | rfd, wfd, efd = select.select([p.stdout], [], [p.stdout], 1) 106 | if p.stdout in rfd: 107 | stdout += os.read(p.stdout.fileno(), 1024) 108 | # older versions of ssh generate this error which we ignore 109 | stdout=stdout.replace("tcgetattr: Invalid argument\n", "") 110 | 111 | if p.returncode != 0 and stdout.find('Bad configuration option: ControlPersist') != -1: 112 | raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" before running again') 113 | 114 | return ('', stdout, '') 115 | 116 | def put_file(self, in_path, out_path): 117 | ''' transfer a file from local to remote ''' 118 | if not os.path.exists(in_path): 119 | raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) 120 | sftp_cmd = ["sftp"] + self.common_args + [self.host] 121 | p = subprocess.Popen(sftp_cmd, stdin=subprocess.PIPE, 122 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 123 | stdout, stderr = p.communicate("put %s %s\n" % (in_path, out_path)) 124 | if p.returncode != 0: 125 | raise errors.AnsibleError("failed to transfer file to %s:\n%s\n%s" % (out_path, stdout, stderr)) 126 | 127 | def fetch_file(self, in_path, out_path): 128 | ''' fetch a file from remote to local ''' 129 | sftp_cmd = ["sftp"] + self.common_args + [self.host] 130 | p = subprocess.Popen(sftp_cmd, stdin=subprocess.PIPE, 131 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 132 | stdout, stderr = p.communicate("get %s %s\n" % (in_path, out_path)) 133 | if p.returncode != 0: 134 | raise errors.AnsibleError("failed to transfer file from %s:\n%s\n%s" % (in_path, stdout, stderr)) 135 | 136 | def close(self): 137 | ''' terminate the connection ''' 138 | pass 139 | -------------------------------------------------------------------------------- /ansible-dist/lib/ansible/runner/poller.py: -------------------------------------------------------------------------------- 1 | # (c) 2012, Michael DeHaan 2 | # 3 | # This file is part of Ansible 4 | # 5 | # Ansible is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Ansible is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Ansible. If not, see . 17 | # 18 | 19 | import time 20 | 21 | from ansible import errors 22 | 23 | class AsyncPoller(object): 24 | """ Manage asynchronous jobs. """ 25 | 26 | def __init__(self, results, runner): 27 | self.runner = runner 28 | 29 | self.results = { 'contacted': {}, 'dark': {}} 30 | self.hosts_to_poll = [] 31 | self.completed = False 32 | 33 | # Get job id and which hosts to poll again in the future 34 | jid = None 35 | for (host, res) in results['contacted'].iteritems(): 36 | if res.get('started', False): 37 | self.hosts_to_poll.append(host) 38 | jid = res.get('ansible_job_id', None) 39 | else: 40 | self.results['contacted'][host] = res 41 | for (host, res) in results['dark'].iteritems(): 42 | self.results['dark'][host] = res 43 | 44 | if jid is None: 45 | raise errors.AnsibleError("unexpected error: unable to determine jid") 46 | if len(self.hosts_to_poll)==0: 47 | raise errors.AnsibleErrot("unexpected error: no hosts to poll") 48 | self.jid = jid 49 | 50 | def poll(self): 51 | """ Poll the job status. 52 | 53 | Returns the changes in this iteration.""" 54 | self.runner.module_name = 'async_status' 55 | self.runner.module_args = "jid=%s" % self.jid 56 | self.runner.pattern = "*" 57 | self.runner.background = 0 58 | 59 | self.runner.inventory.restrict_to(self.hosts_to_poll) 60 | results = self.runner.run() 61 | self.runner.inventory.lift_restriction() 62 | 63 | hosts = [] 64 | poll_results = { 'contacted': {}, 'dark': {}, 'polled': {}} 65 | for (host, res) in results['contacted'].iteritems(): 66 | if res.get('started',False): 67 | hosts.append(host) 68 | poll_results['polled'][host] = res 69 | else: 70 | self.results['contacted'][host] = res 71 | poll_results['contacted'][host] = res 72 | if 'failed' in res: 73 | self.runner.callbacks.on_async_failed(host, res, self.jid) 74 | else: 75 | self.runner.callbacks.on_async_ok(host, res, self.jid) 76 | for (host, res) in results['dark'].iteritems(): 77 | self.results['dark'][host] = res 78 | poll_results['dark'][host] = res 79 | self.runner.callbacks.on_async_failed(host, res, self.jid) 80 | 81 | self.hosts_to_poll = hosts 82 | if len(hosts)==0: 83 | self.completed = True 84 | 85 | return poll_results 86 | 87 | def wait(self, seconds, poll_interval): 88 | """ Wait a certain time for job completion, check status every poll_interval. """ 89 | clock = seconds - poll_interval 90 | while (clock >= 0 and not self.completed): 91 | time.sleep(poll_interval) 92 | 93 | poll_results = self.poll() 94 | 95 | for (host, res) in poll_results['polled'].iteritems(): 96 | if res.get('started'): 97 | self.runner.callbacks.on_async_poll(host, res, self.jid, clock) 98 | 99 | clock = clock - poll_interval 100 | 101 | return self.results 102 | -------------------------------------------------------------------------------- /ansible-dist/library/apt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # (c) 2012, Flowroute LLC 3 | # Written by Matthew Williams 4 | # Based on yum module written by Seth Vidal 5 | # 6 | # This module is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this software. If not, see . 18 | # 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | import os 25 | import sys 26 | import shlex 27 | import subprocess 28 | import syslog 29 | import traceback 30 | 31 | # added to stave off future warnings about apt api 32 | import warnings; 33 | warnings.filterwarnings('ignore', "apt API not stable yet", FutureWarning) 34 | 35 | APT_PATH = "/usr/bin/apt-get" 36 | APT = "DEBIAN_PRIORITY=critical %s" % APT_PATH 37 | 38 | def exit_json(rc=0, **kwargs): 39 | print json.dumps(kwargs) 40 | sys.exit(rc) 41 | 42 | def fail_json(**kwargs): 43 | kwargs['failed'] = True 44 | exit_json(rc=1, **kwargs) 45 | 46 | try: 47 | import apt, apt_pkg 48 | except ImportError: 49 | fail_json(msg="could not import apt, please install the python-apt package on this host") 50 | 51 | def run_apt(command): 52 | try: 53 | cmd = subprocess.Popen(command, shell=True, 54 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 55 | out, err = cmd.communicate() 56 | except (OSError, IOError), e: 57 | rc = 1 58 | err = str(e) 59 | out = '' 60 | except: 61 | rc = 1 62 | err = traceback.format_exc() 63 | out = '' 64 | else: 65 | rc = cmd.returncode 66 | return rc, out, err 67 | 68 | def package_split(pkgspec): 69 | parts = pkgspec.split('=') 70 | if len(parts) > 1: 71 | return parts[0], parts[1] 72 | else: 73 | return parts[0], None 74 | 75 | def package_status(pkgname, version, cache): 76 | try: 77 | pkg = cache[pkgname] 78 | except KeyError: 79 | fail_json(msg="No package matching '%s' is available" % pkgname) 80 | if version: 81 | try : 82 | return pkg.is_installed and pkg.installed.version == version, False 83 | except AttributeError: 84 | #assume older version of python-apt is installed 85 | return pkg.isInstalled and pkg.installedVersion == version, False 86 | else: 87 | try : 88 | return pkg.is_installed, pkg.is_upgradable 89 | except AttributeError: 90 | #assume older version of python-apt is installed 91 | return pkg.isInstalled, pkg.isUpgradable 92 | 93 | def install(pkgspec, cache, upgrade=False, default_release=None, install_recommends=True, force=False): 94 | name, version = package_split(pkgspec) 95 | installed, upgradable = package_status(name, version, cache) 96 | if not installed or (upgrade and upgradable): 97 | if force: 98 | force_yes = '--force-yes' 99 | else: 100 | force_yes = '' 101 | 102 | cmd = "%s --option Dpkg::Options::=--force-confold -q -y %s install '%s'" % (APT, force_yes, pkgspec) 103 | if default_release: 104 | cmd += " -t '%s'" % (default_release,) 105 | if not install_recommends: 106 | cmd += " --no-install-recommends" 107 | rc, out, err = run_apt(cmd) 108 | if rc: 109 | fail_json(msg="'apt-get install %s' failed: %s" % (pkgspec, err)) 110 | return True 111 | else: 112 | return False 113 | 114 | def remove(pkgspec, cache, purge=False): 115 | name, version = package_split(pkgspec) 116 | installed, upgradable = package_status(name, version, cache) 117 | if not installed: 118 | return False 119 | else: 120 | purge = '--purge' if purge else '' 121 | cmd = "%s -q -y %s remove '%s'" % (APT, purge, name) 122 | rc, out, err = run_apt(cmd) 123 | if rc: 124 | fail_json(msg="'apt-get remove %s' failed: %s" % (name, err)) 125 | return True 126 | 127 | 128 | # =========================================== 129 | 130 | if not os.path.exists(APT_PATH): 131 | fail_json(msg="Cannot find apt-get") 132 | 133 | argfile = sys.argv[1] 134 | args = open(argfile, 'r').read() 135 | items = shlex.split(args) 136 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 137 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 138 | 139 | if not len(items): 140 | fail_json(msg='the module requires arguments -a') 141 | sys.exit(1) 142 | 143 | params = {} 144 | for x in items: 145 | (k, v) = x.split("=", 1) 146 | params[k] = v 147 | 148 | state = params.get('state', 'installed') 149 | package = params.get('pkg', params.get('package', params.get('name', None))) 150 | update_cache = params.get('update-cache', 'no') 151 | purge = params.get('purge', 'no') 152 | default_release = params.get('default-release', None) 153 | install_recommends = params.get('install-recommends', 'yes') 154 | force = params.get('force', 'no') 155 | 156 | if state not in ['installed', 'latest', 'removed']: 157 | fail_json(msg='invalid state') 158 | 159 | if update_cache not in ['yes', 'no']: 160 | fail_json(msg='invalid value for update_cache (requires yes or no -- default is no') 161 | 162 | if purge not in ['yes', 'no']: 163 | fail_json(msg='invalid value for purge (requires yes or no -- default is no)') 164 | 165 | if force not in ['yes', 'no']: 166 | fail_json(msg='invalid option for force (requires yes or no -- default is no)') 167 | 168 | if package is None and update_cache != 'yes': 169 | fail_json(msg='pkg=name and/or update-cache=yes is required') 170 | 171 | if install_recommends not in ['yes', 'no']: 172 | fail_json(msg='invalid value for install-recommends (requires yes or no -- default is yes)') 173 | install_recommends = (install_recommends == 'yes') 174 | 175 | cache = apt.Cache() 176 | if default_release: 177 | apt_pkg.config['APT::Default-Release'] = default_release 178 | # reopen cache w/ modified config 179 | cache.open(progress=None) 180 | 181 | if update_cache == 'yes': 182 | cache.update() 183 | cache.open(progress=None) 184 | if package == None: 185 | exit_json(changed=False) 186 | 187 | if force == 'yes': 188 | force_yes = True 189 | else: 190 | force_yes = False 191 | 192 | if package.count('=') > 1: 193 | fail_json(msg='invalid package spec') 194 | 195 | if state == 'latest': 196 | if '=' in package: 197 | fail_json(msg='version number inconsistent with state=latest') 198 | changed = install(package, cache, upgrade=True, 199 | default_release=default_release, 200 | install_recommends=install_recommends, 201 | force=force_yes) 202 | elif state == 'installed': 203 | changed = install(package, cache, default_release=default_release, 204 | install_recommends=install_recommends,force=force_yes) 205 | elif state == 'removed': 206 | changed = remove(package, cache, purge == 'yes') 207 | 208 | exit_json(changed=changed) 209 | 210 | 211 | -------------------------------------------------------------------------------- /ansible-dist/library/assemble: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Stephen Fromm 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | import os 25 | import os.path 26 | import sys 27 | import shlex 28 | import shutil 29 | import syslog 30 | import tempfile 31 | 32 | try: 33 | from hashlib import md5 as _md5 34 | except ImportError: 35 | from md5 import md5 as _md5 36 | 37 | 38 | # =========================================== 39 | # Support methods 40 | 41 | def exit_json(rc=0, **kwargs): 42 | print json.dumps(kwargs) 43 | sys.exit(rc) 44 | 45 | def fail_json(**kwargs): 46 | kwargs['failed'] = True 47 | exit_json(rc=1, **kwargs) 48 | 49 | def assemble_from_fragments(path): 50 | ''' assemble a file from a directory of fragments ''' 51 | assembled = [] 52 | for f in sorted(os.listdir(path)): 53 | fragment = "%s/%s" % (path, f) 54 | if os.path.isfile(fragment): 55 | assembled.append(file(fragment).read()) 56 | return "".join(assembled) 57 | 58 | def write_temp_file(data): 59 | fd, path = tempfile.mkstemp() 60 | os.write(fd, data) 61 | os.close(fd) 62 | return path 63 | 64 | def md5(filename): 65 | ''' Return MD5 hex digest of local file, or None if file is not present. ''' 66 | if not os.path.exists(filename): 67 | return None 68 | digest = _md5() 69 | blocksize = 64 * 1024 70 | infile = open(filename, 'rb') 71 | block = infile.read(blocksize) 72 | while block: 73 | digest.update(block) 74 | block = infile.read(blocksize) 75 | infile.close() 76 | return digest.hexdigest() 77 | 78 | # =========================================== 79 | 80 | if len(sys.argv) == 1: 81 | fail_json(msg="the assemble module requires arguments (-a)") 82 | 83 | argfile = sys.argv[1] 84 | if not os.path.exists(argfile): 85 | fail_json(msg="Argument file not found") 86 | 87 | args = open(argfile, 'r').read() 88 | items = shlex.split(args) 89 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 90 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 91 | 92 | if not len(items): 93 | fail_json(msg="the assemble module requires arguments (-a)") 94 | 95 | params = {} 96 | for x in items: 97 | (k, v) = x.split("=") 98 | params[k] = v 99 | 100 | changed = False 101 | pathmd5 = None 102 | destmd5 = None 103 | src = params.get('src', None) 104 | dest = params.get('dest', None) 105 | 106 | if src: 107 | src = os.path.expanduser(src) 108 | if dest: 109 | dest = os.path.expanduser(dest) 110 | 111 | if not os.path.exists(src): 112 | fail_json(msg="Source (%s) does not exist" % src) 113 | 114 | if not os.path.isdir(src): 115 | fail_json(msg="Source (%s) is not a directory" % src) 116 | 117 | path = write_temp_file(assemble_from_fragments(src)) 118 | pathmd5 = md5(path) 119 | 120 | if os.path.exists(dest): 121 | destmd5 = md5(dest) 122 | 123 | if pathmd5 != destmd5: 124 | shutil.copy(path, dest) 125 | changed = True 126 | 127 | exit_json(md5sum=pathmd5, changed=changed) 128 | -------------------------------------------------------------------------------- /ansible-dist/library/async_status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan , and others 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | try: 22 | import json 23 | except ImportError: 24 | import simplejson as json 25 | import shlex 26 | import os 27 | import subprocess 28 | import sys 29 | import datetime 30 | import traceback 31 | import syslog 32 | 33 | # =========================================== 34 | 35 | # FIXME: better error handling 36 | 37 | argsfile = sys.argv[1] 38 | args = open(argsfile, 'r').read() 39 | items = shlex.split(args) 40 | 41 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 42 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 43 | 44 | params = {} 45 | for x in items: 46 | (k, v) = x.split("=") 47 | params[k] = v 48 | 49 | mode = params.get('mode', 'status') 50 | jid = params.get('jid', None) 51 | 52 | # =========================================== 53 | 54 | if jid is None: 55 | print json.dumps({ 56 | "failed" : True, 57 | "msg" : "jid=INTEGER is required" 58 | }) 59 | sys.exit(1) 60 | 61 | 62 | # setup logging directory 63 | logdir = os.path.expanduser("~/.ansible_async") 64 | log_path = os.path.join(logdir, jid) 65 | 66 | if not os.path.exists(log_path): 67 | print json.dumps({ 68 | "failed" : 1, 69 | "msg" : "could not find job", 70 | "ansible_job_id" : jid 71 | }) 72 | sys.exit(1) 73 | 74 | if mode == 'cleanup': 75 | os.unlink(log_path) 76 | print json.dumps({ 77 | "ansible_job_id" : jid, 78 | "erased" : log_path 79 | }) 80 | sys.exit(0) 81 | 82 | # NOT in cleanup mode, assume regular status mode 83 | # no remote kill mode currently exists, but probably should 84 | # consider log_path + ".pid" file and also unlink that above 85 | 86 | data = file(log_path).read() 87 | try: 88 | data = json.loads(data) 89 | except Exception, e: 90 | if data == '': 91 | # file not written yet? That means it is running 92 | print json.dumps({ 93 | "results_file" : log_path, 94 | "ansible_job_id" : jid, 95 | "started" : 1, 96 | }) 97 | else: 98 | print json.dumps({ 99 | "failed" : True, 100 | "ansible_job_id" : jid, 101 | "results_file" : log_path, 102 | "msg" : "Could not parse job output: %s" % data, 103 | }) 104 | sys.exit(0) 105 | 106 | if not data.has_key("started"): 107 | data['finished'] = 1 108 | data['ansible_job_id'] = jid 109 | 110 | print json.dumps(data) 111 | sys.exit(0) 112 | 113 | 114 | -------------------------------------------------------------------------------- /ansible-dist/library/async_wrapper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan , and others 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | try: 22 | import json 23 | except ImportError: 24 | import simplejson as json 25 | import shlex 26 | import os 27 | import subprocess 28 | import sys 29 | import datetime 30 | import traceback 31 | import signal 32 | import time 33 | import syslog 34 | 35 | def daemonize_self(): 36 | # daemonizing code: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012 37 | # logger.info("cobblerd started") 38 | try: 39 | pid = os.fork() 40 | if pid > 0: 41 | # exit first parent 42 | sys.exit(0) 43 | except OSError, e: 44 | print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) 45 | sys.exit(1) 46 | 47 | # decouple from parent environment 48 | os.chdir("/") 49 | os.setsid() 50 | os.umask(022) 51 | 52 | # do second fork 53 | try: 54 | pid = os.fork() 55 | if pid > 0: 56 | # print "Daemon PID %d" % pid 57 | sys.exit(0) 58 | except OSError, e: 59 | print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) 60 | sys.exit(1) 61 | 62 | dev_null = file('/dev/null','rw') 63 | os.dup2(dev_null.fileno(), sys.stdin.fileno()) 64 | os.dup2(dev_null.fileno(), sys.stdout.fileno()) 65 | os.dup2(dev_null.fileno(), sys.stderr.fileno()) 66 | 67 | if len(sys.argv) < 3: 68 | print json.dumps({ 69 | "failed" : True, 70 | "msg" : "usage: async_wrapper . Humans, do not call directly!" 71 | }) 72 | sys.exit(1) 73 | 74 | jid = sys.argv[1] 75 | time_limit = sys.argv[2] 76 | wrapped_module = sys.argv[3] 77 | argsfile = sys.argv[4] 78 | cmd = "%s %s" % (wrapped_module, argsfile) 79 | 80 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 81 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % " ".join(sys.argv[1:])) 82 | 83 | # setup logging directory 84 | logdir = os.path.expanduser("~/.ansible_async") 85 | log_path = os.path.join(logdir, jid) 86 | 87 | if not os.path.exists(logdir): 88 | try: 89 | os.makedirs(logdir) 90 | except: 91 | print json.dumps({ 92 | "failed" : 1, 93 | "msg" : "could not create: %s" % logdir 94 | }) 95 | 96 | def _run_command(wrapped_cmd, jid, log_path): 97 | 98 | logfile = open(log_path, "w") 99 | logfile.write(json.dumps({ "started" : 1, "ansible_job_id" : jid })) 100 | logfile.close() 101 | logfile = open(log_path, "w") 102 | result = {} 103 | 104 | outdata = '' 105 | try: 106 | cmd = shlex.split(wrapped_cmd) 107 | script = subprocess.Popen(cmd, shell=False, 108 | stdin=None, stdout=logfile, stderr=logfile) 109 | script.communicate() 110 | outdata = file(log_path).read() 111 | result = json.loads(outdata) 112 | 113 | except (OSError, IOError), e: 114 | result = { 115 | "failed": 1, 116 | "cmd" : wrapped_cmd, 117 | "msg": str(e), 118 | } 119 | result['ansible_job_id'] = jid 120 | logfile.write(json.dumps(result)) 121 | except: 122 | result = { 123 | "failed" : 1, 124 | "cmd" : wrapped_cmd, 125 | "data" : outdata, # temporary debug only 126 | "msg" : traceback.format_exc() 127 | } 128 | result['ansible_job_id'] = jid 129 | logfile.write(json.dumps(result)) 130 | logfile.close() 131 | 132 | # immediately exit this process, leaving an orphaned process 133 | # running which immediately forks a supervisory timing process 134 | 135 | #import logging 136 | #import logging.handlers 137 | 138 | #logger = logging.getLogger("ansible_async") 139 | #logger.setLevel(logging.WARNING) 140 | #logger.addHandler( logging.handlers.SysLogHandler("/dev/log") ) 141 | def debug(msg): 142 | #logger.warning(msg) 143 | pass 144 | 145 | try: 146 | pid = os.fork() 147 | if pid: 148 | # Notify the overlord that the async process started 149 | 150 | # we need to not return immmediately such that the launched command has an attempt 151 | # to initialize PRIOR to ansible trying to clean up the launch directory (and argsfile) 152 | # this probably could be done with some IPC later. Modules should always read 153 | # the argsfile at the very first start of their execution anyway 154 | time.sleep(1) 155 | debug("Return async_wrapper task started.") 156 | print json.dumps({ "started" : 1, "ansible_job_id" : jid, "results_file" : log_path }) 157 | sys.stdout.flush() 158 | sys.exit(0) 159 | else: 160 | # The actual wrapper process 161 | 162 | # Daemonize, so we keep on running 163 | daemonize_self() 164 | 165 | # we are now daemonized, create a supervisory process 166 | debug("Starting module and watcher") 167 | 168 | sub_pid = os.fork() 169 | if sub_pid: 170 | # the parent stops the process after the time limit 171 | remaining = int(time_limit) 172 | 173 | # set the child process group id to kill all children 174 | os.setpgid(sub_pid, sub_pid) 175 | 176 | debug("Start watching %s (%s)"%(sub_pid, remaining)) 177 | time.sleep(5) 178 | while os.waitpid(sub_pid, os.WNOHANG) == (0, 0): 179 | debug("%s still running (%s)"%(sub_pid, remaining)) 180 | time.sleep(5) 181 | remaining = remaining - 5 182 | if remaining == 0: 183 | debug("Now killing %s"%(sub_pid)) 184 | os.killpg(sub_pid, signal.SIGKILL) 185 | debug("Sent kill to group %s"%sub_pid) 186 | time.sleep(1) 187 | sys.exit(0) 188 | debug("Done in kid B.") 189 | os._exit(0) 190 | else: 191 | # the child process runs the actual module 192 | debug("Start module (%s)"%os.getpid()) 193 | _run_command(cmd, jid, log_path) 194 | debug("Module complete (%s)"%os.getpid()) 195 | sys.exit(0) 196 | 197 | except Exception, err: 198 | debug("error: %s"%(err)) 199 | raise err 200 | -------------------------------------------------------------------------------- /ansible-dist/library/authorized_key: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Ansible module to add authorized_keys for ssh logins. 3 | 4 | (c) 2012, Brad Olson 5 | 6 | Results: Makes sure the public key line is present or absent in the user's .ssh/authorized_keys. 7 | 8 | Arguments 9 | ========= 10 | user = username 11 | key = line to add to authorized_keys for user 12 | state = absent|present (default: present) 13 | 14 | Command Line Example 15 | ==================== 16 | 17 | ansible somehost -m authorized_key -a user=charlie key="ssh-dss AAAABUfOL+8BTwaRYr/rycsBF1D8e5pTxEsXHQs4iq+mZdyWqlW++L6pMiam1A8yweP+rKtgjK2httVS6GigVsuWWfOd7/sdWippefq74nppVUELHPKkaIOjJNN1zUHFoL/YMwAAAEBALnAsQN10TNGsRDe5arBsW8cTOjqLyYBcIqgPYTZW8zENErFxt7ij3fW3Jh/sCpnmy8rkS7FyK8ULX0PEy/2yDx8/5rXgMIICbRH/XaBy9Ud5bRBFVkEDu/r+rXP33wFPHjWjwvHAtfci1NRBAudQI/98DbcGQw5HmE89CjgZRo5ktkC5yu/8agEPocVjdHyZr7PaHfxZGUDGKtGRL2QzRYukCmWo1cZbMBHcI5FzImvTHS9/8B3SATjXMPgbfBuEeBwuBK5EjL+CtHY5bWs9kmYjmeo0KfUMH8hY4MAXDoKhQ7DhBPIrcjS5jPtoGxIREZjba67r6/P2XKXaCZH6Fc= charlie@somemail.org 2011-01-17" 18 | 19 | Playbook Example 20 | ================ 21 | 22 | --- 23 | # include like this: 24 | # - include: tasks/logins.yaml users=charlie,sue 25 | - name: create user charlie 26 | action: user name=charlie shell=/bin/bash createhome=yes groups=www-data 27 | only_if: "'charlie' in '$users'" 28 | - name: add public key for charlie 29 | action: authorized_key user=charlie key="ssh-dss AAAABUfOL+8BTwaRYr/rycsBF1D8e5pTxEsXHQs4iq+mZdyWqlW++L6pMiam1A8yweP+rKtgjK2httVS6GigVsuWWfOd7/sdWippefq74nppVUELHPKkaIOjJNN1zUHFoL/YMwAAAEBALnAsQN10TNGsRDe5arBsW8cTOjqLyYBcIqgPYTZW8zENErFxt7ij3fW3Jh/sCpnmy8rkS7FyK8ULX0PEy/2yDx8/5rXgMIICbRH/XaBy9Ud5bRBFVkEDu/r+rXP33wFPHjWjwvHAtfci1NRBAudQI/98DbcGQw5HmE89CjgZRo5ktkC5yu/8agEPocVjdHyZr7PaHfxZGUDGKtGRL2QzRYukCmWo1cZbMBHcI5FzImvTHS9/8B3SATjXMPgbfBuEeBwuBK5EjL+CtHY5bWs9kmYjmeo0KfUMH8hY4MAXDoKhQ7DhBPIrcjS5jPtoGxIREZjba67r6/P2XKXaCZH6Fc= charlie@somemail.org 2011-01-17" 30 | only_if: "'charlie' in '$users'" 31 | 32 | 33 | This file is part of Ansible 34 | 35 | Ansible is free software: you can redistribute it and/or modify 36 | it under the terms of the GNU General Public License as published by 37 | the Free Software Foundation, either version 3 of the License, or 38 | (at your option) any later version. 39 | 40 | Ansible is distributed in the hope that it will be useful, 41 | but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 43 | GNU General Public License for more details. 44 | 45 | You should have received a copy of the GNU General Public License 46 | along with Ansible. If not, see . 47 | """ 48 | 49 | try: 50 | import json 51 | except ImportError: 52 | import simplejson as json 53 | 54 | import sys, os, shlex, pwd, syslog 55 | from os.path import expanduser, exists, isfile, join 56 | 57 | params = {} 58 | msg="" 59 | 60 | def exit_json(rc=0, **kwargs): 61 | if 'name' in kwargs: 62 | add_user_info(kwargs) 63 | print json.dumps(kwargs) 64 | sys.exit(rc) 65 | 66 | def fail_json(**kwargs): 67 | kwargs['failed'] = True 68 | exit_json(rc=1, **kwargs) 69 | 70 | def get_params(): 71 | """Startup tasks and read params. 72 | 73 | :return: parameters as dictionary. 74 | """ 75 | global msg 76 | 77 | msg = "reading params" 78 | argfile = sys.argv[1] 79 | try: 80 | f = open(argfile,"r") 81 | args = f.read() 82 | finally: 83 | f.close() 84 | 85 | msg = "writing syslog." 86 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 87 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 88 | 89 | msg = "parsing params" 90 | params = dict( # make a dictionary of... 91 | [ arg.split("=", 1) # assignment pairs 92 | for arg in shlex.split(args) # using shell lexing 93 | if "=" in arg # ignoring tokens without assignment 94 | ]) 95 | 96 | return params 97 | 98 | def keyfile(user, write=False): 99 | """Calculate name of authorized keys file, optionally creating the 100 | directories and file, properly setting permissions. 101 | 102 | :param str user: name of user in passwd file 103 | :param bool write: if True, write changes to authorized_keys file (creating directories if needed) 104 | :return: full path string to authorized_keys for user 105 | """ 106 | 107 | global msg 108 | msg = "Reading system user entry." 109 | user_entry = pwd.getpwnam(user) 110 | msg = "Calculating special directories" 111 | homedir = user_entry.pw_dir 112 | sshdir = join(homedir, ".ssh") 113 | keysfile = join(sshdir, "authorized_keys") 114 | if not write: return keysfile 115 | 116 | #create directories and files for authorized keys 117 | msg = "Reading user and group info." 118 | uid = user_entry.pw_uid 119 | gid = user_entry.pw_gid 120 | msg = "Making ~/.ssh." 121 | if not exists(sshdir): os.mkdir(sshdir, 0700) 122 | os.chown(sshdir, uid, gid) 123 | os.chmod(sshdir, 0700) 124 | msg = "Touching authorized keys file." 125 | if not exists( keysfile): 126 | try: 127 | f = open(keysfile, "w") #touches file so we can set ownership and perms 128 | finally: 129 | f.close() 130 | os.chown(keysfile, uid, gid) 131 | os.chmod(keysfile, 0600) 132 | return keysfile 133 | 134 | def readkeys( filename): 135 | global msg 136 | msg = "Reading authorized_keys." 137 | if not isfile(filename): return [] 138 | try: 139 | f = open(filename) 140 | keys = [line.rstrip() for line in f.readlines()] 141 | finally: 142 | f.close() 143 | return keys 144 | 145 | def writekeys( filename, keys): 146 | global msg 147 | msg = "Writing authorized_keys." 148 | try: 149 | f = open(filename,"w") 150 | f.writelines( (key + "\n" for key in keys) ) 151 | finally: 152 | f.close() 153 | 154 | def enforce_state( params): 155 | """Add or remove key. 156 | 157 | :return: True=changed, False=unchanged 158 | """ 159 | global msg 160 | 161 | #== scrub params 162 | msg = "Invalid or missing param: user." 163 | user = params["user"] 164 | msg = "Invalid or missing param: key." 165 | key = params["key"] 166 | state = params.get("state", "present") 167 | 168 | #== check current state 169 | params["keyfile"] = keyfile(user, write=False) #just get the filename, don't create file 170 | keys = readkeys( params["keyfile"]) 171 | present = key in keys 172 | 173 | #== handle idempotent state=present 174 | if state=="present": 175 | if present: return False #nothing to do 176 | keys.append(key) 177 | writekeys(keyfile(user,write=True), keys) 178 | elif state=="absent": 179 | if not present: return False #nothing to do 180 | keys.remove(key) 181 | writekeys(keyfile(user,write=True), keys) 182 | else: 183 | msg = "Invalid param: state." 184 | raise StandardError(msg) 185 | return True 186 | 187 | #===== MAIN SCRIPT =================================================== 188 | 189 | try: 190 | params = get_params() 191 | changed = enforce_state( params) 192 | msg = "" 193 | except: 194 | msg = "Error %s" % msg 195 | 196 | # Don't do sys.exit() within try...except 197 | if msg: 198 | fail_json(msg=msg) 199 | else: 200 | exit_json( user=params["user"], changed=changed) 201 | 202 | -------------------------------------------------------------------------------- /ansible-dist/library/command: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan , and others 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | try: 22 | import json 23 | except ImportError: 24 | import simplejson as json 25 | 26 | import subprocess 27 | import sys 28 | import datetime 29 | import traceback 30 | import shlex 31 | import os 32 | import syslog 33 | 34 | argfile = sys.argv[1] 35 | args = open(argfile, 'r').read() 36 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 37 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 38 | 39 | shell = False 40 | 41 | if args.find("#USE_SHELL") != -1: 42 | args = args.replace("#USE_SHELL", "") 43 | shell = True 44 | 45 | check_args = shlex.split(args) 46 | for x in check_args: 47 | if x.startswith("creates="): 48 | # do not run the command if the line contains creates=filename 49 | # and the filename already exists. This allows idempotence 50 | # of command executions. 51 | (k,v) = x.split("=",1) 52 | if os.path.exists(v): 53 | print json.dumps({ 54 | "cmd" : args, 55 | "stdout" : "skipped, since %s exists" % v, 56 | "skipped" : True, 57 | "changed" : False, 58 | "stderr" : "", 59 | "rc" : 0, 60 | }) 61 | sys.exit(0) 62 | args = args.replace(x,'') 63 | 64 | 65 | if not shell: 66 | args = shlex.split(args) 67 | 68 | startd = datetime.datetime.now() 69 | 70 | try: 71 | cmd = subprocess.Popen(args, shell=shell, 72 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 73 | out, err = cmd.communicate() 74 | except (OSError, IOError), e: 75 | print json.dumps({ 76 | "cmd" : args, 77 | "failed" : 1, 78 | "msg" : str(e), 79 | }) 80 | sys.exit(1) 81 | except: 82 | print json.dumps({ 83 | "failed" : 1, 84 | "msg" : traceback.format_exc() 85 | }) 86 | sys.exit(1) 87 | 88 | endd = datetime.datetime.now() 89 | delta = endd - startd 90 | 91 | if out is None: 92 | out = '' 93 | if err is None: 94 | err = '' 95 | 96 | result = { 97 | "cmd" : args, 98 | "stdout" : out.strip(), 99 | "stderr" : err.strip(), 100 | "rc" : cmd.returncode, 101 | "start" : str(startd), 102 | "end" : str(endd), 103 | "delta" : str(delta), 104 | "changed" : True 105 | } 106 | 107 | print json.dumps(result) 108 | -------------------------------------------------------------------------------- /ansible-dist/library/copy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | import sys 22 | import os 23 | import shlex 24 | import shutil 25 | import syslog 26 | 27 | try: 28 | from hashlib import md5 as _md5 29 | except ImportError: 30 | from md5 import md5 as _md5 31 | 32 | # =========================================== 33 | # convert arguments of form a=b c=d 34 | # to a dictionary 35 | # FIXME: make more idiomatic 36 | 37 | def dump_kv(vars): 38 | return " ".join("%s='%s'" % (k,v) for (k,v) in vars.items()) 39 | 40 | def exit_kv(rc=0, **kwargs): 41 | if 'path' in kwargs: 42 | add_path_info(kwargs) 43 | print dump_kv(kwargs) 44 | sys.exit(rc) 45 | 46 | def md5(filename): 47 | ''' Return MD5 hex digest of local file, or None if file is not present. ''' 48 | if not os.path.exists(filename): 49 | return None 50 | digest = _md5() 51 | blocksize = 64 * 1024 52 | infile = open(filename, 'rb') 53 | block = infile.read(blocksize) 54 | while block: 55 | digest.update(block) 56 | block = infile.read(blocksize) 57 | infile.close() 58 | return digest.hexdigest() 59 | 60 | # =========================================== 61 | 62 | if len(sys.argv) == 1: 63 | exit_kv(rc=1, failed=1, msg="incorrect number of arguments given") 64 | 65 | argfile = sys.argv[1] 66 | if not os.path.exists(argfile): 67 | exit_kv(rc=1, failed=1, msg="file %s does not exist" % (argfile)) 68 | 69 | args = open(argfile, 'r').read() 70 | items = shlex.split(args) 71 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 72 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 73 | 74 | 75 | params = {} 76 | for x in items: 77 | (k, v) = x.split("=") 78 | params[k] = v 79 | 80 | src = params['src'] 81 | dest = params['dest'] 82 | if src: 83 | src = os.path.expanduser(src) 84 | if dest: 85 | dest = os.path.expanduser(dest) 86 | 87 | md5sum_src = None 88 | # raise an error if there is no src file 89 | if not os.path.exists(src): 90 | exit_kv(rc=1, failed=1, msg="Source %s failed to transfer" % (src)) 91 | if not os.access(src, os.R_OK): 92 | exit_kv(rc=1, failed=1, msg="Source %s not readable" % (src)) 93 | md5sum_src = md5(src) 94 | 95 | md5sum_dest = None 96 | # check if there is no dest file 97 | if os.path.exists(dest): 98 | # raise an error if copy has no permission on dest 99 | if not os.access(dest, os.W_OK): 100 | exit_kv(rc=1, failed=1, msg="Destination %s not writable" % (dest)) 101 | if not os.access(dest, os.R_OK): 102 | exit_kv(rc=1, failed=1, msg="Destination %s not readable" % (dest)) 103 | md5sum_dest = md5(dest) 104 | else: 105 | if not os.access(os.path.dirname(dest), os.W_OK): 106 | exit_kv(rc=1, failed=1, msg="Destination %s not writable" % (os.path.dirname(dest))) 107 | 108 | if md5sum_src != md5sum_dest: 109 | # was os.system("cp %s %s" % (src, dest)) 110 | try: 111 | shutil.copyfile(src, dest) 112 | except shutil.Error: 113 | exit_kv(rc=1, failed=1, msg="failed to copy: %s and %s are the same" % (src, dest)) 114 | except IOError: 115 | exit_kv(rc=1, failed=1, msg="failed to copy: %s to %s" % (src, dest)) 116 | changed = True 117 | else: 118 | changed = False 119 | 120 | # mission accomplished 121 | #print "md5sum=%s changed=%s" % (md5sum_src, changed) 122 | exit_kv(dest=dest, src=src, md5sum=md5sum_src, changed=changed) 123 | 124 | -------------------------------------------------------------------------------- /ansible-dist/library/facter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | # things that must be installed to use this 22 | # facter 23 | # ruby-json 24 | 25 | /usr/bin/logger -t ansible-facter Invoked as-is 26 | /usr/bin/facter --json 2>/dev/null 27 | -------------------------------------------------------------------------------- /ansible-dist/library/failtest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | import sys 21 | 22 | try: 23 | import json 24 | except ImportError: 25 | import simplejson as json 26 | 27 | print >>sys.stderr, "THIS IS A TEST FAILURE" 28 | 29 | print json.dumps({ 30 | "failed" : True, 31 | "msg" : "this module always fails" 32 | }) 33 | 34 | 35 | -------------------------------------------------------------------------------- /ansible-dist/library/fetch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | ### THIS FILE IS FOR REFERENCE OR FUTURE USE ### 22 | 23 | # See lib/ansible/runner.py for implementation of the fetch functionality # 24 | 25 | -------------------------------------------------------------------------------- /ansible-dist/library/file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | import os 21 | import sys 22 | import shlex 23 | import subprocess 24 | import shutil 25 | import stat 26 | import grp 27 | import pwd 28 | import syslog 29 | try: 30 | import selinux 31 | HAVE_SELINUX=True 32 | except ImportError: 33 | HAVE_SELINUX=False 34 | 35 | def dump_kv(vars): 36 | return " ".join("%s='%s'" % (k,v) for (k,v) in vars.items()) 37 | 38 | def exit_kv(rc=0, **kwargs): 39 | if 'path' in kwargs: 40 | add_path_info(kwargs) 41 | print dump_kv(kwargs) 42 | sys.exit(rc) 43 | 44 | def fail_kv(**kwargs): 45 | kwargs['failed'] = True 46 | exit_kv(rc=1, **kwargs) 47 | 48 | def add_path_info(kwargs): 49 | path = kwargs['path'] 50 | if os.path.exists(path): 51 | (user, group) = user_and_group(path) 52 | kwargs['user'] = user 53 | kwargs['group'] = group 54 | st = os.stat(path) 55 | kwargs['mode'] = oct(stat.S_IMODE(st[stat.ST_MODE])) 56 | # secontext not yet supported 57 | if os.path.islink(path): 58 | kwargs['state'] = 'link' 59 | elif os.path.isfile(path): 60 | kwargs['state'] = 'file' 61 | else: 62 | kwargs['state'] = 'directory' 63 | if HAVE_SELINUX and selinux_enabled(): 64 | kwargs['secontext'] = ':'.join(selinux_context(path)) 65 | else: 66 | kwargs['state'] = 'absent' 67 | return kwargs 68 | 69 | # Detect whether using selinux that is MLS-aware. 70 | # While this means you can set the level/range with 71 | # selinux.lsetfilecon(), it may or may not mean that you 72 | # will get the selevel as part of the context returned 73 | # by selinux.lgetfilecon(). 74 | def selinux_mls_enabled(): 75 | if not HAVE_SELINUX: 76 | return False 77 | if selinux.is_selinux_mls_enabled() == 1: 78 | return True 79 | else: 80 | return False 81 | 82 | def selinux_enabled(): 83 | if not HAVE_SELINUX: 84 | return False 85 | if selinux.is_selinux_enabled() == 1: 86 | return True 87 | else: 88 | return False 89 | 90 | # Determine whether we need a placeholder for selevel/mls 91 | def selinux_initial_context(): 92 | context = [None, None, None] 93 | if selinux_mls_enabled(): 94 | context.append(None) 95 | return context 96 | 97 | # If selinux fails to find a default, return an array of None 98 | def selinux_default_context(path, mode=0): 99 | context = selinux_initial_context() 100 | if not HAVE_SELINUX or not selinux_enabled(): 101 | return context 102 | try: 103 | ret = selinux.matchpathcon(path, mode) 104 | except OSError: 105 | return context 106 | if ret[0] == -1: 107 | return context 108 | context = ret[1].split(':') 109 | return context 110 | 111 | def selinux_context(path): 112 | context = selinux_initial_context() 113 | if not HAVE_SELINUX or not selinux_enabled(): 114 | return context 115 | try: 116 | ret = selinux.lgetfilecon(path) 117 | except: 118 | fail_kv(path=path, msg='failed to retrieve selinux context') 119 | if ret[0] == -1: 120 | return context 121 | context = ret[1].split(':') 122 | return context 123 | 124 | # =========================================== 125 | 126 | argfile = sys.argv[1] 127 | args = open(argfile, 'r').read() 128 | items = shlex.split(args) 129 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 130 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 131 | 132 | if not len(items): 133 | fail_kv(msg='the module requires arguments -a') 134 | sys.exit(1) 135 | 136 | params = {} 137 | for x in items: 138 | (k, v) = x.split("=") 139 | params[k] = v 140 | 141 | state = params.get('state','file') 142 | path = params.get('path', params.get('dest', params.get('name', None))) 143 | if path: 144 | path = os.path.expanduser(path) 145 | src = params.get('src', None) 146 | if src: 147 | src = os.path.expanduser(src) 148 | dest = params.get('dest', None) 149 | mode = params.get('mode', None) 150 | owner = params.get('owner', None) 151 | group = params.get('group', None) 152 | 153 | # selinux related options 154 | seuser = params.get('seuser', None) 155 | serole = params.get('serole', None) 156 | setype = params.get('setype', None) 157 | selevel = params.get('serange', 's0') 158 | secontext = [seuser, serole, setype] 159 | if selinux_mls_enabled(): 160 | secontext.append(selevel) 161 | 162 | default_secontext = selinux_default_context(path) 163 | for i in range(len(default_secontext)): 164 | if i is not None and secontext[i] == '_default': 165 | secontext[i] = default_secontext[i] 166 | 167 | if state not in [ 'file', 'directory', 'link', 'absent']: 168 | fail_kv(msg='invalid state: %s' % state) 169 | 170 | if state == 'link' and (src is None or dest is None): 171 | fail_kv(msg='src and dest are required for "link" state') 172 | elif path is None: 173 | fail_kv(msg='path is required') 174 | 175 | changed = False 176 | 177 | # =========================================== 178 | # support functions 179 | 180 | def user_and_group(filename): 181 | st = os.stat(filename) 182 | uid = st.st_uid 183 | gid = st.st_gid 184 | try: 185 | user = pwd.getpwuid(uid)[0] 186 | except KeyError: 187 | user = str(uid) 188 | try: 189 | group = grp.getgrgid(gid)[0] 190 | except KeyError: 191 | group = str(gid) 192 | return (user, group) 193 | 194 | def set_context_if_different(path, context, changed): 195 | if not HAVE_SELINUX or not selinux_enabled(): 196 | return changed 197 | cur_context = selinux_context(path) 198 | new_context = list(cur_context) 199 | # Iterate over the current context instead of the 200 | # argument context, which may have selevel. 201 | for i in range(len(cur_context)): 202 | if context[i] is not None and context[i] != cur_context[i]: 203 | new_context[i] = context[i] 204 | if cur_context != new_context: 205 | try: 206 | rc = selinux.lsetfilecon(path, ':'.join(new_context)) 207 | except OSError: 208 | fail_kv(path=path, msg='invalid selinux context') 209 | if rc != 0: 210 | fail_kv(path=path, msg='set selinux context failed') 211 | changed = True 212 | return changed 213 | 214 | def set_owner_if_different(path, owner, changed): 215 | if owner is None: 216 | return changed 217 | user, group = user_and_group(path) 218 | if owner != user: 219 | rc = os.system("/bin/chown -R %s %s 2>/dev/null" % (owner, path)) 220 | if rc != 0: 221 | fail_kv(path=path, msg='chown failed') 222 | return True 223 | 224 | return changed 225 | 226 | def set_group_if_different(path, group, changed): 227 | if group is None: 228 | return changed 229 | old_user, old_group = user_and_group(path) 230 | if old_group != group: 231 | rc = os.system("/bin/chgrp -R %s %s" % (group, path)) 232 | if rc != 0: 233 | fail_kv(path=path, msg='chgrp failed') 234 | return True 235 | return changed 236 | 237 | def set_mode_if_different(path, mode, changed): 238 | if mode is None: 239 | return changed 240 | try: 241 | # FIXME: support English modes 242 | mode = int(mode, 8) 243 | except Exception, e: 244 | fail_kv(path=path, msg='mode needs to be something octalish', details=str(e)) 245 | 246 | st = os.stat(path) 247 | prev_mode = stat.S_IMODE(st[stat.ST_MODE]) 248 | 249 | if prev_mode != mode: 250 | # FIXME: comparison against string above will cause this to be executed 251 | # every time 252 | try: 253 | os.chmod(path, mode) 254 | except Exception, e: 255 | fail_kv(path=path, msg='chmod failed', details=str(e)) 256 | 257 | st = os.stat(path) 258 | new_mode = stat.S_IMODE(st[stat.ST_MODE]) 259 | 260 | if new_mode != prev_mode: 261 | return True 262 | return changed 263 | 264 | 265 | def rmtree_error(func, path, exc_info): 266 | fail_kv(path=path, msg='failed to remove directory') 267 | 268 | # =========================================== 269 | # go... 270 | 271 | prev_state = 'absent' 272 | if os.path.lexists(path): 273 | if os.path.islink(path): 274 | prev_state = 'link' 275 | elif os.path.isfile(path): 276 | prev_state = 'file' 277 | else: 278 | prev_state = 'directory' 279 | 280 | if prev_state != 'absent' and state == 'absent': 281 | try: 282 | if prev_state == 'directory': 283 | if os.path.islink(path): 284 | os.unlink(path) 285 | else: 286 | shutil.rmtree(path, ignore_errors=False, onerror=rmtree_error) 287 | else: 288 | os.unlink(path) 289 | except Exception, e: 290 | fail_kv(path=path, msg=str(e)) 291 | exit_kv(path=path, changed=True) 292 | sys.exit(0) 293 | 294 | if prev_state != 'absent' and prev_state != state: 295 | fail_kv(path=path, msg='refusing to convert between %s and %s' % (prev_state, state)) 296 | 297 | if prev_state == 'absent' and state == 'absent': 298 | exit_kv(path=path, changed=False) 299 | 300 | if state == 'file': 301 | 302 | if prev_state == 'absent': 303 | fail_kv(path=path, msg='file does not exist, use copy or template module to create') 304 | 305 | # set modes owners and context as needed 306 | changed = set_context_if_different(path, secontext, changed) 307 | changed = set_owner_if_different(path, owner, changed) 308 | changed = set_group_if_different(path, group, changed) 309 | changed = set_mode_if_different(path, mode, changed) 310 | 311 | exit_kv(path=path, changed=changed) 312 | 313 | elif state == 'directory': 314 | 315 | if prev_state == 'absent': 316 | os.makedirs(path) 317 | changed = True 318 | 319 | # set modes owners and context as needed 320 | changed = set_context_if_different(path, secontext, changed) 321 | changed = set_owner_if_different(path, owner, changed) 322 | changed = set_group_if_different(path, group, changed) 323 | changed = set_mode_if_different(path, mode, changed) 324 | 325 | exit_kv(path=path, changed=changed) 326 | 327 | elif state == 'link': 328 | 329 | if os.path.isabs(src): 330 | abs_src = src 331 | else: 332 | abs_src = os.path.join(os.path.dirname(dest), src) 333 | if not os.path.exists(abs_src): 334 | fail_kv(dest=dest, src=src, msg='src file does not exist') 335 | 336 | if prev_state == 'absent': 337 | os.symlink(src, dest) 338 | changed = True 339 | elif prev_state == 'link': 340 | old_src = os.readlink(dest) 341 | if not os.path.isabs(old_src): 342 | old_src = os.path.join(os.path.dirname(dest), old_src) 343 | if old_src != src: 344 | os.unlink(dest) 345 | os.symlink(src, dest) 346 | else: 347 | fail_kv(dest=dest, src=src, msg='unexpected position reached') 348 | 349 | # set modes owners and context as needed 350 | changed = set_context_if_different(dest, secontext, changed) 351 | changed = set_owner_if_different(dest, owner, changed) 352 | changed = set_group_if_different(dest, group, changed) 353 | changed = set_mode_if_different(dest, mode, changed) 354 | 355 | exit_kv(dest=dest, src=src, changed=changed) 356 | 357 | 358 | fail_kv(path=path, msg='unexpected position reached') 359 | sys.exit(0) 360 | 361 | -------------------------------------------------------------------------------- /ansible-dist/library/git: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | # I wanted to keep this simple at first, so for now this checks out 21 | # from the given branch of a repo at a particular SHA or 22 | # tag. Latest is not supported, you should not be doing 23 | # that. Contribs welcome! -- MPD 24 | 25 | try: 26 | import json 27 | except ImportError: 28 | import simplejson as json 29 | import os 30 | import re 31 | import sys 32 | import shlex 33 | import subprocess 34 | import syslog 35 | 36 | # =========================================== 37 | # Basic support methods 38 | 39 | def exit_json(rc=0, **kwargs): 40 | print json.dumps(kwargs) 41 | sys.exit(rc) 42 | 43 | def fail_json(**kwargs): 44 | kwargs['failed'] = True 45 | exit_json(rc=1, **kwargs) 46 | 47 | # =========================================== 48 | # convert arguments of form a=b c=d 49 | # to a dictionary 50 | # FIXME: make more idiomatic 51 | 52 | if len(sys.argv) == 1: 53 | fail_json(msg="the command module requires arguments (-a)") 54 | 55 | argfile = sys.argv[1] 56 | if not os.path.exists(argfile): 57 | fail_json(msg="Argument file not found") 58 | 59 | args = open(argfile, 'r').read() 60 | items = shlex.split(args) 61 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 62 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 63 | 64 | if not len(items): 65 | fail_json(msg="the command module requires arguments (-a)") 66 | 67 | params = {} 68 | for x in items: 69 | (k, v) = x.split("=") 70 | params[k] = v 71 | 72 | dest = params['dest'] 73 | repo = params['repo'] 74 | branch = params.get('branch', 'master') 75 | version = params.get('version', 'HEAD') 76 | 77 | # =========================================== 78 | 79 | def get_version(dest): 80 | ''' samples the version of the git repo ''' 81 | os.chdir(dest) 82 | cmd = "git show --abbrev-commit" 83 | sha = os.popen(cmd).read().split("\n") 84 | sha = sha[0].split()[1] 85 | return sha 86 | 87 | def clone(repo, dest, branch): 88 | ''' makes a new git repo if it does not already exist ''' 89 | try: 90 | os.makedirs(dest) 91 | except: 92 | pass 93 | cmd = "git clone %s %s" % (repo, dest) 94 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 95 | (out, err) = cmd.communicate() 96 | rc = cmd.returncode 97 | 98 | if branch is None or rc != 0: 99 | return (out, err) 100 | 101 | os.chdir(dest) 102 | cmd = "git checkout -b %s origin/%s" % (branch, branch) 103 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 104 | return cmd.communicate() 105 | 106 | def reset(dest): 107 | ''' 108 | Resets the index and working tree to HEAD. 109 | Discards any changes to tracked files in working 110 | tree since that commit. 111 | ''' 112 | os.chdir(dest) 113 | cmd = "git reset --hard HEAD" 114 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 115 | (out, err) = cmd.communicate() 116 | rc = cmd.returncode 117 | return (rc, out, err) 118 | 119 | def switchLocalBranch( branch ): 120 | cmd = "git checkout %s" % branch 121 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 122 | return cmd.communicate() 123 | 124 | def pull(repo, dest, branch): 125 | ''' updates repo from remote sources ''' 126 | os.chdir(dest) 127 | cmd = "git branch -a" 128 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 129 | (gbranch_out, gbranch_err) = cmd.communicate() 130 | 131 | try: 132 | m = re.search( '^\* (\S+|\(no branch\))$', gbranch_out, flags=re.M ) 133 | cur_branch = m.group(1) 134 | m = re.search( '\s+remotes/origin/HEAD -> origin/(\S+)', gbranch_out, flags=re.M ) 135 | default_branch = m.group(1) 136 | except: 137 | fail_json(msg="could not determine branch data - received: %s" % gbranch_out) 138 | 139 | if branch is None: 140 | if cur_branch != default_branch: 141 | (out, err) = switchLocalBranch( default_branch ) 142 | 143 | cmd = "git pull -u origin" 144 | 145 | elif branch == cur_branch: 146 | cmd = "git pull -u origin" 147 | 148 | else: 149 | m = re.search( '^\s+%s$' % branch, gbranch_out, flags=re.M ) #see if we've already checked it out 150 | if m is None: 151 | cmd = "git checkout -b %s origin/%s" % (branch, branch) 152 | 153 | else: 154 | (out, err) = switchLocalBranch( branch ) 155 | cmd = "git pull -u origin" 156 | 157 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 158 | return cmd.communicate() 159 | 160 | def switchver(version, dest): 161 | ''' once pulled, switch to a particular SHA or tag ''' 162 | os.chdir(dest) 163 | if version != 'HEAD': 164 | cmd = "git checkout %s --force" % version 165 | else: 166 | # is there a better way to do this? 167 | cmd = "git rebase origin" 168 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 169 | (out, err) = cmd.communicate() 170 | return (out, err) 171 | 172 | 173 | gitconfig = os.path.join(dest, '.git', 'config') 174 | 175 | out, err, status = (None, None, None) 176 | 177 | # if there is no git configuration, do a clone operation 178 | # else pull and switch the version 179 | 180 | before = None 181 | if not os.path.exists(gitconfig): 182 | (out, err) = clone(repo, dest, branch) 183 | else: 184 | # else do a pull 185 | before = get_version(dest) 186 | (rc, out, err) = reset(dest) 187 | if rc != 0: 188 | fail_json(out=out, err=err) 189 | (out, err) = pull(repo, dest, branch) 190 | 191 | # handle errors from clone or pull 192 | 193 | if out.find('error') != -1: 194 | fail_json(out=out, err=err) 195 | 196 | # switch to version specified regardless of whether 197 | # we cloned or pulled 198 | 199 | (out, err) = switchver(version, dest) 200 | if err.find('error') != -1: 201 | fail_json(out=out, err=err) 202 | 203 | # determine if we changed anything 204 | 205 | after = get_version(dest) 206 | changed = False 207 | 208 | if before != after: 209 | changed = True 210 | 211 | exit_json(changed=changed, before=before, after=after) 212 | -------------------------------------------------------------------------------- /ansible-dist/library/group: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # (c) 2012, Stephen Fromm 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | import os 25 | import grp 26 | import shlex 27 | import subprocess 28 | import sys 29 | import syslog 30 | 31 | GROUPADD = "/usr/sbin/groupadd" 32 | GROUPDEL = "/usr/sbin/groupdel" 33 | GROUPMOD = "/usr/sbin/groupmod" 34 | 35 | def exit_json(rc=0, **kwargs): 36 | if 'name' in kwargs: 37 | add_group_info(kwargs) 38 | print json.dumps(kwargs) 39 | sys.exit(rc) 40 | 41 | def fail_json(**kwargs): 42 | kwargs['failed'] = True 43 | exit_json(rc=1, **kwargs) 44 | 45 | def add_group_info(kwargs): 46 | name = kwargs['name'] 47 | if group_exists(name): 48 | kwargs['state'] = 'present' 49 | info = group_info(name) 50 | kwargs['gid'] = info[2] 51 | else: 52 | kwargs['state'] = 'absent' 53 | return kwargs 54 | 55 | def group_del(group): 56 | cmd = [GROUPDEL, group] 57 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 58 | (out, err) = p.communicate() 59 | rc = p.returncode 60 | return (rc, out, err) 61 | 62 | def group_add(group, **kwargs): 63 | cmd = [GROUPADD] 64 | for key in kwargs: 65 | if key == 'gid' and kwargs[key] is not None: 66 | cmd.append('-g') 67 | cmd.append(kwargs[key]) 68 | elif key == 'system' and kwargs[key] == 'yes': 69 | cmd.append('-r') 70 | cmd.append(group) 71 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 72 | (out, err) = p.communicate() 73 | rc = p.returncode 74 | return (rc, out, err) 75 | 76 | def group_mod(group, **kwargs): 77 | cmd = [GROUPMOD] 78 | info = group_info(group) 79 | for key in kwargs: 80 | if key == 'gid': 81 | if kwargs[key] is not None and info[2] != int(kwargs[key]): 82 | cmd.append('-g') 83 | cmd.append(kwargs[key]) 84 | if len(cmd) == 1: 85 | return (None, '', '') 86 | cmd.append(group) 87 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 88 | (out, err) = p.communicate() 89 | rc = p.returncode 90 | return (rc, out, err) 91 | 92 | def group_exists(group): 93 | try: 94 | if grp.getgrnam(group): 95 | return True 96 | except KeyError: 97 | return False 98 | 99 | def group_info(group): 100 | if not group_exists(group): 101 | return False 102 | try: 103 | info = list(grp.getgrnam(group)) 104 | except KeyError: 105 | return False 106 | return info 107 | 108 | # =========================================== 109 | 110 | if not os.path.exists(GROUPADD): 111 | if os.path.exists("/sbin/groupadd"): 112 | GROUPADD = "/sbin/groupadd" 113 | else: 114 | fail_json(msg="Cannot find groupadd") 115 | if not os.path.exists(GROUPDEL): 116 | if os.path.exists("/sbin/groupdel"): 117 | GROUPDEL = "/sbin/groupdel" 118 | else: 119 | fail_json(msg="Cannot find groupdel") 120 | if not os.path.exists(GROUPMOD): 121 | if os.path.exists("/sbin/groupmod"): 122 | GROUPDEL = "/sbin/groupmod" 123 | else: 124 | fail_json(msg="Cannot find groupmod") 125 | 126 | if len(sys.argv) == 2 and os.path.exists(sys.argv[1]): 127 | argfile = sys.argv[1] 128 | args = open(argfile, 'r').read() 129 | else: 130 | args = ' '.join(sys.argv[1:]) 131 | items = shlex.split(args) 132 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 133 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 134 | 135 | if not len(items): 136 | fail_json(msg='the module requires arguments -a') 137 | sys.exit(1) 138 | 139 | params = {} 140 | for x in items: 141 | (k, v) = x.split("=") 142 | params[k] = v 143 | 144 | state = params.get('state','present') 145 | name = params.get('name', None) 146 | gid = params.get('gid', None) 147 | system = params.get('system', 'no') 148 | 149 | if state not in [ 'present', 'absent' ]: 150 | fail_json(msg='invalid state') 151 | if system not in ['yes', 'no']: 152 | fail_json(msg='invalid system') 153 | if name is None: 154 | fail_json(msg='name is required') 155 | 156 | rc = None 157 | out = '' 158 | err = '' 159 | result = {} 160 | result['name'] = name 161 | if state == 'absent': 162 | if group_exists(name): 163 | (rc, out, err) = group_del(name) 164 | if rc != 0: 165 | fail_json(name=name, msg=err) 166 | elif state == 'present': 167 | if not group_exists(name): 168 | (rc, out, err) = group_add(name, gid=gid, system=system) 169 | else: 170 | (rc, out, err) = group_mod(name, gid=gid) 171 | 172 | if rc is not None and rc != 0: 173 | fail_json(name=name, msg=err) 174 | 175 | if rc is None: 176 | result['changed'] = False 177 | else: 178 | result['changed'] = True 179 | if out: 180 | result['stdout'] = out 181 | if err: 182 | result['stderr'] = err 183 | exit_json(**result) 184 | fail_json(name=name, msg='Unexpected position reached') 185 | -------------------------------------------------------------------------------- /ansible-dist/library/ohai: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | /usr/bin/logger -t ansible-ohai Invoked as-is 22 | /usr/bin/ohai 23 | -------------------------------------------------------------------------------- /ansible-dist/library/ping: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | 25 | import os 26 | import syslog 27 | 28 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 29 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked as-is') 30 | 31 | print json.dumps({ "ping" : "pong" }) 32 | -------------------------------------------------------------------------------- /ansible-dist/library/raw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | # hey the Ansible raw module isn't really a remote transferred 21 | # module. All the magic happens in Runner.py, see the web docs 22 | # for more details. 23 | 24 | -------------------------------------------------------------------------------- /ansible-dist/library/service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | import sys 25 | import shlex 26 | import subprocess 27 | import os.path 28 | import syslog 29 | 30 | # TODO: switch to fail_json and other helper functions 31 | # like other modules are using 32 | 33 | # =========================================== 34 | 35 | SERVICE = None 36 | CHKCONFIG = None 37 | 38 | def fail_json(d): 39 | print json.dumps(d) 40 | sys.exit(1) 41 | 42 | def _find_binaries(): 43 | # list of possible paths for service/chkconfig binaries 44 | # with the most probable first 45 | global CHKCONFIG 46 | global SERVICE 47 | global INITCTL 48 | paths = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 49 | binaries = [ 'service', 'chkconfig', 'update-rc.d', 'initctl'] 50 | location = dict() 51 | 52 | for binary in binaries: 53 | location[binary] = None 54 | 55 | for binary in binaries: 56 | for path in paths: 57 | if os.path.exists(path + '/' + binary): 58 | location[binary] = path + '/' + binary 59 | break 60 | 61 | if location.get('chkconfig', None): 62 | CHKCONFIG = location['chkconfig'] 63 | elif location.get('update-rc.d', None): 64 | CHKCONFIG = location['update-rc.d'] 65 | else: 66 | fail_json(dict(failed=True, msg='unable to find chkconfig or update-rc.d binary')) 67 | if location.get('service', None): 68 | SERVICE = location['service'] 69 | else: 70 | fail_json(dict(failed=True, msg='unable to find service binary')) 71 | if location.get('initctl', None): 72 | INITCTL = location['initctl'] 73 | else: 74 | INITCTL = None 75 | 76 | def _get_service_status(name): 77 | rc, status_stdout, status_stderr = _run("%s %s status" % (SERVICE, name)) 78 | status = status_stdout + status_stderr 79 | 80 | # set the running state to None because we don't know it yet 81 | running = None 82 | 83 | # Check if we got upstart on the system and then the job state 84 | if INITCTL != None: 85 | # check the job status by upstart response 86 | initctl_rc, initctl_status_stdout, initctl_status_stderr = _run("%s status %s" % (INITCTL, name)) 87 | if initctl_status_stdout.find("stop/waiting") != -1: 88 | running = False 89 | elif initctl_status_stdout.find("start/running") != -1: 90 | running = True 91 | 92 | # if the job status is still not known check it by response code 93 | if running == None: 94 | if rc == 3: 95 | running = False 96 | elif rc == 0: 97 | running = True 98 | 99 | # if the job status is still not known check it by status output keywords 100 | if running == None: 101 | # first tranform the status output that could irritate keyword matching 102 | cleaned_status_stdout = status_stdout.lower().replace(name.lower(),'') 103 | if cleaned_status_stdout.find("stop") != -1: 104 | running = False 105 | elif cleaned_status_stdout.find("run") != -1 and cleaned_status_stdout.find("not") != -1: 106 | running = False 107 | elif cleaned_status_stdout.find("run") != -1 and cleaned_status_stdout.find("not") == -1: 108 | running = True 109 | elif cleaned_status_stdout.find("start") != -1 and cleaned_status_stdout.find("not") == -1: 110 | running = True 111 | elif 'could not access pid file' in cleaned_status_stdout: 112 | running = False 113 | 114 | # if the job status is still not known check it by special conditions 115 | if running == None: 116 | if name == 'iptables' and status_stdout.find("ACCEPT") != -1: 117 | # iptables status command output is lame 118 | # TODO: lookup if we can use a return code for this instead? 119 | running = True 120 | 121 | return running 122 | 123 | def _run(cmd): 124 | # returns (rc, stdout, stderr) from shell command 125 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 126 | stdout, stderr = process.communicate() 127 | return (process.returncode, stdout, stderr) 128 | 129 | 130 | def _do_enable(name, enable): 131 | # we change argument depending on real binary used 132 | # update-rc.d wants enable/disable while 133 | # chkconfig wants on/off 134 | valid_argument = dict({'on' : 'on', 'off' : 'off'}) 135 | 136 | if CHKCONFIG.endswith("update-rc.d"): 137 | valid_argument['on'] = "enable" 138 | valid_argument['off'] = "disable" 139 | 140 | if enable.lower() in ['on', 'true', 'yes', 'enable']: 141 | rc, stdout, stderr = _run("%s %s %s" % (CHKCONFIG, name, valid_argument['on'])) 142 | elif enable.lower() in ['off', 'false', 'no', 'disable']: 143 | rc, stdout, stderr = _run("%s %s %s" % (CHKCONFIG, name, valid_argument['off'])) 144 | 145 | return rc, stdout, stderr 146 | 147 | argfile = sys.argv[1] 148 | args = open(argfile, 'r').read() 149 | items = shlex.split(args) 150 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 151 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 152 | 153 | if not len(items): 154 | fail_json(dict(failed=True, msg='this module requires arguments (-a)')) 155 | 156 | params = {} 157 | for arg in items: 158 | if "=" not in arg: 159 | fail_json(dict(failed=True, msg='expected key=value format arguments')) 160 | 161 | (name, value) = arg.split("=") 162 | params[name] = value 163 | 164 | name = params.get('name', None) 165 | 166 | if name is None: 167 | fail_json(dict(failed=True, msg='missing name')) 168 | 169 | state = params.get('state', None) 170 | list_items = params.get('list', None) 171 | enable = params.get('enabled', params.get('enable', None)) 172 | 173 | # running and started are the same 174 | if state and state.lower() not in [ 'running', 'started', 'stopped', 'restarted','reloaded' ]: 175 | fail_json(dict(failed=True, msg='invalid value for state')) 176 | if list_items and list_items.lower() not in [ 'status' ]: 177 | fail_json(dict(failed=True, msg='invalid value for list')) 178 | if enable and enable.lower() not in [ 'on', 'off', 'true', 'false', 'yes', 'no', 'enable', 'disable' ]: 179 | fail_json(dict(failed=True, msg='invalid value for enable')) 180 | 181 | 182 | # =========================================== 183 | # find binaries locations on minion 184 | _find_binaries() 185 | 186 | 187 | # =========================================== 188 | # get service status 189 | running = _get_service_status(name) 190 | 191 | 192 | if state or enable: 193 | rc = 0 194 | out = '' 195 | err = '' 196 | changed = False 197 | 198 | if enable: 199 | rc_enable, out_enable, err_enable = _do_enable(name, enable) 200 | rc += rc_enable 201 | out += out_enable 202 | err += err_enable 203 | 204 | if state and running == None: 205 | print json.dumps({ 206 | "failed" : True, 207 | "msg" : "failed determining the current service state => state stays unchanged", 208 | }) 209 | print >> sys.stderr, out + err 210 | elif state: 211 | # a state change command has been requested 212 | 213 | # =========================================== 214 | # determine if we are going to change anything 215 | 216 | if not running and state in ("started", "running"): 217 | changed = True 218 | elif running and state in ("stopped","reloaded"): 219 | changed = True 220 | elif state == "restarted": 221 | changed = True 222 | 223 | # =========================================== 224 | # run change commands if we need to 225 | 226 | if changed: 227 | if state in ('started', 'running'): 228 | rc_state, stdout, stderr = _run("%s %s start" % (SERVICE, name)) 229 | elif state == 'stopped': 230 | rc_state, stdout, stderr = _run("%s %s stop" % (SERVICE, name)) 231 | elif state == 'reloaded': 232 | rc_state, stdout, stderr = _run("%s %s reload" % (SERVICE, name)) 233 | elif state == 'restarted': 234 | rc1, stdout1, stderr1 = _run("%s %s stop" % (SERVICE, name)) 235 | rc2, stdout2, stderr2 = _run("%s %s start" % (SERVICE, name)) 236 | rc_state = rc + rc1 + rc2 237 | stdout = stdout1 + stdout2 238 | stderr = stderr1 + stderr2 239 | 240 | out += stdout 241 | err += stderr 242 | rc = rc + rc_state 243 | 244 | if rc != 0: 245 | 246 | print json.dumps({ 247 | "failed" : 1, 248 | "rc" : rc, 249 | }) 250 | print >> sys.stderr, out + err 251 | sys.exit(1) 252 | 253 | 254 | # =============================================== 255 | # success 256 | 257 | result = {"changed": changed} 258 | 259 | rc, stdout, stderr = _run("%s %s status" % (SERVICE, name)) 260 | if list_items and list_items in [ 'status' ]: 261 | result['status'] = stdout 262 | print json.dumps(result) 263 | 264 | 265 | elif list_items is not None: 266 | 267 | # solo list=status mode, don't change anything, just return 268 | # suitable for /usr/bin/ansible usage or API, playbooks 269 | # not so much 270 | 271 | print json.dumps({ 272 | "status" : status 273 | }) 274 | 275 | else: 276 | 277 | print json.dumps(dict(failed=True, msg="expected state or list parameters")) 278 | 279 | 280 | sys.exit(0) 281 | 282 | -------------------------------------------------------------------------------- /ansible-dist/library/shell: -------------------------------------------------------------------------------- 1 | # VIRTUAL 2 | 3 | There is actually no actual shell module source, when you use 'shell' in ansible, 4 | it runs the 'command' module with special arguments and it behaves differently. 5 | See the command source and the comment "#USE_SHELL". 6 | 7 | -------------------------------------------------------------------------------- /ansible-dist/library/slurp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | import sys 21 | import os 22 | import shlex 23 | import base64 24 | import syslog 25 | 26 | try: 27 | import json 28 | except ImportError: 29 | import simplejson as json 30 | 31 | # =========================================== 32 | # convert arguments of form a=b c=d 33 | # to a dictionary 34 | 35 | if len(sys.argv) == 1: 36 | sys.exit(1) 37 | argfile = sys.argv[1] 38 | if not os.path.exists(argfile): 39 | sys.exit(1) 40 | 41 | args = open(argfile, 'r').read() 42 | items = shlex.split(args) 43 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 44 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % args) 45 | 46 | params = {} 47 | for x in items: 48 | (k, v) = x.split("=") 49 | params[k] = v 50 | source = os.path.expanduser(params['src']) 51 | 52 | # ========================================== 53 | 54 | # raise an error if there is no template metadata 55 | if not os.path.exists(source): 56 | print json.dumps(dict( 57 | failed = 1, 58 | msg = "file not found: %s" % source 59 | )) 60 | sys.exit(1) 61 | 62 | if not os.access(source, os.R_OK): 63 | print json.dumps(dict( 64 | failed = 1, 65 | msg = "file is not readable: %s" % source 66 | )) 67 | sys.exit(1) 68 | 69 | # ========================================== 70 | 71 | data = file(source).read() 72 | data = base64.b64encode(data) 73 | 74 | print json.dumps(dict(content=data, encoding='base64')) 75 | sys.exit(0) 76 | 77 | -------------------------------------------------------------------------------- /ansible-dist/library/template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Michael DeHaan 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | # hey the Ansible template module isn't really a remote transferred 21 | # module. All the magic happens in Runner.py making use of the 22 | # copy module, and if not running from a playbook, also the 'slurp' 23 | # module. 24 | 25 | -------------------------------------------------------------------------------- /ansible-dist/library/user: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # (c) 2012, Stephen Fromm 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | 20 | try: 21 | import json 22 | except ImportError: 23 | import simplejson as json 24 | import os 25 | import re 26 | import pwd 27 | import grp 28 | import shlex 29 | import subprocess 30 | import sys 31 | import syslog 32 | try: 33 | import spwd 34 | HAVE_SPWD=True 35 | except: 36 | HAVE_SPWD=False 37 | 38 | USERADD = "/usr/sbin/useradd" 39 | USERMOD = "/usr/sbin/usermod" 40 | USERDEL = "/usr/sbin/userdel" 41 | 42 | def exit_json(rc=0, **kwargs): 43 | if 'name' in kwargs: 44 | add_user_info(kwargs) 45 | print json.dumps(kwargs) 46 | sys.exit(rc) 47 | 48 | def fail_json(**kwargs): 49 | kwargs['failed'] = True 50 | exit_json(rc=1, **kwargs) 51 | 52 | def add_user_info(kwargs): 53 | name = kwargs['name'] 54 | if user_exists(name): 55 | kwargs['state'] = 'present' 56 | info = user_info(name) 57 | kwargs['uid'] = info[2] 58 | kwargs['group'] = info[3] 59 | kwargs['comment'] = info[4] 60 | kwargs['home'] = info[5] 61 | kwargs['shell'] = info[6] 62 | kwargs['createhome'] = os.path.exists(info[5]) 63 | groups = user_group_membership(name) 64 | if len(groups) > 0: 65 | kwargs['groups'] = groups 66 | else: 67 | kwargs['state'] = 'absent' 68 | return kwargs 69 | 70 | def user_del(user, **kwargs): 71 | cmd = [USERDEL] 72 | for key in kwargs: 73 | if key == 'force' and kwargs[key] == 'yes': 74 | cmd.append('-f') 75 | elif key == 'remove' and kwargs[key] == 'yes': 76 | cmd.append('-r') 77 | cmd.append(user) 78 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 79 | (out, err) = p.communicate() 80 | rc = p.returncode 81 | return (rc, out, err) 82 | 83 | def user_add(user, **kwargs): 84 | cmd = [USERADD] 85 | for key in kwargs: 86 | if key == 'uid' and kwargs[key] is not None: 87 | cmd.append('-u') 88 | cmd.append(kwargs[key]) 89 | elif key == 'group' and kwargs[key] is not None: 90 | if not group_exists(kwargs[key]): 91 | fail_json(msg="Group %s does not exist" % (kwargs[key])) 92 | cmd.append('-g') 93 | cmd.append(kwargs[key]) 94 | elif key == 'groups' and kwargs[key] is not None: 95 | for g in kwargs[key].split(','): 96 | if not group_exists(g): 97 | fail_json(msg="Group %s does not exist" % (g)) 98 | cmd.append('-G') 99 | cmd.append(kwargs[key]) 100 | elif key == 'comment' and kwargs[key] is not None: 101 | cmd.append('-c') 102 | cmd.append(kwargs[key]) 103 | elif key == 'home' and kwargs[key] is not None: 104 | cmd.append('-d') 105 | cmd.append(kwargs[key]) 106 | elif key == 'shell' and kwargs[key] is not None: 107 | cmd.append('-s') 108 | cmd.append(kwargs[key]) 109 | elif key == 'password' and kwargs[key] is not None: 110 | cmd.append('-p') 111 | cmd.append(kwargs[key]) 112 | elif key == 'createhome': 113 | if kwargs[key] is not None: 114 | if kwargs[key] == 'yes': 115 | cmd.append('-m') 116 | else: 117 | cmd.append('-M') 118 | elif key == 'system' and kwargs[key] == 'yes': 119 | cmd.append('-r') 120 | cmd.append(user) 121 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 122 | (out, err) = p.communicate() 123 | rc = p.returncode 124 | return (rc, out, err) 125 | 126 | """ 127 | Without spwd, we would have to resort to reading /etc/shadow 128 | to get the encrypted string. For now, punt on idempotent password changes. 129 | """ 130 | def user_mod(user, **kwargs): 131 | cmd = [USERMOD] 132 | info = user_info(user) 133 | for key in kwargs: 134 | if key == 'uid': 135 | if kwargs[key] is not None and info[2] != int(kwargs[key]): 136 | cmd.append('-u') 137 | cmd.append(kwargs[key]) 138 | elif key == 'group' and kwargs[key] is not None: 139 | if not group_exists(kwargs[key]): 140 | fail_json(msg="Group %s does not exist" % (kwargs[key])) 141 | ginfo = group_info(group) 142 | if info[3] != ginfo[2]: 143 | cmd.append('-g') 144 | cmd.append(kwargs[key]) 145 | elif key == 'groups' and kwargs[key] is not None: 146 | current_groups = user_group_membership(user) 147 | groups = kwargs[key].split(',') 148 | for g in groups: 149 | if not group_exists(g): 150 | fail_json(msg="Group %s does not exist" % (g)) 151 | group_diff = set(sorted(current_groups)).symmetric_difference(set(sorted(groups))) 152 | groups_need_mod = False 153 | 154 | if group_diff: 155 | if kwargs['append'] is not None and kwargs['append'] == 'yes': 156 | for g in groups: 157 | if g in group_diff: 158 | cmd.append('-a') 159 | groups_need_mod = True 160 | else: 161 | groups_need_mod = True 162 | 163 | if groups_need_mod: 164 | cmd.append('-G') 165 | cmd.append(','.join(groups)) 166 | 167 | elif key == 'comment': 168 | if kwargs[key] is not None and info[4] != kwargs[key]: 169 | cmd.append('-c') 170 | cmd.append(kwargs[key]) 171 | elif key == 'home': 172 | if kwargs[key] is not None and info[5] != kwargs[key]: 173 | cmd.append('-d') 174 | cmd.append(kwargs[key]) 175 | elif key == 'shell': 176 | if kwargs[key] is not None and info[6] != kwargs[key]: 177 | cmd.append('-s') 178 | cmd.append(kwargs[key]) 179 | elif key == 'password': 180 | if kwargs[key] is not None and info[1] != kwargs[key]: 181 | cmd.append('-p') 182 | cmd.append(kwargs[key]) 183 | # skip if no changes to be made 184 | if len(cmd) == 1: 185 | return (None, '', '') 186 | cmd.append(user) 187 | p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 188 | (out, err) = p.communicate() 189 | rc = p.returncode 190 | return (rc, out, err) 191 | 192 | def group_exists(group): 193 | try: 194 | if group.isdigit(): 195 | if grp.getgrgid(group): 196 | return True 197 | else: 198 | if grp.getgrnam(group): 199 | return True 200 | except KeyError: 201 | return False 202 | 203 | def group_info(group): 204 | if not group_exists(group): 205 | return False 206 | if group.isdigit(): 207 | return list(grp.getgrgid(group)) 208 | else: 209 | return list(grp.getgrnam(group)) 210 | 211 | def user_group_membership(user): 212 | groups = [] 213 | info = get_pwd_info(user) 214 | for group in grp.getgrall(): 215 | if user in group[3] and info[3] != group[2]: 216 | groups.append(group[0]) 217 | return groups 218 | 219 | def user_exists(user): 220 | try: 221 | if pwd.getpwnam(user): 222 | return True 223 | except KeyError: 224 | return False 225 | 226 | def get_pwd_info(user): 227 | if not user_exists(user): 228 | return False 229 | return list(pwd.getpwnam(user)) 230 | 231 | def user_info(user): 232 | if not user_exists(user): 233 | return False 234 | try: 235 | info = get_pwd_info(user) 236 | if HAVE_SPWD: 237 | sinfo = spwd.getspnam(user) 238 | except KeyError: 239 | return False 240 | if HAVE_SPWD: 241 | info[1] = sinfo[1] 242 | return info 243 | 244 | # =========================================== 245 | 246 | if not os.path.exists(USERADD): 247 | if os.path.exists("/sbin/useradd"): 248 | USERADD = "/sbin/useradd" 249 | else: 250 | fail_json(msg="Cannot find useradd") 251 | if not os.path.exists(USERMOD): 252 | if os.path.exists("/sbin/usermod"): 253 | USERMOD = "/sbin/usermod" 254 | else: 255 | fail_json(msg="Cannot find usermod") 256 | if not os.path.exists(USERDEL): 257 | if os.path.exists("/sbin/userdel"): 258 | USERDEL = "/sbin/userdel" 259 | else: 260 | fail_json(msg="Cannot find userdel") 261 | 262 | argfile = sys.argv[1] 263 | args = open(argfile, 'r').read() 264 | items = shlex.split(args) 265 | syslog.openlog('ansible-%s' % os.path.basename(__file__)) 266 | log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", args) 267 | syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args) 268 | 269 | if not len(items): 270 | fail_json(msg='the module requires arguments -a') 271 | sys.exit(1) 272 | 273 | params = {} 274 | for x in items: 275 | (k, v) = x.split("=") 276 | params[k] = v 277 | 278 | state = params.get('state','present') 279 | name = params.get('name', None) 280 | uid = params.get('uid', None) 281 | group = params.get('group', None) 282 | groups = params.get('groups', None) 283 | comment = params.get('comment', None) 284 | home = params.get('home', None) 285 | shell = params.get('shell', None) 286 | password = params.get('password', None) 287 | 288 | # =========================================== 289 | # following options are specific to userdel 290 | force = params.get('force', 'no') 291 | remove = params.get('remove', 'no') 292 | 293 | # =========================================== 294 | # following options are specific to useradd 295 | createhome = params.get('createhome', 'yes') 296 | system = params.get('system', 'no') 297 | 298 | # =========================================== 299 | # following options are specific to usermod 300 | append = params.get('append', 'no') 301 | 302 | if state not in [ 'present', 'absent' ]: 303 | fail_json(msg='invalid state') 304 | if createhome not in [ 'yes', 'no' ]: 305 | fail_json(msg='invalid createhome') 306 | if system not in ['yes', 'no']: 307 | fail_json(msg='invalid system') 308 | if append not in [ 'yes', 'no' ]: 309 | fail_json(msg='invalid append') 310 | if force not in ['yes', 'no']: 311 | fail_json(msg="invalid option for force, requires yes or no (defaults to no)") 312 | if remove not in ['yes', 'no']: 313 | fail_json(msg="invalid option for remove, requires yes or no (defaults to no)") 314 | if name is None: 315 | fail_json(msg='name is required') 316 | 317 | rc = None 318 | out = '' 319 | err = '' 320 | result = {} 321 | result['name'] = name 322 | if state == 'absent': 323 | if user_exists(name): 324 | (rc, out, err) = user_del(name, force=force, remove=remove) 325 | if rc != 0: 326 | fail_json(name=name, msg=err) 327 | result['force'] = force 328 | result['remove'] = remove 329 | elif state == 'present': 330 | if not user_exists(name): 331 | (rc, out, err) = user_add(name, uid=uid, group=group, groups=groups, 332 | comment=comment, home=home, shell=shell, 333 | password=password, createhome=createhome, 334 | system=system) 335 | else: 336 | (rc, out, err) = user_mod(name, uid=uid, group=group, groups=groups, 337 | comment=comment, home=home, shell=shell, 338 | password=password, append=append) 339 | if rc is not None and rc != 0: 340 | fail_json(name=name, msg=err) 341 | if password is not None: 342 | result['password'] = 'NOTLOGGINGPASSWORD' 343 | 344 | if rc is None: 345 | result['changed'] = False 346 | else: 347 | result['changed'] = True 348 | if out: 349 | result['stdout'] = out 350 | if err: 351 | result['stderr'] = err 352 | exit_json(**result) 353 | sys.exit(0) 354 | -------------------------------------------------------------------------------- /ansible-dist/library/yum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -tt 2 | # (c) 2012, Red Hat, Inc 3 | # Written by Seth Vidal 4 | # 5 | # This file is part of Ansible 6 | # 7 | # Ansible is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Ansible is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Ansible. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import sys 24 | import yum 25 | import subprocess 26 | import datetime 27 | import shlex 28 | import re 29 | import traceback 30 | import syslog 31 | 32 | 33 | try: 34 | import json 35 | except ImportError: 36 | import simplejson as json 37 | 38 | 39 | def yum_base(conf_file=None, cachedir=False): 40 | my = yum.YumBase() 41 | my.preconf.debuglevel=0 42 | if conf_file and os.path.exists(conf_file): 43 | my.preconf.fn = conf_file 44 | if cachedir: 45 | if hasattr(my, 'setCacheDir'): 46 | my.setCacheDir() 47 | else: 48 | cachedir = yum.misc.getCacheDir() 49 | my.repos.setCacheDir(cachedir) 50 | my.conf.cache = 0 51 | 52 | return my 53 | 54 | def pkg_to_dict(po): 55 | d = { 56 | 'name':po.name, 57 | 'arch':po.arch, 58 | 'epoch':po.epoch, 59 | 'release':po.release, 60 | 'version':po.version, 61 | } 62 | 63 | if type(po) == yum.rpmsack.RPMInstalledPackage: 64 | d['yumstate'] = 'installed' 65 | d['repo'] = 'installed' 66 | else: 67 | d['yumstate'] = 'available' 68 | d['repo'] = po.repoid 69 | 70 | if hasattr(po, 'ui_from_repo'): 71 | d['repo'] = po.ui_from_repo 72 | 73 | if hasattr(po, 'ui_nevra'): 74 | d['_nevra'] = po.ui_nevra 75 | else: 76 | d['_nevra'] = '%s-%s-%s.%s' % (po.name, po.version, po.release, po.arch) 77 | 78 | 79 | 80 | return d 81 | 82 | def list_stuff(my, stuff): 83 | # FIXME - there are potential tracebacks that could occur here 84 | # need some more catching for them so we can see what happened 85 | if stuff == 'installed': 86 | return [ pkg_to_dict(po) for po in my.rpmdb ] 87 | elif stuff == 'updates': 88 | return [ pkg_to_dict(po) for 89 | po in my.doPackageLists(pkgnarrow='updates').updates ] 90 | elif stuff == 'available': 91 | return [ pkg_to_dict(po) for po in my.pkgSack ] 92 | elif stuff == 'repos': 93 | r = [] 94 | for repo in my.repos.repos.values(): 95 | t = {} 96 | s = 'disabled' 97 | if repo.enabled: 98 | s = 'enabled' 99 | t[repo.id] = s 100 | r.append(t) 101 | 102 | return r 103 | else: 104 | e,m,u = my.rpmdb.matchPackageNames([stuff]) 105 | p = e + m 106 | e,m,u = my.pkgSack.matchPackageNames([stuff]) 107 | p = p + e + m 108 | return [ pkg_to_dict(po) for po in p ] 109 | 110 | def run_yum(command): 111 | try: 112 | cmd = subprocess.Popen(command, shell=True, 113 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 114 | out, err = cmd.communicate() 115 | except (OSError, IOError), e: 116 | rc = 1 117 | err = str(e) 118 | out = '' 119 | except: 120 | rc = 1 121 | err = traceback.format_exc() 122 | out = '' 123 | 124 | if out is None: 125 | out = '' 126 | if err is None: 127 | err = '' 128 | else: 129 | rc = cmd.returncode 130 | 131 | return rc, out, err 132 | 133 | def ensure(my, state, pkgspec): 134 | yumconf = my.conf.config_file_path 135 | res = {} 136 | if state == 'installed': 137 | # check if pkgspec is installed 138 | if re.search('[> 2 | pkgname=ansible-git 3 | pkgver=20120419 4 | pkgrel=1 5 | pkgdesc="A radically simple deployment, model-driven configuration management, and command execution framework" 6 | arch=('any') 7 | url="https://github.com/ansible/ansible" 8 | license=('GPL3') 9 | depends=('python2' 'python2-yaml' 'python-paramiko>=1.7.7' 'python2-jinja' 'python-simplejson') 10 | makedepends=('git' 'asciidoc' 'fakeroot') 11 | 12 | _gitroot="https://github.com/ansible/ansible" 13 | _gitname="ansible" 14 | 15 | build() { 16 | cd "$srcdir" 17 | msg "Connecting to GIT server...." 18 | 19 | if [ -d $_gitname ] ; then 20 | cd $_gitname && git pull origin 21 | msg "The local files are updated." 22 | else 23 | git clone $_gitroot $_gitname 24 | fi 25 | 26 | msg "GIT checkout done or server timeout" 27 | 28 | cd "$srcdir/$_gitname" 29 | make 30 | } 31 | 32 | package() { 33 | cd "$srcdir/$_gitname" 34 | 35 | mkdir -p ${pkgdir}/usr/share/ansible 36 | cp ./library/* ${pkgdir}/usr/share/ansible/ 37 | python setup.py install -O1 --root=${pkgdir} 38 | 39 | install -D docs/man/man1/ansible.1 ${pkgdir}/usr/share/man/man1/ansible.1 40 | install -D docs/man/man1/ansible-playbook.1 ${pkgdir}/usr/share/man/man1/ansible-playbook.1 41 | 42 | gzip -9 ${pkgdir}/usr/share/man/man1/ansible.1 43 | gzip -9 ${pkgdir}/usr/share/man/man1/ansible-playbook.1 44 | } 45 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/README.txt: -------------------------------------------------------------------------------- 1 | I have added a debian folder for use in building a .deb file for ansible. From the ansible directory you can run the following command to construct a debian package of ansible. 2 | 3 | ~/ansible$ dpkg-buildpackage -us -uc -rfakeroot 4 | 5 | The debian package files will be placed in the ../ directory and can be installed with the following command: 6 | ~/$ sudo dpkg -i .deb 7 | 8 | Dpkg -i doesn't resolve dependencies, so if the previous command fails because of dependencies, you will need to run the following to install the dependencies (if needed) and then re-run the dpkg -i command to install the package: 9 | $ sudo apt-get -f install 10 | 11 | --Henry Graham 12 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/ansible.dirs: -------------------------------------------------------------------------------- 1 | etc/ansible 2 | usr/lib/python2.7/site-packages 3 | usr/share/ansible 4 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/ansible.install: -------------------------------------------------------------------------------- 1 | examples/hosts etc/ansible 2 | library/* usr/share/ansible 3 | docs/man/man1/ansible.1 usr/share/man/man1 4 | docs/man/man1/ansible-playbook.1 usr/share/man/man1 5 | bin/* usr/bin 6 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/changelog: -------------------------------------------------------------------------------- 1 | ansible (0.6) debian; urgency=low 2 | 3 | * 0.6 not released yet 4 | 5 | -- Michael DeHaan Wed, 04 Jul 2012 13:40:01 -0400 6 | 7 | ansible (0.5) debian; urgency=low 8 | 9 | * 0.5 update 10 | 11 | -- Michael DeHaan Wed, 04 Jul 2012 13:40:00 -0400 12 | 13 | ansible (0.4) debian; urgency=low 14 | 15 | * 0.4 update 16 | 17 | -- Michael DeHaan Wed, 23 May 2012 19:40:00 -0400 18 | 19 | ansible (0.3) debian; urgency=low 20 | 21 | * 0.3 update 22 | 23 | -- Michael DeHaan Mon, 23 Apr 2012 11:08:00 -0400 24 | 25 | ansible (0.0.2) debian; urgency=low 26 | 27 | * Initial Release 28 | 29 | -- Henry Graham (hzgraham) Tue, 17 Apr 2012 17:17:01 -0400 30 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/compat: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/control: -------------------------------------------------------------------------------- 1 | Source: ansible 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Henry Graham (hzgraham) 5 | Build-Depends: cdbs, debhelper (>= 5.0.0) 6 | Standards-Version: 3.9.1 7 | Homepage: http://ansible.github.com/ 8 | 9 | Package: ansible 10 | Architecture: all 11 | Depends: python, python-support (>= 0.90), python-jinja2, python-yaml, python-paramiko 12 | Description: Ansible Application 13 | Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH executing commands, running "modules", or executing larger 'playbooks' that can serve as a configuration management or deployment system. 14 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by Henry Graham (hzgraham) on 2 | Tue, 17 Apr 2012 12:19:47 -0400. 3 | 4 | It was downloaded from https://github.com/ansible/ansible.git 5 | 6 | Copyright: Henry Graham (hzgraham) 7 | 8 | License: 9 | 10 | This package is free software; you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation; version 2 dated June, 1991. 13 | 14 | This package is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this package; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 22 | USA. 23 | 24 | On Debian systems, the complete text of the GNU General 25 | Public License can be found in `/usr/share/common-licenses/GPL'. 26 | 27 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/pycompat: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /ansible-dist/packaging/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -- makefile -- 3 | 4 | include /usr/share/cdbs/1/rules/debhelper.mk 5 | DEB_PYTHON_SYSTEM = pysupport 6 | include /usr/share/cdbs/1/class/python-distutils.mk 7 | -------------------------------------------------------------------------------- /ansible-dist/packaging/gentoo/README.md: -------------------------------------------------------------------------------- 1 | Gentoo ebuilds are available here: 2 | 3 | https://github.com/uu/ubuilds 4 | -------------------------------------------------------------------------------- /ansible-dist/packaging/rpm/ansible.spec: -------------------------------------------------------------------------------- 1 | %if 0%{?rhel} <= 5 2 | %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} 3 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot 4 | %endif 5 | 6 | Name: ansible 7 | Release: 1%{?dist} 8 | Summary: Minimal SSH command and control 9 | Version: 0.6 10 | 11 | Group: Development/Libraries 12 | License: GPLv3 13 | Source0: https://github.com/downloads/ansible/ansible/%{name}-%{version}.tar.gz 14 | Url: http://ansible.github.com 15 | 16 | BuildArch: noarch 17 | BuildRequires: python2-devel 18 | 19 | Requires: PyYAML 20 | Requires: python-paramiko 21 | Requires: python-jinja2 22 | 23 | %description 24 | 25 | Ansible is a radically simple model-driven configuration management, 26 | multi-node deployment, and remote task execution system. Ansible works 27 | over SSH and does not require any software or daemons to be installed 28 | on remote nodes. Extension modules can be written in any language and 29 | are transferred to managed machines automatically. 30 | 31 | 32 | %prep 33 | %setup -q 34 | 35 | %build 36 | %{__python} setup.py build 37 | 38 | %install 39 | %{__python} setup.py install -O1 --root=$RPM_BUILD_ROOT 40 | mkdir -p $RPM_BUILD_ROOT/etc/ansible/ 41 | cp examples/hosts $RPM_BUILD_ROOT/etc/ansible/ 42 | mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1/ 43 | cp -v docs/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1/ 44 | mkdir -p $RPM_BUILD_ROOT/%{_datadir}/ansible 45 | cp -v library/* $RPM_BUILD_ROOT/%{_datadir}/ansible/ 46 | 47 | %clean 48 | rm -rf $RPM_BUILD_ROOT 49 | 50 | %files 51 | %defattr(-,root,root) 52 | %{python_sitelib}/ansible* 53 | %{_bindir}/ansible* 54 | %{_datadir}/ansible 55 | %config(noreplace) %{_sysconfdir}/ansible 56 | %doc README.md PKG-INFO 57 | %doc %{_mandir}/man1/ansible* 58 | 59 | 60 | %changelog 61 | * Wed Jul 4 2012 Michael DeHaan - 0.5-0 62 | - Not released yet 63 | 64 | * Wed May 23 2012 Michael DeHaan - 0.4-0 65 | - Release of 0.4 66 | 67 | * Mon Apr 23 2012 Michael DeHaan - 0.3-1 68 | - Release of 0.3 69 | 70 | * Tue Apr 3 2012 John Eckersberg - 0.0.2-1 71 | - Release of 0.0.2 72 | 73 | * Sat Mar 10 2012 - 0.0.1-1 74 | - Release of 0.0.1 75 | -------------------------------------------------------------------------------- /ansible-dist/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # NOTE: setup.py does NOT install the contents of the library dir 4 | # for you, you should go through "make install" or "make RPMs" 5 | # for that, or manually copy modules over. 6 | 7 | import os 8 | import sys 9 | 10 | sys.path.insert(0, os.path.abspath('lib')) 11 | from ansible import __version__, __author__ 12 | from distutils.core import setup 13 | 14 | setup(name='ansible', 15 | version=__version__, 16 | description='Minimal SSH command and control', 17 | author=__author__, 18 | author_email='michael.dehaan@gmail.com', 19 | url='http://ansible.github.com/', 20 | license='GPLv3', 21 | install_requires=['paramiko', 'jinja2', "PyYAML"], 22 | package_dir={ 'ansible': 'lib/ansible' }, 23 | packages=[ 24 | 'ansible', 25 | 'ansible.inventory', 26 | 'ansible.playbook', 27 | 'ansible.runner', 28 | 'ansible.runner.connection', 29 | ], 30 | scripts=[ 31 | 'bin/ansible', 32 | 'bin/ansible-playbook' 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /hosts: -------------------------------------------------------------------------------- 1 | # This same inventory file is deployed to each "pull"-type node 2 | 3 | [nodeonly] 4 | 127.0.0.1 5 | -------------------------------------------------------------------------------- /localhost.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: 127.0.0.1 3 | connection: local 4 | vars: 5 | author: JP Mens 6 | tasks: 7 | - name: Create JANED 8 | action: user name=janed comment="Jane Doe" shell=/bin/bash createhome=yes home=/home/janed 9 | - include: node/mutt.yaml muttuser=janed 10 | - name: Create config file 11 | action: template src=node/templates/whazzup.in dest=/tmp/whazzup 12 | 13 | -------------------------------------------------------------------------------- /node-runner.cf: -------------------------------------------------------------------------------- 1 | #(@)node-runner.cf (C)2012 by Jan-Piet Mens 2 | # This shell script is sourced by node-runner.sh 3 | 4 | # Default playbook name; can be overriden on a per/node basis 5 | 6 | playbook=localhost.yaml 7 | case `hostname -s` in 8 | b1) 9 | playbook=specialnode.yaml 10 | ;; 11 | esac 12 | 13 | -------------------------------------------------------------------------------- /node-runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | NODE_RUNNER_HOME=/etc/ansible 6 | ANSIBLE_DIST=/etc/ansible/ansible-dist # Full path to dir containing Ansible code 7 | 8 | function bailout() { 9 | echo "node-runner: $*" >&2 10 | exit 2 11 | } 12 | 13 | git=/usr/bin/git 14 | 15 | 16 | if [ -d "$NODE_RUNNER_HOME" ]; then 17 | source $NODE_RUNNER_HOME/node-runner.cf 18 | else 19 | echo "$0: \$NODE_RUNNER_HOME is not a directory." >&2 20 | exit 1 21 | fi 22 | 23 | if [ -d "$ANSIBLE_DIST" ]; then 24 | 25 | cd $ANSIBLE_DIST 26 | source hacking/env-setup > /dev/null 27 | else 28 | echo "$0: \$ANSIBLE_DIST is not a directory." >&2 29 | exit 1 30 | fi 31 | 32 | [ -x $git ] || bailout "Can't find $git or is not executable" 33 | 34 | cd $NODE_RUNNER_HOME 35 | $git pull --quiet 36 | 37 | # Re-read our config, as it may have changed after pull 38 | 39 | source $NODE_RUNNER_HOME/node-runner.cf 40 | 41 | # todo: maybe check md5sum of node-runner.sh before and after pull to 42 | # decide if we want to re-exec node-runner.sh :) 43 | 44 | 45 | # Run the playbook 46 | 47 | ansible-playbook ${playbook} 48 | -------------------------------------------------------------------------------- /node/mutt.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configure Mutt for $muttuser 3 | 4 | - name: Install Mutt 5 | action: yum pkg=mutt state=installed 6 | - name: dot mutt 7 | action: file path=/home/$muttuser/.mutt state=directory owner=$muttuser 8 | - name: Muttconfig 9 | action: template src=node/templates/mutt.rc.in dest=/home/$muttuser/.muttrc owner=$muttuser mode=0600 10 | -------------------------------------------------------------------------------- /node/templates/mutt.rc.in: -------------------------------------------------------------------------------- 1 | # Expects password to be in $MUTTPW before launching Mutt 2 | # just an example. 3 | 4 | set folder = "imaps://imap.gmail.com:993" 5 | set imap_user = "{{ muttuser }}" 6 | set imap_pass = $MUTTPW 7 | set spoolfile = "+INBOX" 8 | set postponed="+[Gmail]/Drafts" 9 | 10 | set smtp_url = "smtp://{{ muttuser }}@smtp.gmail.com:587/" 11 | set smtp_pass = $MUTTPW 12 | set from = "$muttuser@gmail.com" 13 | 14 | set header_cache=”~/.mutt/cache/headers” 15 | set message_cachedir=”~/.mutt/cache/bodies” 16 | set certificate_file=~/.mutt/certificates 17 | -------------------------------------------------------------------------------- /node/templates/whazzup.in: -------------------------------------------------------------------------------- 1 | #(@)installed by Ansible 2 | # 3 | # configured by {{ author }} 4 | 5 | hostname: {{ ansible_fqdn }} 6 | IP address: {{ ansible_eth0.ipv4.address }} 7 | --------------------------------------------------------------------------------