Compare commits

..

1212 Commits
0.01 ... 0.6

Author SHA1 Message Date
53c927ff26 changelog dates 2012-08-06 21:17:58 -04:00
08c1f96e4b Fix some subtle behavior of how the get_url module chains into the file module, plus blend the results of daisy chain
operations in the correct order, so the file module results overlay the original module results, not the other way
around (which keeps any failure msg's intact)
2012-08-03 07:02:31 -04:00
7e9e29011e Add encoding lines to python modules such that they can take unicode options if they are fed them, since the
AnsibleModule stuff no longer base64 encodes for simplicity and speed reasons.
2012-08-02 21:29:10 -04:00
ce01c3f7e7 Allow unicode transfer by not base64 encoding. Also: faster 2012-08-02 21:20:43 -04:00
227dfc17f3 fix tree output 2012-08-02 20:42:32 -04:00
05df6464ef Make module args as unicode more or less not choke. 2012-08-02 20:29:18 -04:00
b76efa39be Fix some templating issues, needs testing with anti-unicode safeguard around shlex.split 2012-08-02 20:21:59 -04:00
bd7de28a64 Merge pull request #778 from skvidal/devel
remove the tab I found
2012-08-02 16:54:42 -07:00
4d29cd6ee3 make virt module command= work again - better error handling should be in its
future
2012-08-02 17:46:21 -04:00
f924c96370 remove the tab I found
DIE TAB DIE
pep8
2012-08-02 17:26:04 -04:00
0394ef776f Merge pull request #776 from skvidal/devel
- add a check for repoquery so we can abort politely
2012-08-02 14:06:30 -07:00
fd492bebc6 - add a check for repoquery so we can abort politely
- make the results output a bit more readable
- fix up where repoquery is looked for so it is easier to change
2012-08-02 17:02:37 -04:00
98e4034de2 Merge pull request #772 from gottwald/devel-serviceFix
Fixed a Bug in the service module "state changed" recognition
2012-08-02 08:49:30 -07:00
45d2c90169 Merge pull request #771 from sfromm/issue765
Add Blade to FORM_FACTOR list and document SMBIOS spec URL
2012-08-02 08:48:49 -07:00
25b52109b4 Fixed a Bug in the service module state changed recognition 2012-08-02 17:18:25 +02:00
85c838c835 Add Blade to FORM_FACTOR list and document SMBIOS spec URL 2012-08-02 07:24:05 -07:00
9a3ad060dc Catch exception if form factor id is unknown 2012-08-02 07:06:55 -07:00
38ed7ca7df Merge pull request #759 from lorin/error-feedback
If task action is invalid, output the name in error message
2012-08-02 05:39:53 -07:00
aed6ee7f74 Report only name in task error, all on one line 2012-08-02 08:37:43 -04:00
fa9fe9d246 If task action is invalid, output the name and action. 2012-08-01 23:51:25 -04:00
c192d8a2b2 Revert "Apply templating to module arguments in playbook"
Breaks copy module by introducing unicodeness, among other things

This reverts commit 4fedb17e38.
2012-08-01 23:07:15 -04:00
a015c2e581 Merge pull request #767 from sfromm/issue765
Catch exception if form factor id is unknown
2012-08-02 07:08:36 -07:00
fa79d57d13 changelog / remove a failed=False 2012-08-01 22:35:07 -04:00
b7c3fd1246 failed should not be in the result returns for a module upon success 2012-08-01 22:30:22 -04:00
fe87a16a3e Make yum module use one-pass with with_items 2012-08-01 22:27:25 -04:00
91d31d8164 Merge pull request #761 from skvidal/devel
redo of yum module - uses more system calls but should handle
2012-08-02 05:13:23 -07:00
c91befda5c redo of yum module - uses more system calls but should handle
random plugin vomit better.
also should handle multiple withitems in a single execution, now.
2012-08-02 01:47:48 -04:00
4fedb17e38 Apply templating to module arguments in playbook
Module arguments should have variable substitution applied
Also, replacing task.action with the templated task.name seems wrong
2012-08-01 22:22:46 -04:00
e820c282be Merge pull request #760 from sfromm/fixes
A couple fixes for the file module
2012-08-02 04:54:16 -07:00
f092a69d77 Merge pull request #763 from willthames/sudo_missing_user
Raise exception when sudo user does not exist
2012-08-02 04:50:06 -07:00
9e0bf168ca Merge pull request #764 from willthames/fail_copy_on_directory
Revert acceptance of directory as dest for copy
2012-08-02 04:49:17 -07:00
0e26ef9bdd Revert acceptance of directory as dest for copy
Although library/copy can be corrected to understand that dest is
a directory, I can't see how to let _execute_copy know this and let the
file module know.

As a better solution than before #733, the copy module now explicitly (rather
than silently) fails when dest is a directory.
2012-08-02 16:54:03 +10:00
d43d81a9a4 Raise exception when sudo user does not exist
sudo can fail before getting to the password prompt if a user does not exist
<pre>
[will@tangerine ansible (sudo_missing_user)]$ sudo -u bobbins echo hello
sudo: unknown user: bobbins
sudo: unable to initialize policy plugin
</pre>
Fix raises a distinct exception when that occurs
2012-08-02 16:19:15 +10:00
d0892c19ad Add setype to file's argument_spec and remove secontext
secontext is used internally and should not be in argument_spec
2012-08-01 22:24:09 -07:00
52afacf475 Fix module_fail_json call when symlink src does not exist 2012-08-01 22:23:02 -07:00
bf25193ec1 changelog updates 2012-08-01 20:34:39 -04:00
25acfa8170 Join with_items for the apt module, yum pending, and various fixes to the yum module to support aliases 2012-08-01 20:33:13 -04:00
86ec639176 add error handling if you try to load files in the wrong data format 2012-08-01 19:51:52 -04:00
08c5fe875b yell if input data for host or group vars are not hashes 2012-08-01 19:46:33 -04:00
ed14312ad6 reinstate invalid argument checks where possible, daisy chainee/chained modules turn it off 2012-08-01 19:42:31 -04:00
08999a2a2f Merge pull request #748 from dagwieers/make-rpm
Implement git commit date into RPM release
2012-08-01 16:29:16 -07:00
3ff03d14e3 Merge pull request #751 from Ernest0x/ignore_errors_task_option
added an 'ignore_errors' option to tasks
2012-08-01 16:28:35 -07:00
77029a4227 Merge pull request #752 from go2sh/apt-patch
Enable apt module to handle multiply packages (comma separated)
2012-08-01 16:27:29 -07:00
128a606a59 Merge pull request #756 from sfromm/issue755
Issue755 - fixes for service module
2012-08-01 16:27:13 -07:00
53c62f2655 Merge pull request #757 from pas256/devel
Smiting Debian packaging instructions that did not work
2012-08-01 16:26:50 -07:00
b1b543c289 Smiting Debian packaging instructions that did not work, and replacing
them with the correct instructions. Changing file to .md to match 'docs'
file.
2012-08-01 16:12:10 -07:00
abe8d8d4d6 Return extra information from service module
If _do_enable() is run and returns rc == 0, set changed=True
Add enabled and state to result if they are supplied when the module is
run.
2012-08-01 14:58:32 -07:00
42ad1c879f Fix service module for issue 755 and another bug
Allow use of service module with just enable parameter, per issue #755.
Also fixed two other issues:
- fixed parameter to be 'enabled' per docs, not 'enable'.
- fixed if block that checks whether to run _do_enable() to check
  whether the parameter is set, not the value of the enable value which
  may be None or False.  If enabled=no, the service would never be
  disabled.
2012-08-01 14:44:26 -07:00
2ac4acbf97 fixes for tests 2012-08-01 22:17:16 +03:00
6baaea90ae Tweaked error massages. 2012-08-01 18:45:32 +02:00
b973910283 Some optimizations to previous code. 2012-08-01 18:43:39 +02:00
5f4bf813b1 added an 'ignore_errors' option to tasks
Failed tasks with ignore_errors=True, allow the remaining tasks in the
play to be executed as if no failure happened. A failure notice is still
written to the output as well as an '...ignoring' message.
2012-08-01 19:13:07 +03:00
418445d3c5 Enable apt module to handle multiply packages (comma separated) 2012-08-01 18:09:30 +02:00
2d9ceafbd4 Merge pull request #749 from jpmens/get_urlcomma
get_url except fix
2012-08-01 06:39:01 -07:00
f60517aa33 temporarily disable failing check on some things 2012-08-01 09:33:20 -04:00
31c0e523c2 Fix exeptions, replace 'as' by ',' as reported on IRC for Python 2.4.3 on Centos5 2012-08-01 15:30:16 +02:00
6b774f8cf5 Place the freshly built RPM directly into rpm-build/
So we get the exact behavior as the SRPM build
2012-08-01 15:25:19 +02:00
ec7c8eb8ca Implements git commit date into RPM release
With this patch one can do `make rpm` and get an RPM file that looks like:

  rpm-build/noarch/ansible-0.6-0.git201208010541.el6.noarch.rpm

My goal was not to rewrite the original SPEC file, and/or the tarball. In other
projects what I tend to do is prepare the SPEC file in the tarball with the
correct version/release so that rpmbuild works on the (released) tarball as
well. If this is wanted, we will have to rewrite the SPEC file on the fly as
part of creating the tarball. If there is no git or .git/ available, we will
use the current time.
2012-08-01 14:58:02 +02:00
cfea9c54bb Update README.md 2012-08-01 09:16:21 -03:00
0a3ebdb6d5 Module consistency and make daisy chaining work with invalid arguments detection. 2012-08-01 00:22:02 -04:00
db9955d3df Merge pull request #743 from akhayyat/devel
Allow = symbols in variable values in host inventory
2012-07-31 20:41:31 -07:00
6728b6cdda Allow = symbols in variable values in host inventory 2012-07-31 23:36:26 -04:00
7d2877fbf9 Merge pull request #742 from willthames/fix_test_one
fixing test_one
2012-07-31 20:32:59 -07:00
845a7ac5ae test_one not working as tests after skipped test failing, because
on_skipped callback being passed an extra item parameter that it wasn't
expecting.
Fixed it so that on_skipped in TestCallbacks accepts and ignores the
extra parameter
Extra parameter was added in 4b9b9a8a5b
but not really clear why from commit message
2012-08-01 12:35:17 +10:00
8700de964c Teach the common module code to warn users about typo'd arguments and also set everything to None automatically such
that code doesn't have to do a lot of params.get('foo', None) everywhere.
2012-07-31 21:23:34 -04:00
190d72761f Reformat changelog so it's easier to tell what is new 2012-07-31 21:00:55 -04:00
dc3e142486 Merge pull request #741 from tima/low-level-stderr
concatenating stdout and stderr in runner/__init__'s _low_level_exec_command
2012-07-31 17:32:29 -07:00
b30f4688cd concatenating stdout and stderr in runner/__init__'s _low_level_exec_command. 2012-07-31 16:59:45 -04:00
9f7ad62376 Merge pull request #739 from lorin/apt-add-alias
Apt module: add update-cache as alias of update_cache
2012-07-31 07:11:45 -07:00
9bcc18d17b Apt module: add update-cache as alias of update_cache
This will ensure users of previous versions of this module don't
have their playbooks break.
2012-07-31 09:56:36 -04:00
d79900b434 Merge pull request #732 from willthames/fix_git_fail_json
Fixes for git module when it fails.
2012-07-31 05:04:47 -07:00
89ef236e9c Merge pull request #733 from willthames/copy_to_directory
Allow copy dest argument to be a directory
2012-07-31 05:03:50 -07:00
e6d898cd17 Merge pull request #734 from willthames/local_sudo
Use sudo_user as an argument in the sudo command
2012-07-31 05:03:12 -07:00
a8d13fe40d Merge pull request #735 from nix85/fix_ohai_mod
Changed funtion name to get_ohai_data
2012-07-31 05:02:44 -07:00
7538110036 Merge pull request #736 from Ernest0x/db_create_extra_args
added extra arguments for database creation
2012-07-31 05:02:20 -07:00
24c1d32120 added extra arguments for database creation
mysql_db: added 'encoding' and 'collation' args
postgresql_db: added 'owner', 'template' and 'encoding' args
2012-07-31 12:56:29 +03:00
526a0b1f60 Changed funtion name to get_ohai_data 2012-07-31 13:07:53 +05:30
7e279f972e Use sudo_user as an argument in the sudo command 2012-07-31 16:21:24 +10:00
3cb51acd78 Allow copy dest argument to be a directory
Could have used shutil.copy rather than shutil.copyfile, but this
implementation preserves the md5 comparison to avoid unnecessary copies
2012-07-31 14:47:01 +10:00
e863ba0cec Fixes for git module when it fails.
* module.fail_json *must* have msg argument
Using http://github.com/ rather than git://github.com/ as it gets through more
firewalls
2012-07-31 12:59:22 +10:00
ce5f3dd148 Port the file module over to the new common code infrastructure + cleanup some redundant imports since the module code already imports those things. 2012-07-30 21:50:32 -04:00
8e60ad986a Do not log login_password params to DB module 2012-07-30 21:21:30 -04:00
d0f6410326 Porting the virt module to new module core. 2012-07-30 21:16:29 -04:00
da44fb1e43 Fixes #731 by reinstating variables for inventory scripts 2012-07-30 19:26:46 -04:00
fe558f680a Fix some failing tests, script tests seem to indicate magic variables are not being provided
to scripted inventory so need to investigate this.
2012-07-30 19:21:49 -04:00
6b72804cb4 remove unneeded imports 2012-07-28 17:15:07 -04:00
d0a5dec686 Port setup module to use the common module base 2012-07-28 17:14:23 -04:00
d79ba6f2aa Port the ohai module over, while this is actually *longer* now, not transferring the args file makes this much
faster.
2012-07-28 17:07:55 -04:00
5fd2018117 Convert authorized_key modules to use new common code + misc style things 2012-07-28 17:03:10 -04:00
63459110c4 Merge pull request #730 from marktheunissen/mysql_change_params
Changing the parameter names to be consistent, and adding root user
2012-07-30 15:50:40 -07:00
9a1265b856 Changing the parameter names to be consistent, and adding root user default with empty password, as per mysql standard 2012-07-30 17:15:24 -05:00
af3b0e3b82 Merge pull request #728 from pas256/devel
Making inventory pattern matching handle group names
2012-07-30 13:42:24 -07:00
47cead3603 Merge pull request #727 from sfromm/git
Git module ported to use module magic
2012-07-30 13:40:36 -07:00
0188889aea Making inventory pattern matching handle group names 2012-07-30 12:51:29 -07:00
d0f0315c9d Merge pull request #723 from skvidal/devel
fix trace-creating error in apt module - also make the default setting a...
2012-07-30 10:45:42 -07:00
21a35bde00 new patch - adds a 'boolean' function to the module_common class and cleans up
the apt module to use it
2012-07-30 13:41:42 -04:00
3ad07d5965 Merge pull request #724 from goozbach/inventory_short
added inventory_hostname_short varible
2012-07-30 10:37:15 -07:00
547395a2be added inventory_hostname_short varible 2012-07-30 13:26:48 -04:00
c4c53d545a fix trace-creating error in apt module - also make the default setting actualy work 2012-07-30 13:07:19 -04:00
8f9e136ca0 Merge pull request #721 from Ernest0x/command_change_cwd
added a 'chdir' argument to the command module
2012-07-30 09:02:23 -07:00
4ad0ff61e5 added a 'chdir' argument to the command module
the 'chdir' argument changes the current working directory to the
fullpath supplied as its value, before the execution of the command.
2012-07-30 18:39:45 +03:00
b7da73b73f Merge pull request #715 from willthames/showversion
Output version string when --version is passed using optparse's version ...
2012-07-30 04:56:30 -07:00
238524ccc6 Merge pull request #714 from willthames/envsetup
env-setup no longer needs to be run from the ansible directory, it can b...
2012-07-30 04:55:49 -07:00
45bf88b78c Merge pull request #717 from willthames/fix_osx_tests3
Fixes for OS X tests
2012-07-30 04:54:48 -07:00
1bcba4f4a2 test/TestRunner.py now executes false with no directory prefix
as it is /bin/false on many systems but /usr/bin/false on OS X
test/playbook1.yml now just does command true, rather than command /bin/true,
again so that it works on OS X

Changed from using which false to just using command false to
make it simpler and also match how playbook1.yml works
2012-07-30 21:39:58 +10:00
4d4d0b90c5 readlink -f does not work on OS X. Replaced with a python solution 2012-07-30 21:35:24 +10:00
90bf67cf56 Make test_git functional in TestRunner.py 2012-07-29 22:40:12 -07:00
87b3e1ecdc Update git module to use module magic and other changes
Convert git module to module magic.
Drop cruft no longer needed.
Standardize indent to 4 spaces in methods switchLocalBranch, reset,
clone, and get_version.
Update is_local_branch to also handle '* branch' format.
Add is_current branch method()
Update pull() method to use is_current_branch()
2012-07-29 22:39:19 -07:00
ad9b8e2312 Output version string when --version is passed using optparse's version argument 2012-07-30 14:19:02 +10:00
6219fc4932 env-setup no longer needs to be run from the ansible directory, it can be run from anywhere
env-setup now takes a -q argument to run in quiet mode
2012-07-30 13:18:28 +10:00
f5e08158ca Merge pull request #713 from lorin/postgres-fixup
Rename: postgresql.yaml -> postgresql.yml
2012-07-29 12:35:12 -07:00
5a109154f1 Rename: postgresql.yaml -> postgresql.yml 2012-07-29 13:52:30 -04:00
b3aba405dd Merge pull request #712 from lorin/postgres-fixup
Postgres fixup
2012-07-29 10:28:50 -07:00
dcd214a631 Use standard argument names in PostgreSQL modules
passwd -> password
loginpass -> login_password
loginuser -> login_user
loginhost -> login_host

Add an example playbook that shows how to use the modules.
2012-07-29 12:52:14 -04:00
a9c2e597ac Fix typo in apt error message: update-cache -> update_cache
The apt parameter changed from update-cache to update_cache, but
the error message still mentioned update-cache
2012-07-29 12:29:00 -04:00
ff82f0a168 Further service module tweaks 2012-07-28 16:48:04 -04:00
e9c4eb36d1 Service tweaks WIP 2012-07-28 16:39:18 -04:00
aca860df9c Standardizing the module 2012-07-28 16:39:18 -04:00
61780e0980 Merge pull request #729 from marktheunissen/fix-apt
Fixes for apt module
2012-07-30 15:37:16 -07:00
7a67145ef4 Fixes for apt module 2012-07-30 17:20:43 -05:00
487c826700 Tweak alias consistency a bit. 'package' has never been documented, but since it works, make it work in both places.
'name' is the preferred form.  Similarly, take 'name' for the 'guest' argument to the 'virt' module.
2012-07-28 09:22:13 -04:00
4d9ecebc20 update changelog 2012-07-28 08:50:19 -04:00
17be5537d6 When sudoing to another user, the umask may be set such that the next user might not be able to read/execute
the file.  Deal with this.
2012-07-28 08:48:05 -04:00
fcdc281c28 If sudoing to a user other than root, the home directory of the original user is very unlikely to be readable by the
sudoed to user, so just use tmp.  Data will be readable by others (briefly before deletion) but never writeable.
2012-07-27 20:53:50 -04:00
aa40c5f3b3 remove commented out debug lines 2012-07-27 20:44:55 -04:00
1e464d6c4b update changelog 2012-07-27 19:03:12 -04:00
ee5f12cd40 Slight tweaks on vars_prompt upgrades 2012-07-27 19:00:48 -04:00
c717934b7e Improved 'vars_prompt' syntax to support prompt text and (non-)private input
An example of the new syntax:

vars_prompt:
 - name: 'secret_variable_name"
   prompt: "Enter secret value: "
   private: "yes"
 - name: "nonsecret_variable_name"
   prompt: "Enter non-secret value: "
   private: "no"
2012-07-27 19:00:48 -04:00
4ecdd17caf Merge pull request #702 from lorin/postgresql-module
Add postgresql_db and postgresql_user module.
2012-07-27 15:32:10 -07:00
d55012e570 Merge pull request #704 from lorin/gitfix
git module: Don't pass rc as separate arg to fail_json
2012-07-27 05:49:22 -07:00
9253d89280 git module: Don't pass rc as separate arg to fail_json 2012-07-27 08:40:47 -04:00
0c61d049a2 Merge pull request #690 from marktheunissen/mysql_default_password
Change modules to use credentials in my.cnf if they are available
2012-07-26 17:16:05 -07:00
ebfd7ec7e3 Merge pull request #695 from nix85/fix_apt_mod
Standardizing the apt module
2012-07-26 17:15:28 -07:00
def1fa23f8 Add postgresql_db and postgresql_user module.
These modules are based on the mysql_db and mysql_user modules.

Currently, the postgresql_user module can only grant all permissions
on a database, fine-grained access has not been implemented yet.
2012-07-26 17:05:04 -04:00
7395becf3a Check file exists instead of relying on the exception. Leave the exception catch in, in case the file is deleted or some other issue crops up 2012-07-26 11:30:22 -05:00
888ac86d70 Woops, missed the rest of them 2012-07-26 11:13:10 -05:00
ac23c69bcc Making the passwd parameter consistent 2012-07-26 11:10:22 -05:00
6afe3fd497 Changing to read from a file pointer instead so that an exception is thrown if the file doesn't exist 2012-07-26 08:58:21 -05:00
daf44331c4 Code review changes
1. Passing the module to the various functions so that they can use module.fail_json and module.exit_json methods inside.
2. Because of point 1, install and remove methods do not return anything. Instead, they use the module functions itself.
3. Move the import statement (for apt and apt_pkg) inside main function so on import error, we can use module.fail_json to print the error.
2012-07-26 17:29:15 +05:30
b3b12b5e41 Merge pull request #694 from sfromm/git
Update git module to handle branches better
2012-07-26 04:03:11 -07:00
dd9e09dee6 Adding dict() for json.dumps 2012-07-26 16:24:10 +05:30
4c8d949642 Standardizing the apt module 2012-07-26 16:21:49 +05:30
1727bd3b7a Update git module to handle branches better
This drops the branch option.  The version option is overloaded
to mean either a sha1, branch, or tag.  This also adds the option
'remote' which defaults to 'origin'.
clone() was simplified by removing the checkout operation.  That
happens later when switch_version() is called.

Added the methods get_branches(), is_remote_branch(), and
is_local_branch().  get_branches() returns an array listing all
of the branches for the git repository.  is_remote_branch() checks
whether the arguments supplied correspond to a remote branch.
Similarly, is_local_branch() checks for a local branch.

The pull() method now checks to see if it is on the desired branch.
If not, it checks out the requested branch and then does a pull.
This should keep issue #604 still fixed.

switch_version(), formerly switchver(), looks to see if it is
checking out a branch.  If a branch, it checks it out with the --track
option.  This type of checkout was in pull() before.

Updated pull, clone, and switch_version to return (rc, out, err).
2012-07-26 00:22:30 -07:00
a65b4fef97 Fix up chain invocations of template 2012-07-26 00:39:44 -04:00
19fc8eea80 Things that happen inside multiprocessing shouldn't make side effects, ergo append ansible_facts only
in playbook code.

This technically means with_items calls can't continuously add facts I guess, but I can't see anyone
ever trying that.
2012-07-25 21:30:49 -04:00
51739fc47d update changelog 2012-07-25 21:05:01 -04:00
da4562f495 changes to not clear the setup cache between runs, and also decide to run plays with no tasks
in them if it looks like they are not tagged, because if that is the case, then we may just
be gathering facts about them.
2012-07-25 20:45:51 -04:00
f371092845 rm stray comment 2012-07-25 20:31:57 -04:00
e035ce97ce blue on black, tears on a river, push on a shove, it don't mean much
(make colorized playbook runs not suck on black terminals)
2012-07-25 20:31:26 -04:00
2030f82bf2 Further standardize the yum module 2012-07-25 20:14:15 -04:00
1584eda3f8 Merge pull request #684 from nix85/fix_yum_mod
Standardizing the yum module
2012-07-25 17:33:55 -07:00
12979cf834 Change modules to use credentials in my.cnf if they are available 2012-07-25 16:31:12 -05:00
fcd02e45a2 Merge pull request #686 from sfromm/fixassemble
More cleanup in assemble module for module magic (tm)
2012-07-25 11:47:17 -07:00
101c4084f2 Merge pull request #687 from lorin/save-name-args
Save module name and args when processing list of items.
2012-07-25 11:46:23 -07:00
b63a3bd0c7 Save module name and args when processing list of items.
This is necessary because daisy-chained modules will overwrite
these values.
2012-07-25 13:26:07 -04:00
40eaf282ea More cleanup in assemble module for module magic (tm) 2012-07-25 09:24:10 -07:00
b47bed9601 Standardizing the yum module 2012-07-25 19:45:41 +05:30
8d7f526df6 importing/not-importing methods should be colorized 2012-07-24 20:41:24 -04:00
d3e8e8ff54 Fix output message 2012-07-24 20:37:27 -04:00
c16b691883 Copy module needs to use src as transfer path. 2012-07-24 20:35:10 -04:00
263432daa1 Changed no hosts to red. Not sure this is the right color choice, but there is no orange :) 2012-07-24 20:21:49 -04:00
82cca242e3 use AnsibleError so that if there is an error it does not print a traceback 2012-07-24 20:16:35 -04:00
8fa4dc3920 Small change to previous patch, make ranges of hosts inclusive. 2012-07-24 20:10:05 -04:00
416b8d59a9 Merge branch 'devel' of https://github.com/fangchin/ansible into devel 2012-07-24 19:57:51 -04:00
6a0326b6d0 changelog update 2012-07-24 19:57:21 -04:00
15062b38e1 Fix typo 2012-07-24 19:50:16 -04:00
772e241d1f shorten up the assemble module 2012-07-24 19:44:53 -04:00
f787cda7be Merge pull request #675 from jpmens/assemble1
convert sfromm's assemble to module-magic
2012-07-24 16:41:25 -07:00
12ff9b5b9a Move color coding bits to module, use over rest of playbook 2012-07-24 19:30:02 -04:00
f4a4649086 Port async_status to use the new common code. 2012-07-24 19:03:18 -04:00
45354c6be5 Port command module over to new common code. Notice that this has to subclass AnsibleModule -- this should be the only
one that has to do that.
2012-07-24 18:52:52 -04:00
5315dd146d Added lib/ansible/inventory/expand_hosts.py 2012-07-24 12:43:35 -07:00
e3b2521f01 Added a host expansion feature to ansible's inventory parsing 2012-07-24 12:38:52 -07:00
2d1c297fb8 Merge pull request #674 from nix85/fix_facter_mod
Standardizing the module
2012-07-24 11:54:50 -07:00
f6fe9124b8 convert assemble to module-magic 2012-07-24 18:35:06 +02:00
8bbcef6f13 Standardizing the module 2012-07-24 21:08:56 +05:30
bbaa2048c8 Merge pull request #673 from jpmens/copy_file
file mode now correctly set after chaining from copy
2012-07-24 07:48:17 -07:00
44a9226ece file mode now correctly set after chaining from copy 2012-07-24 16:43:36 +02:00
c2a63a5ce3 Merge pull request #672 from marktheunissen/fix-user-module
Undefined variable error
2012-07-24 07:32:26 -07:00
97cc259d8c Undefined variable error 2012-07-24 09:26:45 -05:00
fef53b1a9e Merge pull request #670 from jpmens/color1
Support for colors in ansible-playbook
2012-07-24 05:50:36 -07:00
bc17553d1c Support for colors in ansible-playbook 2012-07-24 14:39:45 +02:00
432dae769e Merge pull request #669 from nix85/fix_mount_mod
Standardizing the mount module
2012-07-24 04:40:26 -07:00
483f7fd625 Standardizing the mount module 2012-07-24 17:00:08 +05:30
3a2df329af Tweak the way chained callbacks work 2012-07-23 22:12:26 -04:00
dd6399b5ae Fixup args being passed into file from copy, tweak preservation of changed attribute. 2012-07-23 21:56:21 -04:00
2cda36f7dc Merge pull request #664 from jpmens/a1
Fix file module to support = in args; remove superflous call in get_url
2012-07-24 03:39:30 -07:00
e4afa83b82 Merge pull request #667 from go2sh/issue-666
Fixes locale problem with ifconfig in setup module
2012-07-24 03:37:53 -07:00
ab8a36686e Fixes locale problem with ifconfig in setup module 2012-07-24 11:10:11 +02:00
628b289ab3 Fix file module to support = in args; remove superflous call in get_url 2012-07-24 06:40:34 +02:00
153f3932b5 reinstate delete_remote_files 2012-07-23 21:30:09 -04:00
632a83c79e Remove unused function 2012-07-23 21:28:18 -04:00
479f8b8c26 Templating neccessary here. 2012-07-23 21:10:16 -04:00
fdf14ec7ab fix module name in examples 2012-07-23 21:06:24 -04:00
9b8793e42e Fix a glitch with daisy chain logic 2012-07-23 21:01:59 -04:00
183330a9fa Merge pull request #662 from tima/regex-cond-tasks
Importing re in runner for doing conditional tasks using pattern matching i.e. re.search(). Also conditionally outputting item when skipping.
2012-07-23 17:52:52 -07:00
cbea17e4c5 Fix up async 2012-07-23 20:46:35 -04:00
cac7afd700 async is under construction 2012-07-23 20:06:18 -04:00
1f44c07ddf Fix assemble usage, some issues still with async. 2012-07-23 19:40:21 -04:00
4b9b9a8a5b Importing re in runner for doing conditional tasks using pattern matching i.e. re.search(). Also conditionally outputting item when skipping. 2012-07-23 19:36:26 -04:00
220ab33da3 changelog update 2012-07-23 19:34:18 -04:00
738cea9c93 small tweaks to --help for hacking/test-module 2012-07-23 19:33:26 -04:00
fc96b88205 Teach the test-module script about the new way MODULE_ARGS works in new-style modules. 2012-07-23 19:28:43 -04:00
ec12cc4154 Save the transfer of the module file for new style modules, because we can inject the arguments into the modules.
Module consumers using the API don't have to know how this works.  base64 stuff is only there
because escaping a docstring inside a docstring was a bit of a challenge :)
2012-07-23 19:18:45 -04:00
f8520750cb Merge pull request #659 from bradobro/test_module_optparse
#658
2012-07-23 16:16:55 -07:00
6448deb0b1 Merge pull request #660 from sfromm/newuser
Update user module to use new shared module code
2012-07-23 13:39:37 -07:00
f068bedfa1 Update user module to use new shared module code 2012-07-23 11:29:59 -07:00
e8583833a7 test-module uses optparse with --debugger
Refactored hacking/test-module

1. uses optparse

2. has --debugger option

tested only with pdb on Python 2.7
2012-07-23 16:28:06 +00:00
ade0233d57 Refactoring test-module to be more like ansible. 2012-07-23 16:04:28 +00:00
08ece6c54a update changelog 2012-07-22 11:56:18 -04:00
617f9dc942 Added some caching logic to improve external inventory scripts, removed some debug statements. 2012-07-22 11:53:19 -04:00
57f12ac9e3 Using __slots__ in more places, in particular, hosts and groups, where we are apt to create a fair amount of objects. 2012-07-22 11:40:02 -04:00
84e1d21b9c Merge pull request #651 from marktheunissen/mysql_user_upgrade
Upgrading MySQL user module to new format
2012-07-22 11:19:41 -07:00
12e23a1a6c Upgrading MySQL user module to new format 2012-07-22 12:11:39 -05:00
0b891fc8fb Tweaking daisychain internals to allow get_url to modify the path destination when downloading to a directory.
Minor module refactoring.
2012-07-22 11:08:16 -04:00
46650cfcec Merge branch 'get_url3' of https://github.com/jpmens/ansible into devel 2012-07-22 10:08:27 -04:00
396b81b647 Remove this check as it wasn't really needed and in recent refinements keeps the group_vars stuff from working
as desired.
2012-07-22 10:06:10 -04:00
492953f299 Merge pull request #650 from sfromm/newgroup
Update group module to use new shared module code
2012-07-22 06:40:57 -07:00
1a9c54b1e1 Update group module to use new shared module code 2012-07-21 22:30:13 -07:00
636a3367b5 Merge pull request #649 from marktheunissen/mysql_db_upgrade
Upgrading mysql_db to new shared module code
2012-07-21 16:06:23 -07:00
efb60776f4 Upgrading to new shared module code 2012-07-21 18:02:34 -05:00
9f149c9f1a imports not needed 2012-07-21 17:25:47 -04:00
d0f4358730 Port the copy module over to the new "common module" logic. 2012-07-21 17:07:42 -04:00
d76c8c9c85 Various cleanup around runner's constructor and how daisy chaining is invoked. 2012-07-21 16:51:31 -04:00
1682dd06c0 Avoid duplicate call to fetch inventory variables 2012-07-21 16:23:00 -04:00
54c9c1e25c Some streamlining of first_available_file code 2012-07-21 16:15:36 -04:00
b8d7b5041b new module: get_url
get module (with new module-magic-code!)
	Usage: ansible -m get -a "url=http://xxxxxxx  dest=fileordirctory"
	all cleanups as per @mpdehaan's suggestions
	add daisychain
	added example playbook (get_url.yml) with URLencode example
2012-07-21 13:34:42 +02:00
2d06ee4c0d Fix some subtle things that were keeping 'hostvars' from being usable in templates. 2012-07-20 12:34:26 -04:00
2b6d8a8be5 Fix some issues with the setup cache 2012-07-20 11:58:08 -04:00
dde11baa56 Remove YAML inventory tests since this is now deprecated. 2012-07-20 11:31:28 -04:00
5a67a556cd Script to migrate YAML users to the INI format, so we can deprecate YAML inventory (which we are now doing). 2012-07-20 11:15:57 -04:00
b15dde6b9f update changelog 2012-07-20 10:13:26 -04:00
dcca08b27b Don't let with_items erase 'hostvars' 2012-07-20 10:02:35 -04:00
b574c70006 host and group vars should update the setup cache 2012-07-20 10:00:51 -04:00
ba3466af95 The following paths are now implict and optional in vars_files:
./group_vars/groupname.yml (for all groups the host is in)
./host_vars/hostname.yml (for the hostname given in the inventory)

This requires an actual inventory file, not script and the paths are relative
to the directory of the inventory file.
2012-07-20 09:43:45 -04:00
db1a4d8fac Make 'assemble' use the new daisychain logic 2012-07-20 09:17:42 -04:00
6fb74ae2f9 preserve 'changed' attributes in daisychain logic 2012-07-20 09:13:10 -04:00
d72c0c8898 replace module daisy-chaining logic 2012-07-20 08:57:50 -04:00
a33713234c Merge pull request #645 from sfromm/issue643
Skip get_ipv6_facts if socket.has_ipv6 is false
2012-07-20 17:30:08 -07:00
2a0dfba838 Skip get_ipv6_facts if socket.has_ipv6 is false 2012-07-20 17:19:26 -07:00
b0eedfbf92 Merge pull request #639 from goozbach/issue-627
improved output in with_items fixes: #627
2012-07-20 16:36:49 -07:00
85b9fa580c Merge pull request #644 from bradobro/common_fix
AnsibleModule now correctly reads param values with '"'.
2012-07-20 14:31:24 -07:00
a4a09fec82 AnsibleModule (in module_common.py) now correctly reads param values containing '='. 2012-07-20 21:19:38 +00:00
290e1af339 Merge pull request #640 from sfromm/fancy
Sanitize possible password argument when logging invocation
2012-07-20 14:11:58 -07:00
8195375412 Sanitize possible password argument when logging invocation; taken from user module 2012-07-20 11:57:36 -07:00
34e2584220 improved output in with_items fixes: #627 2012-07-20 14:01:54 -04:00
baf07659f7 Merge pull request #638 from sfromm/issue604
Fix for issue 604
2012-07-20 10:49:15 -07:00
fd059a3df2 Fix for issue 604
Do not switch to master branch in pull()
Add --track to git checkout, when checking out a remote branch to track.
2012-07-20 10:43:26 -07:00
70ef8e9ebb Merge pull request #637 from goozbach/git-mkdir
git mkdir causes problems with older git (can't c/o to existing directory)
2012-07-20 06:54:20 -07:00
f0f8eb3445 git mkdir causes problems with older git (can't checkout to existing directory) 2012-07-20 09:48:18 -04:00
dba87fce91 Merge pull request #636 from preyk-work/devel
fixed debian changelog format
2012-07-20 05:44:35 -07:00
b0b7484390 fixed debian changelog format 2012-07-20 14:33:35 +02:00
6e84374c2a Make hostvars work post refactoring 2012-07-20 08:29:44 -04:00
31b6c1c28e Merge pull request #630 from cocoy/ssh-ubuntu
Suppress Ubuntu ssh -tt getting ioctl error.
2012-07-20 03:59:37 -07:00
b114a6075f Merge pull request #631 from sfromm/facts
Update setup module for facts derived from classes - take 2
2012-07-20 03:58:42 -07:00
d69e70db01 By defining a main function here, and including it ahead of the boilerplate insertion symbol, tracebacks are
now accurate with respect to the main function, but may include lines not in the original file.  A lot better
than before, where they were offset.
2012-07-20 06:37:46 -04:00
0efc0bec89 Update LinuxNetwork to not use fcntl and ioctls
get_interfaces() updated to read /proc/net/dev.  This means it no
longer provides only interfaces that are up.

get_iface_hwaddr() updated to read from /sys/class/net/<iface>/address.

Added get_interface_facts() to pull in mac address and interface mtu.
Can be used later for additional interface-facts.

Added get_ipv6_facts(), which reads from /proc/net/if_inet6.

get_network_facts() renamed to get_ipv4_facts().  It still calls
ifconfig to determine ipv4 facts.
2012-07-19 23:50:13 -07:00
162b78ea5c Check if CPU is QEMU via different mechanism
The processor fact doesn't exist in LinuxVirtual.  Read /proc/cpuinfo
and check if it is QEMU.
2012-07-19 23:50:12 -07:00
e3dd25b1b2 Add back erroneously deleted get_file_content() 2012-07-19 23:50:12 -07:00
97c64f3632 Adjustments to behavior of Facts and subclass behavior
The Facts class and subclasses no longer take a dict argument for
instantiation.  populate() now returns self.facts.
Other changes:
- Facts.__init__() takes over most of the work from populate() so that
  subclasses can benefit from its knowledge.
- Drop setting unknown facts to 'NA' in __init__() in various
  subclasses.
- Check for presence of facts in get_virtual_facts()
- Update ansible_facts() to use facts.update(<classname>().populate())
2012-07-19 23:50:12 -07:00
ae1b2394ac Update setup module for facts derived from classes
This changes and organizes facts into a base class Facts and several
sub classes that implement the necessary functionality.  The classes
are:
- Facts: base class.  Implements basic facts that should be common to a
  number of platforms.  It is also where SSH keys and SELinux facts are
  set.
- Hardware: A subclass of Facts that should be further
  subclassed per platform for CPU, memory, and related facts.
  - LinuxHardware: subclass of Hardware for Linux platforms
  - SunOSHardware: subclass of Hardware for SunOS platforms
  - FreeBSDHardware: subclass of Hardware for FreeBSD
- Network: A subclass of Facts that that should be further
  subclassed per platform for IP, both IPv4 and IPv6, information.
  - LinuxNetwork: Currently only implementation for determining network
    facts.
- Virtual: A subclass of Facts that that should be further
  subclassed per platform to determine virtual environment facts.
  - LinuxVirtual: Currently only implementation for determining virtual
    facts.

If facts are needed for additional platforms, one of the above classes
(eg Network) can be further subclassed and implement the necessary
functionality.

In addition, it fixes get_network_facts() to work on Fedora17.  That
broke due to changes to ifconfig output.
2012-07-19 23:50:12 -07:00
5f068913ee Suppress Ubuntu ssh -tt error ioctl error. 2012-07-20 13:23:14 +08:00
a94ec130d2 Common module code upgrades 2012-07-17 23:09:57 -04:00
9006d4557d Added code to allow insertion of boilerplate into modules to make them able to share lots of code, the result
should be a huge reduction of total ansible source, at a slight cost of difficulty in original module development.

We need to apply this now to all modules, but may need to have some exemptions to things like command, which should
subclass this module.
2012-07-17 22:34:52 -04:00
ca21423c71 Merge pull request #628 from pas256/devel
Pass through error message when repo not found
2012-07-19 17:38:57 -07:00
5e35cd6466 Pass through error message when repo not found 2012-07-19 15:48:13 -07:00
f1148fc26d Merge pull request #625 from jkleint/devel
Service module outputting extra data
2012-07-19 12:00:02 -07:00
ae665c15b3 Service module outputting extra data.
The service module was printing stuff to stderr, returning two
JSON dicts, not using consistent 'failed' values, had dead code
and unused variables.  Added detection for the case when service
status returns 'xxx is dead and pid file exists' and made the
code a bit easier to read.
2012-07-19 13:15:09 -04:00
2a281f69c2 Merge pull request #621 from pas256/patch-2
Adding missing colon... can't believe I missed that... sorry
2012-07-18 17:48:59 -07:00
4b66d98332 Adding missing colon... can't believe I missed that... sorry 2012-07-18 17:48:13 -07:00
b38f8bfa80 Merge pull request #620 from pas256/patch-1
Conditional Imports should only import the first vars_file found in list
2012-07-18 16:31:45 -07:00
413fb15e0a Conditional Imports should only import the first file in the list, not all of them. This change stops looping through the vars_files list as soon as a file is found. 2012-07-18 15:31:04 -07:00
4e74ab4856 Merge pull request #619 from skvidal/devel
when using -i"hostname," on the cli - don't complain about the host name...
2012-07-18 15:25:22 -07:00
5ad9bc8833 when using -i"hostname," on the cli - don't complain about the host named "" not being a host - just clip it from the list 2012-07-18 16:56:41 -04:00
bfed0f14ec Merge pull request #613 from ludovicc/devel
Allow local plays to execute when --ask-sudo-pass is used on the command line
2012-07-18 10:27:51 -07:00
26dc607c5c Revert "Change previous fix to a one line change relative to upstream"
This reverts commit 0d53b1b097.
2012-07-18 19:02:56 +02:00
0d53b1b097 Change previous fix to a one line change relative to upstream 2012-07-18 17:52:51 +02:00
2377152d4c Merge pull request #612 from jhoekx/fix-custom-facts
Fix typo while handling custom facts.
2012-07-18 04:33:01 -07:00
c4303c8c04 Avoid spurious error when mixing a local play with remote plays depending on --ask-sudo-pass 2012-07-18 12:05:12 +02:00
f1b1611956 Fix typo while handling custom facts.
This fixes #611.
2012-07-18 11:50:34 +02:00
8bb8314d10 Inject vars need to pushed further up to make with_items work as desired. 2012-07-17 20:09:18 -04:00
3c2cbae68e Plays and tasks now yell when they see parameters they do not understand, so that typos like 'var' for 'vars'
don't result in people having to ask questions about what is wrong with their playbook
2012-07-17 19:09:36 -04:00
e4a546e8bb Make it possible to use variables from 'vars' in the paths to task level include statements. 2012-07-17 18:57:02 -04:00
43b3a0be97 Merge pull request #603 from davehatton/meaningful_messages_if_remote_md5_fails
meaningful messages if the remote md5 fails
2012-07-17 06:57:06 -07:00
72ca9e58e7 meaningful messages if the remote md5 fails 2012-07-17 09:14:16 +01:00
9e62fd42b5 update test utils code 2012-07-16 20:28:32 -04:00
0edd0d4736 Fixing template calls 2012-07-16 20:26:39 -04:00
01f2c85a3c update changelog 2012-07-16 20:19:52 -04:00
1a88a3362f add noninteractive to debian module per #602 2012-07-16 20:18:58 -04:00
994019a5c9 update changelog 2012-07-16 20:15:10 -04:00
f6a09bc4c1 Squashed commit of the following:
commit ea14bbfb52587bf5b97b5577c0439b01cb0b4836
Merge: 82819a9 285aaf8
Author: Michael DeHaan <michael.dehaan@gmail.com>
Date:   Sun Jul 15 14:38:28 2012 -0400

    Merge branch 'devel' of https://github.com/skvidal/ansible into skvidal_mount

commit 285aaf836c
Merge: 634c117 32b6879
Author: Seth Vidal <skvidal@fedoraproject.org>
Date:   Mon Jul 16 12:42:21 2012 -0400

    Merge remote-tracking branch 'upstream/devel' into devel

    * upstream/devel: (52 commits)
      format fixes to make fetch more usable
      ...

commit 634c11748e
Author: Seth Vidal <skvidal@fedoraproject.org>
Date:   Thu Jul 12 01:16:00 2012 -0400

    fix a bunch of small bugs in mount module - test with bind and local mounts

commit fcfd73de71
Author: Seth Vidal <skvidal@fedoraproject.org>
Date:   Wed Jul 11 20:46:14 2012 -0400

    fix some obvious bugs pointed out by #ansible

commit 13c3080383
Author: Seth Vidal <skvidal@fedoraproject.org>
Date:   Wed Jul 11 19:35:34 2012 -0400

    mount/fstab editor
2012-07-15 14:38:40 -04:00
3f17ad62cd Squashed commit of the following:
commit 5b9d29e08fba6402efccaa1ca56d834ff58f4b55
Merge: 7286f3a 111561f
Author: Michael DeHaan <michael.dehaan@gmail.com>
Date:   Sun Jul 15 14:34:10 2012 -0400

    Merge branch 'devel' of https://github.com/pas256/ansible into pasec2

commit 111561f782
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Mon Jul 16 16:13:48 2012 -0700

    Reading the config file from the same directory as the script, and
    fixing typo in security group name

commit e63659d04e
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Mon Jul 16 10:56:59 2012 -0700

    Adding list of variables to documentation

commit d0aecf025e
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Fri Jul 13 15:35:00 2012 -0700

    Finished EC2 external inventory script

commit 136b8a5082
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Thu Jul 12 18:01:44 2012 -0700

    Almost done, just a few more types to deal with

commit 09f92052ce
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Tue Jul 10 23:43:30 2012 -0700

    Adding region options to config, and getting host details happening, but
    need to figure out complex types

commit a940c9b8fb
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Tue Jul 10 18:06:05 2012 -0700

    Converting to a class, commenting methods, reading settings from a
    config file, and starting to work on the host variables

commit 43eb3214a6
Author: Peter Sankauskas <pas256@gmail.com>
Date:   Mon Jul 9 13:11:27 2012 -0700

    First pass of the Ansible EC2 inventory script
2012-07-15 14:34:24 -04:00
5f69c557b9 Fix for the way host variables were being swallowed, plus some overall simplification. Deciding what
dictionary to use for templating now happens in exactly one place (executor_internal) and the "inject"
dictionary is passed to what needs it.
2012-07-15 14:19:11 -04:00
9ef7168a49 Merge pull request #596 from davehatton/output_format_fixes
format fixes to output from fetch
2012-07-16 04:46:18 -07:00
c3205595b3 format fixes to make fetch more usable 2012-07-16 11:43:37 +01:00
b542dd8466 Fix import in ssh connection 2012-07-15 13:12:11 -04:00
80edea2934 with_items handling code should apply to all modules, was screwed up in refactoring to only apply to 'normal'
modules.
2012-07-15 12:51:56 -04:00
1754de3335 Misc code cleanup, mostly whitespace preferences, removing unused imports, plus a few fixes here and there. 2012-07-15 12:29:53 -04:00
4b73931351 misc code cleanup, don't pass parameters to the connection object we can already get from Runner 2012-07-15 11:54:39 -04:00
6341e75e33 misc code cleanup 2012-07-15 11:20:59 -04:00
031ba43d22 minor code cleanup 2012-07-15 11:09:15 -04:00
1d8bb17785 misc code cleanup 2012-07-15 10:57:22 -04:00
5a63a48e6b No need to feed variables into the setup module for preservation since the file is no longer saved to disk. 2012-07-15 10:24:38 -04:00
227c500ad7 Misc cleanup/formatting 2012-07-15 10:22:15 -04:00
867b2437cc consolidate output code in callbacks.py, from utils, remove extra functions 2012-07-15 10:12:49 -04:00
bc71c6ddd7 add space between function 2012-07-15 09:33:48 -04:00
cb55cd6f8d removed unused function 2012-07-15 09:33:22 -04:00
68a9adc1be Indentation cleanup (partial) 2012-07-15 09:32:47 -04:00
7b177e8ebb update changelog 2012-07-15 09:25:59 -04:00
244b923559 No longer need to calculate setup metadata location since we don't write
the setup file to disk.
2012-07-15 09:25:17 -04:00
61d8e2e7c7 with_items should be processed inside the task class, also make first_available_file
and with_items incompatible.  Most cases that would need this could be solved
with vars_files.
2012-07-15 09:17:04 -04:00
706cfd9ade Misc cleanup -- moved function out of utils, removed debug statement. 2012-07-15 08:50:23 -04:00
19a2fc00dc Clean up imports in utils, add nicer recursion depth limit check. 2012-07-15 08:46:58 -04:00
60d64251f8 Allow gather_facts: False in a playbook as a way of disabling the fact step if you know
you aren't going to need it.
2012-07-14 19:49:42 -04:00
3466ad5e45 Update changelog 2012-07-14 19:31:18 -04:00
64c51ade1e Change the way we do with_items to make them happen next to each other in runner, which eliminates the problem of with_items and vars_files sometimes not playing nice with each other.
(Also a fix for the user module error handling when the user
is not present at the time of the return.  This can only really be caused by multiple ansible executions).
2012-07-14 19:18:33 -04:00
6c84ec72c6 changelog update 2012-07-14 13:46:34 -04:00
028f216689 Error handling and magic around with_items, to let you know when variables are usable inside it and when not. 2012-07-14 13:23:14 -04:00
102b22be1f Make the logic behind vars_files smarter, so host specific vars go into the setup cache, but other variables
are usable top level.  This allows vars_files data to be used, most of the time, just fine in with_items,
(error handling pending) but is mostly a memory and efficiency thing.
2012-07-14 13:12:19 -04:00
15b2b3a020 Warn if something loaded by vars_files is not a dictionary 2012-07-14 12:39:06 -04:00
279b5965b8 Much streamlining around setup steps in playbooks, now only run setup once per play. 2012-07-14 12:24:19 -04:00
931f9f1a61 Setup module no longer saves to disk, as templates are mostly useful in playbooks and this allows lots of simplifications
around file pathing and removes occasional permissions conflicts depending on how things are used.
2012-07-14 11:59:12 -04:00
2a2017f295 Changelog update 2012-07-12 01:33:13 -04:00
60f295f7a0 Squashed commit of the following:
commit 4430ce3eefcdff0b0ceffea0ef66ea8e876a807d
Merge: 631783b 649963c
Author: Michael DeHaan <michael.dehaan@gmail.com>
Date:   Thu Jul 12 01:28:43 2012 -0400

    Merge branch 'host-groups' of https://github.com/dagwieers/ansible into daggroups

commit 649963ca2c
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 23:01:00 2012 +0200

    Added comments in the example yaml file as requested

commit 7f9718f185
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 22:49:38 2012 +0200

    Add the default nose color too, to test specific overrides

commit eb63b9e899
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 22:44:35 2012 +0200

    Introduce comics and cartoons to test yaml groups defined on a per-node basis

commit aa13d23307
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 19:33:15 2012 +0200

    A small fix to revert to old state

commit 264ebaa77c
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 19:31:51 2012 +0200

    Combine both yaml unit tests into one example file

commit 7db49a8048
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 16:46:53 2012 +0200

    Might as well fix this too

commit f36c6c8c5b
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 16:42:00 2012 +0200

    Added unit tests for host-groups patch

    For the unit test I chose to keep the original yaml file in place as a reference.

    This patch also includes a fix.

commit a96f681352
Author: Dag Wieërs <dag@wieers.com>
Date:   Thu Jul 12 12:30:43 2012 +0200

    Allow groups to be defined on a per-host basis

    This makes it possible to define on a per-host basis what groups a host is in.
    When managing a large set of systems it makes it easier to ensure each of the
    systems is defined in a set of groups (e.g. production/qa/development,
    linux/solaris/aix) rather than having to add systems to multiple disconnected
    groups.

    ----
      - host: system01

      - host: system02

      - host: system03

      - group: linux
        hosts:
        - system01
        - system02

      - group: solaris
        hosts:
        - system03

      - group: production
        hosts:
        - system01
        - system03

      - group: qa
        - system02

      - group: dbserver
        hosts:
        - system01

      - group: ntpserver
        hosts:
        - system02

      - group: webserver
        - system03
    ----

    Can be redefined as:

    ----
      - host: system01
        groups: [ linux, production, dbserver ]

      - host: system02
        groups: [ linux, qa, ntpserver ]

      - host: system03
        groups: [ solaris, production, webserver ]
    ----
2012-07-12 01:29:51 -04:00
86c25fd82d Merge pull request #590 from marktheunissen/mysql
The MySQL modules
2012-07-14 07:48:27 -07:00
f47cc1693d The MySQL modules 2012-07-13 17:07:23 -05:00
420337b50c Merge pull request #587 from dagwieers/ssh-fetch
Wrong evaluation of a local file before fetching the remote file
2012-07-13 05:25:40 -07:00
cb40778b84 Wrong evaluation of a local file before fetching the remote file 2012-07-13 11:07:32 +02:00
fd0ed04d19 Merge pull request #586 from tima/env-private-key-fix
The default private key file was getting set incorrectly from environment variables. My bad.
2012-07-12 11:38:44 -07:00
84fa9dcaee The default private key file was getting set incorrectly from environment variables. My bad. 2012-07-12 13:30:30 -04:00
b1c0fcd1d3 Merge pull request #583 from jeremysmitherman/boolean_parameters
Removed unused recurse option in file module.
2012-07-12 05:43:04 -07:00
98eec45525 Merge pull request #584 from gottwald/devel-cowsayUbuntu
Enabled cowsay support for Ubuntu
2012-07-12 05:42:14 -07:00
26aa4e2469 Enabled cowsay support for Ubuntu 2012-07-12 11:14:15 +02:00
e5a6627d10 Removed unused recurse option in file module. 2012-07-12 02:04:51 -04:00
fd29ecc24b changelog update 2012-07-12 01:09:10 -04:00
bcd0001cb1 Bring the cobbler external inventory script up to date with current usage (--list, --host hostname) 2012-07-12 01:05:49 -04:00
611e5b0c90 Make adding tags to a playbook work as shorthand to tagging all tasks in the play. 2012-07-12 00:58:48 -04:00
cf313cde96 Soft import of paramiko since we have the SSH and local connection types now. Packaging will still
require it.
2012-07-12 00:43:51 -04:00
a765deccce Allow top level playbook files to import other playbook files, added some basic
playbook structure checking.
2012-07-12 00:19:44 -04:00
87e911c405 changelog update 2012-07-11 20:51:07 -04:00
dc4ebab345 Merge pull request #576 from davehatton/adjust_playbook_output_formating
improve output formating for playbook
2012-07-11 17:42:39 -07:00
969c3feb13 Allow include statements from plays to specify tags (see tags.yml example file).
Also be smart and don't run a play at all if no tasks in the play match any of the tags specified.  This includes not running the setup actions!
2012-07-11 20:33:46 -04:00
83f23ef861 Basic support for tagging tasks and selecting a subset of tasks to run with --tags. 2012-07-11 19:51:26 -04:00
fd7e96d33e Merge pull request #578 from jeremysmitherman/boolean_parameters
Removed boolean parameter setting from user module.
2012-07-11 10:58:09 -07:00
626e4cb003 Merge pull request #572 from goozbach/devel
fixes #569 apt module problems on old versions of apt-python
2012-07-11 10:53:29 -07:00
ecacb67d31 Added yes/no to user options instead of False comparison for force and remove options for user module 2012-07-11 12:16:40 -04:00
3f5c4772be Merge pull request #575 from jeremysmitherman/apt-force
Added cmd modifications to use a force-yes option for apt module
2012-07-11 07:16:40 -07:00
949f8b8fc5 Merge pull request #571 from sfromm/group
Group module fixes
2012-07-11 05:27:56 -07:00
36177cc935 Merge pull request #573 from sfromm/user
Minor tweak to user module
2012-07-11 05:26:26 -07:00
7ecdb83800 improve output formating for playbook 2012-07-11 09:35:10 +01:00
a35a0f885e Added cmd modifications to use a force-yes option for apt module 2012-07-10 22:22:52 -04:00
f38b9d1ca7 added filter for future warning 2012-07-10 19:50:08 -04:00
ac3341cc12 Minor fix to user module to further streamline setting 'changed' flag 2012-07-10 16:37:07 -07:00
e261d49acc fixes #569 apt module problems on old versions of apt-python (namely debian 5.0) 2012-07-10 19:28:14 -04:00
3a5f2126a6 Fixes to group module for better error handling
Takes a lot of the fixes to the user module and applies them to the
group module: provide stdout/stderr in result if available and call
fail_json() if the attempted action fails.
2012-07-10 16:18:53 -07:00
3235da074e Merge pull request #568 from sfromm/user
User modules fixes
2012-07-10 16:04:17 -07:00
8bb41bc627 Merge pull request #566 from jeremysmitherman/service-no-pid
Added additional output to service status checker
2012-07-10 16:03:51 -07:00
25986fa181 Merge pull request #567 from skvidal/devel
modify user module so that password is NEVER logged before the command runs
2012-07-10 16:03:22 -07:00
09f025b7a0 Fixes to user module for better error handling
The user module now returns the output, both stdout and stderr, from
useradd, usermod, and userdel.  This should help debug cases why one of
those commands fail.  In addition, the user module will now call
fail_json() when the attempted command failed so as to properly
communicate a failure in a playbook.
2012-07-10 15:55:39 -07:00
2f11350665 modify user module so that password is NEVER logged before the command runs 2012-07-10 17:13:51 -04:00
7af3bdc89e Added additional output to service status checker 2012-07-10 16:13:39 -04:00
238e4adba2 Merge pull request #559 from jkleint/iterative-md5
Use iterative MD5 hashing.
2012-07-09 11:03:59 -07:00
375a1eaf43 Use iterative MD5 hashing. 2012-07-09 13:27:47 -04:00
211f1e69c3 Merge branch 'change_to_using_hashlib_or_md5' of https://github.com/davehatton/ansible into devel
Conflicts:
	lib/ansible/utils.py
2012-07-07 11:51:30 -04:00
55694db7c3 switch to hashlib.md5 or md5 instead of OS md5 commands 2012-07-09 08:52:00 +01:00
f39b99f823 Revert "switch to using hashlib.md5 or md5 (if python 2.4) instead of a os call"
This reverts commit 865796c368.

(Superseeded by next commit)
2012-07-07 11:48:32 -04:00
c432f9bc74 Merge pull request #554 from davehatton/switch_to_using_hashlib_or_md5
Switch to using hashlib or md5
2012-07-09 04:47:02 -07:00
fb51805e1b switch to using hashlib.md5 or md5 (if python 2.4) instead of a os call 2012-07-08 18:41:04 +01:00
19b43cd454 further md5 fixes 2012-07-08 17:39:57 +01:00
921bf3da9d changelog update 2012-07-07 09:30:39 -04:00
0d28466b22 When sudo'ing to root, keep the setup file location as /etc/ansible/setup 2012-07-07 09:29:03 -04:00
c592fdf838 update changelog 2012-07-07 09:11:24 -04:00
c2b8fabf66 Take a remote md5sum before a file transfer to decide whether to transfer the file or not.
Allows for efficient transfer of large files.  Templates do not sample first because they are small.
2012-07-07 09:10:18 -04:00
6c08913448 changelog updates 2012-07-07 08:49:15 -04:00
494077591a changelog update 2012-07-07 08:47:32 -04:00
8220d57690 Clean up md5 functions + make the fetch module stay happy if the remote file does not exist. 2012-07-07 08:47:32 -04:00
b551eba6d4 Comment uncommented methods in utils 2012-07-07 08:47:32 -04:00
749472559b Merge pull request #546 from pas256/devel
apt module to support --no-install-recommends
2012-07-07 05:00:42 -07:00
fed1e9870c Assigning to boolean first 2012-07-06 18:05:15 -07:00
fced8cf6b0 Getting the apt module to accept and understand a 'install-recommends' parameter which allows running apt with --no-install-recommends 2012-07-06 12:12:04 -07:00
1a151a581a Merge pull request #542 from davehatton/copy_always_returns_changed_equals_true
copy module, return changed attribute seperate from md5sum
2012-07-06 07:06:14 -07:00
ecaa794f7f copy always returned change=true 2012-07-06 13:43:19 +01:00
367dce9790 Merge pull request #537 from davehatton/bump_rpm_spec_to_0_6
bump rpm spec to 0.6
2012-07-05 05:37:35 -07:00
7c0ac3653d bump rpm spec to 0.6 2012-07-05 07:18:43 +01:00
bd2a5724ec update versions mentioned in README.md 2012-07-04 18:07:19 -04:00
a490213d45 Release bump to start development on 0.6 2012-07-04 18:07:19 -04:00
e3aba7770f Merge pull request #518 from brainpage/devel
adds 'groups' variable, essentially making hosts file accessible as a var
2012-07-04 11:28:46 -07:00
ea0c82d680 Merge pull request #534 from davehatton/switch_to_shutil_copy
switch to shutil.copy rather than os.system(cp)
2012-07-04 11:25:35 -07:00
0bbe0a8670 Bump dates in packaging files for release. 2012-07-04 13:37:46 -04:00
9d85a55434 switch to shutil.copy rather than os.system(cp) 2012-07-04 12:26:25 +01:00
bea2939deb Merge pull request #532 from davehatton/allow_copy_if_file_doesnt_exist
md5 allow copy if file is missing
2012-07-03 12:41:20 -07:00
263fcab594 md5 allow copy if file is missing 2012-07-03 20:07:08 +01:00
ec0522a127 Merge pull request #531 from jeremysmitherman/devel
Added IndexError catch for copy module, IndexError is thrown if a destin...
2012-07-03 11:37:21 -07:00
3003aa2d47 Added IndexError catch for copy module, IndexError is thrown if a destination file doesn't exist 2012-07-03 14:12:19 -04:00
e0a1c5be42 Merge pull request #529 from davehatton/error_trapping_md5
improve error trapping for md5
2012-07-03 10:07:59 -07:00
24b91169d9 Merge pull request #530 from dhozac/templates-vars
Allow variables in variables in templates
2012-07-03 10:06:04 -07:00
9882dd56f6 Allow variables in variables in templates 2012-07-03 16:42:00 +02:00
d47de18b6c improve error trapping for md5 2012-07-03 12:44:37 +01:00
73c89bced0 Merge pull request #526 from setsuna-/devel
Fixed: executing templates outside playbooks
2012-07-02 15:55:49 -07:00
f72b1f4fd7 Fixed: pathname for tmp 2012-07-02 22:04:56 +02:00
9ad622946a Merge pull request #524 from bradobro/authorized_key
Authorized key fixes
2012-07-02 12:39:42 -07:00
bbf2939063 Fixed: executing templates outside playbooks 2012-07-02 21:34:11 +02:00
be9ff7ff46 Fixes ansible/ansible#522, no longer creates comment in ~/.ssh/authorized_keys, does not create directory or file if state==absent. 2012-07-02 19:16:57 +00:00
756df550f8 Fixes ansible/ansible#523. Removed 'with:' blocks so module works with Python 2.4 for CentOS 5 support, courtesy of mcodd/ansible@29af24b732. 2012-07-02 17:57:38 +00:00
9714f5c79d Merge pull request #517 from goozbach/devel
Updated the file module to handle the case where the UID for the file doesn't exist on the system.
2012-07-02 05:18:29 -07:00
fc4ae3e270 remove UID/GID from output 2012-07-01 17:38:17 -04:00
cedbcfcc0a adds 'groups' variable, essentially making hosts file accessible as a variable 2012-07-01 15:04:53 +08:00
a548769792 removed print from debug code of file module 2012-06-29 21:38:31 -04:00
82832021e8 file when UID or GID doesn't exist 2012-06-29 20:05:23 -04:00
3d7a51f2ec Add some recent merges to the changelog 2012-06-29 18:56:49 -04:00
a5dd280149 Fix up the output of the raw module when run within /usr/bin/ansible 2012-06-29 18:48:37 -04:00
d95914ca87 update changelog 2012-06-29 18:44:09 -04:00
efac68b636 Remove the -D module debug flag, which no longer is functional due to sudo pty requirements, and replace with -v/--verbose.
This flag will show playbook output from non-failing commands.  -v is also added to /usr/bin/ansible, but not  yet used.

I also gutted some internals code dealing with 'invocations' which allowed the callback to know what module invoked
it.  This is not something 0.5 does or needed, so callbacks have been simplified.
2012-06-19 21:57:43 -04:00
9efea6f7a2 Merge pull request #512 from jhoekx/yaml-inventory-one-host
Correctly add ungrouped hosts to 'ungrouped' in YAML inventory.
2012-06-27 05:03:31 -07:00
d592e15dcc Correctly add ungrouped hosts to 'ungrouped' in YAML inventory. 2012-06-27 08:26:01 +02:00
afb2e3e5ac Merge pull request #511 from dsummersl/devel
added ability to ovverride all ssh settings via ANSIBLE_SSH_ARGS
2012-06-26 15:28:49 -07:00
37fce9c0f6 added ability to ovverride all ssh settings via ANSIBLE_SSH_ARGS 2012-06-26 14:59:42 -04:00
91ad42ae75 Merge pull request #510 from ludovicc/devel
Add basic support for Solaris
2012-06-26 11:07:29 -07:00
daf858648b Add basic support for Solaris 2012-06-26 19:49:26 +02:00
8468e2255b Merge pull request #507 from jhoekx/convention-library-path
Add ./library to module search path.
2012-06-25 12:20:56 -07:00
879ddddf6f Add ./library to module search path.
It allows for custom modules in the best practices directory structure.
Bundling custom modules along with playbooks in packages is useful.
2012-06-25 21:08:43 +02:00
c2694986d8 Merge pull request #506 from jhoekx/import-errors-poller
Import ansible errors in Poller.
2012-06-25 11:58:16 -07:00
c4b489ab1f Merge pull request #504 from gottwald/devel-copyfix
Copy module: Error handling for missing permissions on the copy destination.
2012-06-25 11:57:50 -07:00
6c92d2fa88 Import ansible errors in Poller. 2012-06-25 20:48:35 +02:00
a42167ca73 Copy module: Extended error handling and fixed error messages 2012-06-25 20:45:05 +02:00
270266e920 Added to copy module: Error handling for missing permissions on the copy destination 2012-06-23 21:39:37 +02:00
618924a24f Merge pull request #497 from davehatton/change_constants
shouldn't need a (slash) before /home/daveh
2012-06-22 12:01:15 -07:00
81c69d7333 Merge pull request #500 from davehatton/change_to_use_stdout
change to STDOUT from PIPE to get ssh connection module working
2012-06-22 04:41:11 -07:00
d42df3a82e change to STDOUT from PIPE to get ssh connection module working 2012-06-22 12:37:23 +01:00
6e60fc20c1 Merge pull request #498 from weaselkeeper/devel
fix syntax error in changelog that was preventing building of deb packag...
2012-06-21 11:50:34 -07:00
9be2a94045 fix syntax error in changelog that was preventing building of deb packages 2012-06-21 10:40:52 -07:00
d28bbe14ed shouldn't need a (slash) before /home/daveh 2012-06-21 15:37:43 +01:00
54ac3dac34 Update devel 2012-06-21 10:03:01 -03:00
a7dc1bf350 Update devel 2012-06-21 10:01:31 -03:00
d9753a798a Update devel 2012-06-21 10:01:09 -03:00
bdec9e7064 Merge pull request #496 from davehatton/fix_stderr
change STDOUT to PIPE  to get ssh connection module working
2012-06-21 06:00:07 -07:00
30d4f070a1 change STDOUT to PIPE to get ssh connection module working 2012-06-21 11:01:08 +01:00
25083953fc Merge pull request #495 from nafeger/devel
make copy module implemented closer to other newer modules when returning results
2012-06-20 13:12:24 -07:00
dc13265a70 Merge pull request #494 from dhozac/ssh-conn-helpful
Make the SSH transport helpful
2012-06-20 13:09:50 -07:00
26816b72ec attempt to make copy more idiomatic. 2012-06-20 14:17:15 -05:00
fa887bc90f Merge stdout and stderr for non-sudo 2012-06-20 20:49:52 +02:00
7884bc02ea Wait for process to finish and then parse output 2012-06-20 20:49:52 +02:00
dca75033fe add -q to handle stdout/stderr being combined, add catch for ControlPersist not existing in ssh for rhel6, etc 2012-06-20 14:06:24 -04:00
9662902c31 Merge pull request #492 from dhozac/unnecessary-parse_json
Remove unnecessary parse_json invocation
2012-06-20 10:26:17 -07:00
071978a2e7 Merge pull request #493 from dhozac/ssh-merge-stdouterr
Merge stdout and stderr as that is what is expected
2012-06-20 10:24:32 -07:00
f5ecd075f7 Remove unnecessary parse_json invocation 2012-06-20 14:54:36 +02:00
12825edae1 update the changelog for the currently ongoing development release. 2012-06-19 21:20:17 -04:00
980c9d2d8c Merge pull request #483 from gottwald/devel-service-enhancement
Enhanced the service state recognition in the service module
2012-06-19 17:34:02 -07:00
bd653c8f3c Merge pull request #488 from dhozac/jinja2-env
Create a Jinja2 environment allowing includes
2012-06-19 13:09:02 -07:00
e0f12292d8 Merge pull request #489 from dhozac/ssh-args-default
Default to using a ControlMaster connection
2012-06-19 13:01:09 -07:00
7f6359b143 Default to using a ControlMaster connection 2012-06-19 21:58:24 +02:00
d08ba12a42 Merge stdout and stderr as that is what is expected
Should also fix problems where something might output an error
before the sudo prompt.
2012-06-19 21:55:00 +02:00
97cdc7ae82 Merge pull request #487 from davehatton/fix_changelog_date
The changelog entry requires a valid date
2012-06-19 05:20:45 -07:00
a30f5461da The changelog entry requires a valid date 2012-06-19 10:51:22 +01:00
b02959d64f Merge pull request #486 from skvidal/devel
make Runner options conflict errors raise AnsibleErrors not traceback in...
2012-06-18 13:42:40 -07:00
1ec518543e make Runner options conflict errors raise AnsibleErrors not traceback in general
add catch if -c ssh and -k are passed at the same time since the ssh connection type doesn't
support passwords
2012-06-18 15:48:19 -04:00
aa18e66987 Merge pull request #485 from dhozac/ssh-conn
Add an ssh command wrapper transport
2012-06-18 11:17:53 -07:00
031c75e53a Update tests to reflect new API 2012-06-18 00:13:12 +02:00
9f93839d13 Create a Jinja2 environment allowing includes 2012-06-17 22:18:08 +02:00
ab08fea1aa Add an ssh command wrapper transport 2012-06-17 20:34:17 +02:00
abe33a5881 Enhanced the service state recognition in the service module:
- Added Upstart support
- Added an initial unknown state
- Prevented state changes when the current state is not recognized
- Changed the keyword recognition to a safer method
2012-06-17 15:55:26 +02:00
2cabbd50fc Merge pull request #482 from dhozac/iterative-template
Iterative template
2012-06-16 11:13:11 -07:00
24d73b0fe6 Add tests of iterative templating 2012-06-16 13:13:01 +02:00
13b814a33c Run templating function until the text doesn't change
This allows variables to contain other variables.
2012-06-16 12:51:19 +02:00
dad9695ac7 Merge pull request #478 from dhozac/not-host
Allow exclusion of hosts/groups
2012-06-15 17:39:47 -07:00
38b186b159 Merge pull request #481 from jonten/devel
Fixed typo from results to result in _execute_raw method
2012-06-15 17:39:19 -07:00
1b091a7e3e Fixed typo from results to result in _execute_raw method 2012-06-15 22:45:47 +02:00
05a7a3725b Add tests of host exclusions 2012-06-15 19:09:04 +02:00
8df9cc7649 Merge pull request #477 from phred/md5sum-on-bsd
md5sum on bsd
2012-06-15 04:40:51 -07:00
3d5c93bf44 Allow exclusion of hosts/groups 2012-06-15 11:01:30 +02:00
80f402aa9b Remove unused md5sum function from file module 2012-06-15 00:13:35 -04:00
834f6a216d Make shell outs to md5sum work on FreeBSD and OS X
Tested with OS X local connection and Linux remote. The paths to the
md5sum and md5 commands are hardcoded to the most common location. This
will definitely fail if the commands are elsewhere, or if the md5
command doesn't support the -q 'quiet' option.
2012-06-15 00:04:01 -04:00
c0a5d91592 Merge pull request #475 from nafeger/devel
Better error messaging in utils
2012-06-14 12:15:36 -07:00
9feb26ab28 Better error messaging in utils
When a command responds with json that is unparseable, dump that unpareseable response instead of swallowing it.
2012-06-14 13:17:38 -05:00
036e0866b5 Merge pull request #474 from sfromm/issue471
Issue471 - import hashlib to sidestep deprecation warnings
2012-06-14 09:40:20 -07:00
4a6a3fd305 Conditionally import hashlib (issue #471)
Import hashlib if it is there, otherwise import md5.  Adds method
file_digest that wraps the logic on which module to invoke.
2012-06-14 07:18:46 -07:00
2a8d1f07d4 Merge branch 'mktemp-vanish' of https://github.com/dagwieers/ansible into devel
Conflicts:
	lib/ansible/runner/__init__.py
2012-06-13 22:39:58 -04:00
7926ab5b95 Merge pull request #469 from nafeger/devel
Add support for older version of python-apt
2012-06-13 19:21:31 -07:00
27c949ade9 Merge pull request #462 from tima/more-env-defaults
Most constants can be overriden with environment variables.
2012-06-13 19:20:33 -07:00
2a49438c37 Merge pull request #461 from dhozac/only_if-early
Evaluate only_if early
2012-06-13 19:19:25 -07:00
bd893b765b Merge pull request #459 from dhozac/with_items-variable
Allow a variable in with_items
2012-06-13 19:18:51 -07:00
37fd408c69 Merge pull request #463 from sfromm/assemble
Assemble module
2012-06-13 19:17:47 -07:00
1d553830f2 Add support for older version of python-apt 2012-06-13 13:57:47 -05:00
9cf182c225 Add assemble module
This adds a module that concatenates (ie. assembles) a file from
fragments in a directory in alphabetical order.  It chains the file
module afterward to fix up ownership and permission.  This also adds
tests for the assemble module with fragments in assemble.d.
2012-06-11 23:09:54 -07:00
5ea8ad2d94 Most constants can be overriden with environment variables. Reserved doing anything with the connection options for further review. 2012-06-11 18:07:44 -04:00
7942570411 Fix on_unreachable invocation with non-existing variable 2012-06-11 15:15:29 +02:00
eba81ee36f Evaluate only_if early to do as little work as possible 2012-06-11 15:15:29 +02:00
93ac6c5ba0 Merge pull request #460 from emgee/module-paths-2
Search multiple paths for modules.
2012-06-10 09:56:21 -07:00
c1b38f62c8 Search multiple paths for modules.
Minimal change to allow a list of paths (separated by the typical path
separator) to be searched in sequence for the named module.
2012-06-10 16:22:59 +01:00
a05da2d312 Allow with_items to specify a variable 2012-06-09 21:59:22 +02:00
933b5e8631 Reduce code duplication 2012-06-09 21:50:46 +02:00
e8e6c98016 Merge pull request #455 from dhozac/vars_files-non-list
Expand vars_files identically for lists and non-lists
2012-06-08 12:19:47 -07:00
01387520e9 Merge pull request #451 from phred/fix-fetch-module
Make 'fetch' test for local directories before creating.  Fixes issue #450
2012-06-08 12:06:02 -07:00
e265cdfa1d Merge pull request #454 from jhoekx/configure-python-interpreter
Introduce ansible_python_interpreter variable.
2012-06-08 12:03:40 -07:00
b1c2820a32 Merge pull request #456 from dhozac/notify-variable
Allow variables in notify list
2012-06-08 11:59:39 -07:00
9fe4a13217 Merge pull request #457 from dhozac/module_vars-consistent
Expand $item for copy and fetch
2012-06-08 08:55:20 -07:00
d0d036fd3f Expand $item for copy and fetch 2012-06-08 01:36:52 +02:00
ad7b744764 Allow variables in notify list 2012-06-08 01:35:23 +02:00
dff8d7be04 Expand vars_files identically for lists and non-lists 2012-06-08 01:32:56 +02:00
1f63c47738 Introduce ansible_python_interpreter variable.
This allows configuration of the correct python interpreter on the managed system.
2012-06-07 09:50:38 +02:00
b3b22c41e9 Get rid of mktemp dependency to support AIX
The function call has been renamed to better reflect what it does, and we
reduced the number of calls from two to one in case the remote user is not
root.

This patch also fixes a string concatenation in _copy_module() that
should use os.path.join() instead.

This closes #436
2012-06-06 14:47:47 +02:00
bee04de26a Fix exception in fetch module when src or dest parameter omitted. 2012-06-05 11:35:19 -04:00
d0c4f4015c Fix remote md5 in fetch module, related to Issue #450 2012-06-05 11:35:19 -04:00
51b460e8c6 Make 'fetch' test for local directories before creating. Fixes issue #450 2012-06-05 10:38:12 -04:00
57a34d914f Merge pull request #448 from jkleint/better-ssh-error
More helpful message when ssh fails.
2012-06-01 14:49:36 -07:00
e9f770fa11 More helpful message when ssh fails. 2012-06-01 17:16:02 -04:00
9ff59090c2 capture possible chown stderr output 2012-05-31 22:19:05 -04:00
5a8e61a610 Fix parsing error in file module when returning errors. 2012-05-31 22:14:33 -04:00
1ee7ff2f7b To allow plays using different user accounts in the same playbook to function, clear the setup cache
between plays.
2012-05-31 22:08:00 -04:00
16f0d7b333 Revert "Get rid of mktemp dependency to support AIX"
This reverts commit 4ad1b57b52.
2012-05-31 21:59:31 -04:00
4886b26ea9 Merge pull request #426 from dagwieers/facts
Add BIOS information and fix VMware detection (supports both IDE and SCSI)
2012-05-31 18:45:52 -07:00
99430fa0a3 Fix for older version of sudo + some changes to imports so tests are happy 2012-05-31 21:44:56 -04:00
634cf9aaba Merge pull request #437 from dagwieers/mktemp-vanish
Get rid of mktemp dependency to support AIX
2012-05-31 18:37:28 -07:00
5c44ec440f Merge pull request #441 from jkleint/devel
Properly template list of hosts in playbooks.
2012-05-31 18:34:25 -07:00
70837469c6 Properly template list of hosts in playbooks.
In playbooks, hosts can be a YAML list. We templated the list before
converting it to a semicolon-separated string, which actually templated its
repr. This converts to a string first. A basic unit test is included.
2012-05-31 16:06:21 -04:00
a8fd6ee9c3 Get rid of mktemp dependency to support AIX
The function call has been renamed to better reflect what it does, and we
reduced the number of calls from two to one in case the remote user is not
root.

This patch also fixes a string concatenation in _copy_module() that
should use os.path.join()

This closes #436
2012-05-31 17:02:10 +02:00
6034b5b0f3 Split connection code into submodules. 2012-05-30 20:16:31 -04:00
0972b761de Leverage cowsay if installed. 2012-05-30 19:44:00 -04:00
960e7c331c Merge pull request #433 from bradobro/authorized_key
Renamed module, fixed idempotency, removed debug param.
2012-05-30 13:59:13 -07:00
18c59a774e Merge pull request #429 from sfromm/facts
Fix for get_selinux_facts
2012-05-30 13:58:03 -07:00
ab55b1a412 Renamed module, fixed idempotency, removed debug param. 2012-05-30 16:41:38 -04:00
0eca3030f4 Merge pull request #431 from bradobro/fix_tasksafterfailure
Fix to make 0.5 hosts with failures not be included in future task executions
2012-05-30 13:08:09 -07:00
c34921fe7b Removed legacy comment. 2012-05-30 15:37:14 -04:00
81591009ea Fixed Inventory.get_hosts() ignoring restriction when there are no hosts left.
get_hosts() was treating [] (meaning complete restriction, no hosts allowed)
the same as None (meaning no restriction, all hosts allowed). Fixed logic.
2012-05-30 15:34:21 -04:00
3eef7a629b Update get_selinux_facts in setup module
This switches to using selinux library calls instead of parsing the
output of sestatus.  This fixes issue #428 where the output was slightly
different than expected on F17.  Tested against debian (non-selinux),
centos5, centos6, and fedora17.
2012-05-30 08:42:05 -07:00
50ef1bb93f Add BIOS information and fix VMware detection (supports both IDE and SCSI disks) 2012-05-30 14:23:52 +02:00
af9651f015 Merge pull request #420 from bradobro/fix_no_varfiles
Test for play.vars_files == None, avoiding nasty traceback.
2012-05-29 08:32:47 -07:00
b9fd948c26 Merge pull request #421 from bradobro/fix_ignore_no_vars
Avoid traceback when vars file has no vars items. Could warn or fail instead.
2012-05-29 08:31:37 -07:00
7ea4a0e895 Avoid traceback when vars file has no vars items. Could warn or fail instead. 2012-05-29 10:23:09 -04:00
32b9ea689a Test for play.vars_files == None, avoiding nasty traceback. Ignores for now. Could warn.
Playbook test for no vars_files with len(), but that excepts if play.vars_files==None, as can happen when there's a vars_files section with no vars files listed. What is the ansible way: ignore, warn, or fail with message (instead of traceback)?
2012-05-29 10:09:38 -04:00
c03fec4864 Merge pull request #419 from jtacoma/devel
Listed python sub-packages in setup.py.
2012-05-29 04:45:29 -07:00
0f7ab7cbf5 Listed python sub-packages in setup.py. 2012-05-28 22:06:38 -04:00
f96be11a3a Merge pull request #415 from lcosmin/0.5-fixes
Removed line that caused stacktrace
2012-05-27 10:45:39 -07:00
74bfd7eb81 Removed line that caused stacktrace 2012-05-27 14:15:30 +03:00
4664e354c3 Get with_items to work with new play/task architecture. 2012-05-26 01:49:23 -04:00
ecb944892d Tweaks on previous refactoring of playbook, version bump a 0.4 reference, remove some debug, etc 2012-05-26 01:20:53 -04:00
b9b53d1941 Playbook refactoring -- work in progress. 2012-05-26 00:37:34 -04:00
cf9ddf3a30 Reorganizing file structure. Not done. 2012-05-25 19:34:13 -04:00
6f114a2e2c Delete poller code from runner.py now that it is moved. 2012-05-25 19:20:01 -04:00
db1d5b154a Fix casing/underscore convention in method name, split polling logic away from runner. 2012-05-25 19:18:02 -04:00
6d580aea02 As part of a precursor to other refactoring, make returns less list-like throughout runner. 2012-05-25 18:44:29 -04:00
da3816d370 Move version files to 0.5 for devel branch 2012-05-25 16:43:16 -04:00
abbfb20947 changelog updates 2012-05-23 20:23:46 -04:00
28b94cd2f6 Update changelog for release. 2012-05-23 19:38:59 -04:00
ac4e55aeff Merge pull request #413 from emgee/kv-equals
Allow "=" in k-v values.
2012-05-23 03:59:53 -07:00
639763c138 Allow "=" in k-v values. 2012-05-23 00:58:05 +01:00
f8abf67ebe Merge pull request #412 from mgwilliams/bug-include-handlers-templating
template path for handlers: include
2012-05-22 04:23:46 -07:00
7bbcf5d2d5 template path for handlers: include 2012-05-21 21:40:07 -07:00
1125ca548a Merge pull request #411 from mgwilliams/bug-fetch-module
Fetch module: run dwim on dest after templating, not before
2012-05-20 07:01:11 -07:00
effd931031 dim fetch dest after templating 2012-05-19 18:59:23 -07:00
8a687b77f8 Merge pull request #391 from cocoy/reload-feature
Feature: Add state=reloaded
2012-05-19 14:26:20 -07:00
8e88667f82 Fix hosts being in multiple groups, and duplicate host references in the same inventory file. 2012-05-19 17:24:54 -04:00
7302b7bbc4 Merge pull request #402 from denen99/devel
updated connection.py to have the same interface for exec_command on both LocalConnection and ParamikoConnection classes
2012-05-17 04:06:02 -07:00
3dfd240b86 updated connection.py to have the same interface for exec_command on both LocalConnection and ParamikoConnection classes 2012-05-17 00:11:50 -04:00
0db6d73523 Merge pull request #393 from bradobro/fix_usergroup
0.4 fix, Remove call to undefined debug() in module 'user'
2012-05-15 10:04:28 -07:00
7338c7e838 Merge pull request #392 from jhoekx/fix-large-output-test
If /usr/share/dict/words does not exist, use cracklib-small to test larg...
2012-05-15 10:04:06 -07:00
81866ccac4 Merge pull request #396 from jhoekx/setup-json
Arguments to setup module should be json.
2012-05-15 10:03:11 -07:00
3eff44a2b0 Arguments to setup module should be json. 2012-05-15 17:43:01 +02:00
2b8bc789c2 Remove call to undefined debug() in module 'user'.
I think when we stopped using stderr for debugging modules because
paramiko didn't like it, many modules used the idiom of defining
a debug function that used standard error. The def's and calls were
removed.

This looks like a stray debug() that didn't get removed and didn't
show up unless you alter a user's groups. If it's hit, 'user' fails
with a global undefined function error.
2012-05-15 09:19:43 -04:00
ecc0b0770c If /usr/share/dict/words does not exist, use cracklib-small to test large output. 2012-05-15 14:26:43 +02:00
25f808de8f Add state=reloaded 2012-05-15 17:28:49 +08:00
306bab93d8 Merge pull request #387 from skvidal/devel
implement --private-key for ansible - to take pem files as an optional arg
2012-05-14 15:56:27 -07:00
bddf2f9755 Merge pull request #388 from wgjohnson/devel
Devel: created get_selinux_facts
2012-05-14 15:55:42 -07:00
153df9885e Merge pull request #1 from wgjohnson/get_selinux_facts
starting fresh, doing it 'right'
2012-05-14 14:34:36 -07:00
5da7f2aff4 starting fresh, doing it 'right'
Lost commit history, but this way I'm not introducing crappy commit
history in RP
2012-05-14 14:31:53 -07:00
d80fd74b64 make sure private_key_file is hooked up in playbooks, too 2012-05-14 16:52:48 -04:00
b42628d858 hook up --private-key to the runner 2012-05-14 16:22:05 -04:00
96ef6482c5 add --private-key option and related infrastructure to make paramiko work
with a private key file - not just an agent or pw
2012-05-14 16:14:38 -04:00
8e2528eb83 Merge pull request #384 from dne/template-fixes
Template fixes
2012-05-14 05:17:54 -07:00
e4991d1a61 In the template module, use utils.template_from_file instead of reimplementing it inline 2012-05-13 20:57:01 +02:00
f8b17ef8d3 For utils.template_from_file, change 'no_engine' default to True
This now follows the other template functions, and implicitly also
fixes that playbook handler includes were still processed by Jinja2.
2012-05-13 20:42:11 +02:00
4b0480dd9d Set 'hostvars' before performing variable replacements 2012-05-13 20:36:08 +02:00
60d44e1a01 Squashed commit of the following, plus some streamlining (MPD).
commit e00368e7c65c65bed11fcaaf83fe8b093dbf492e
Merge: 2ea7110 c039aa0
Author: Michael DeHaan <michael.dehaan@gmail.com>
Date:   Thu May 10 01:43:10 2012 -0400

    Merge branch 'devel' of https://github.com/weaselkeeper/ansible into weaselkeeper-devel

commit c039aa0915
Author: Jim Richardson <weaselkeeper@gmail.com>
Date:   Fri May 11 17:55:13 2012 -0700

    cleanup and simplification of ANSIBLE_REMOTE_TMP feature

commit d87f15b796
Merge: 5917aba 4c2fd25
Author: Jim Richardson <weaselkeeper@gmail.com>
Date:   Fri May 11 17:30:16 2012 -0700

    Merge branch 'devel' of github.com:weaselkeeper/ansible into devel

commit 5917aba761
Author: Jim Richardson <jrichardson@classmates.com>
Date:   Wed May 9 11:25:45 2012 -0700

     ANSIBLE_REMOTE_TMP environment variable sets where ansible will stuf tmp files on remote host.  Default is /var/tmp for root, and $HOME/.ansible/tmp for non-root

commit 4c2fd25777
Author: Jim Richardson <jrichardson@classmates.com>
Date:   Wed May 9 11:25:45 2012 -0700

     ANSIBLE_REMOTE_TMP environment variable sets where ansible will stuf tmp files on remote host.  Default is /var/tmp for root, and $HOME/.ansible/tmp for non-root
2012-05-10 01:50:22 -04:00
ccab8ac2c0 Reinstate raw module 2012-05-10 01:14:30 -04:00
54a56e478b add fake module for 'raw' 2012-05-10 01:08:01 -04:00
45100f03b1 Default git module to master branch so branch= is not required. 2012-05-10 00:41:44 -04:00
e6416def6b update comment about branch support, now that supported 2012-05-10 00:39:15 -04:00
b7bb0ab225 Do not read user's SSH config file because only reading it partially is confusing to everyone :) 2012-05-09 23:45:37 -04:00
bfe5f4d0ed Update development changelogs 2012-05-09 23:31:58 -04:00
e060b06081 Allow hosts not in inventory to override inventory with -i "host1,host2:port" etc, which is the original intent
of --override-hosts.
2012-05-09 23:26:45 -04:00
d4f660edc3 Do not run unit tests against the EVENTS code because it is subject to change and that does not mean anything is broken.
If we want to test playbooks, it's best to test the result of a playbook to make sure it does the correct thing.  Also
remove playbook2/3 yml which were not used.
2012-05-09 23:21:44 -04:00
f8807da57b Merge pull request #363 from jhoekx/template-only-if
Fix correct variable expansion in includes only_if.
2012-05-11 15:55:09 -07:00
cd9f926b5f Fix correct variable expansion in includes only_if.
There's not need to run the complete include through the templating engine.
Several variables were not included before the conditional was evaluated.
2012-05-11 15:55:33 +02:00
dab50574e0 Merge pull request #356 from jkleint/singleton-notify
Support single handlers for notify.
2012-05-10 17:04:54 -07:00
5ebe915da3 Merge pull request #355 from jkleint/devel
Fix YAML hosts list.
2012-05-10 13:48:57 -07:00
d9427db14b Support single handlers for notify. 2012-05-10 15:37:30 -04:00
4cf0c5550e Fix YAML hosts list. 2012-05-10 15:13:32 -04:00
e5d3576e0b Merge pull request #354 from jhoekx/varreplace-list
Support list indexing in varReplace.
2012-05-10 11:12:55 -07:00
3cbc229990 Support list indexing in varReplace. 2012-05-10 19:25:28 +02:00
4819ce4989 Merge pull request #349 from jhoekx/template-unicode-fixes
Fixup unicode varReplace templating.
2012-05-10 05:05:50 -07:00
24c171a759 Merge pull request #351 from cocoy/bug-264
Fix for bug #264
2012-05-10 05:04:57 -07:00
c1fd1348b1 Fix for bug #264 2012-05-10 18:53:24 +08:00
74e21e6154 Fixup unicode varReplace templating.
The original patches should have conflicted?
53bde0bf51 vs efde61e537
2012-05-10 10:11:14 +02:00
eb41bfd3c8 Allow service 'enable' parameter be 'enabled', which is what I'd personally try to use. 2012-05-09 23:13:13 -04:00
089c75016f Merge pull request #371 from lcosmin/devel
Added the network address of an interface as an ansible variable
2012-05-11 15:21:37 -07:00
87610f2ff0 Merge pull request #370 from sfromm/issue353
Fix for issue 353
2012-05-11 11:55:18 -07:00
fa9bfbba13 Fix for sudo to another non-root user (issue #353)
mktemp creates the temp directory 700 only.  If the sudo-user is not
root, the other user will not be able to run the command (Permission
denied error).  This adds the executable bit for all on the temp
directory.
2012-05-11 11:48:31 -07:00
36639186e0 Merge pull request #362 from jhoekx/uppercase-vars
Allow camelCase variables in varReplace.
2012-05-11 10:06:21 -07:00
291de4f3b4 Merge pull request #364 from lcosmin/a5b13fe4ce7e3d3846999ae35127fbe4095ee6c3
Added parameter to the apt-get install command to prevent interactive conflict resolution
2012-05-11 10:05:44 -07:00
43083996fe Merge pull request #365 from jkleint/devel
Fix hang on large command output.
2012-05-11 09:52:35 -07:00
83b9a43e60 Add test for large output; fix indentation. 2012-05-11 11:38:15 -04:00
fba2bdcf0c Don't read from Paramiko's stderr since there isn't one. 2012-05-11 10:53:35 -04:00
c37014083a Added a new fact: network address for an interface 2012-05-11 17:17:59 +03:00
dc60f2d844 Allow camelCase variables in varreplace. 2012-05-11 14:14:53 +02:00
204bffa21e Added parameter to the apt-get install command to prevent interactive conflict resolution 2012-05-11 10:54:55 +03:00
262b35e7d3 override-hosts is gone since we can't load inventory for hosts (so it's not an whitelist), but we can
still support hosts outside of inventory if you pass in an array of hosts.  Ports are supported,
but not much other info.
2012-05-09 21:53:09 -04:00
9bb8e8e36d Don't use template syntax in non-templates, it's not the default anymore 2012-05-08 00:09:04 -04:00
cb6f0f4284 Merge pull request #346 from jhoekx/improve-varreplace
Support nested variables in varReplace
2012-05-09 17:25:07 -07:00
2bbc05185e Make no_engine the default templating action. 2012-05-09 16:15:43 +02:00
82144fd543 Support nested variables in varReplace. 2012-05-09 16:15:43 +02:00
036b779188 Merge pull request #342 from jhoekx/fix-jinja-newline
Preserve trailing newline after templating.
2012-05-09 05:33:10 -07:00
98f93fcc07 Preserve trailing newline after templating.
Jinja seems to eat trailing whitespace. A lot of tools complain about this: iptables-restore, iscsid...
2012-05-09 10:31:32 +02:00
ee34c0c154 Merge pull request #331 from mgwilliams/bug-fetch-module
Template dest and src parameters
2012-05-08 11:11:13 -07:00
adfe6b8589 Merge pull request #335 from sfromm/user
User module fix
2012-05-08 11:10:37 -07:00
73ffec9710 Move import of spwd under a try block
Resolves issue #333.  If spwd is not available, the password will
be set regardless.
2012-05-08 10:40:44 -07:00
e56023b8bb Template dest and src parameters 2012-05-08 07:12:30 -07:00
b5b60ab1b4 changelog update 2012-05-07 23:48:18 -04:00
d118cf3e31 Merge pull request #336 from sfromm/issue122
Issue122 - have all modules log to syslog
2012-05-09 17:19:24 -07:00
0fe8e51cb5 Merge pull request #337 from ordrin/devel
Support for branches in git module
2012-05-09 17:18:32 -07:00
d149c1823a Merge pull request #339 from mgwilliams/feature-unicode
Allow unicode (utf8) in templates
2012-05-09 17:16:59 -07:00
aea022b002 Apply logging of arguments to modules (issue #122)
The ohai and facter modules use /usr/bin/logger to log the fact that
they have been invoked.  I added 'import os' to the ping module
so that it could have the same syslog statements as the other modules.
I separated the condensed:
    shlex.split(open(argfile, 'r').read())
into two separate statements similar to the other modules.
2012-05-09 15:19:20 -07:00
43b1727f6a removed debug code 2012-05-08 16:05:43 -07:00
39a92f9cd6 allow unicode (utf8) characters in jinja templates 2012-05-08 16:03:51 -07:00
7ee6ae3663 removed unecessary branch addition from switchver 2012-05-08 16:24:29 -04:00
c9fafcecbe added in support for branches in git module (now takes optional "branch") 2012-05-08 16:20:08 -04:00
0894206467 Be able to use --extra-vars to set the hosts variable 2012-05-07 23:37:09 -04:00
8e8cde3a2c override hosts gone 2012-05-07 23:18:06 -04:00
07508ad535 Since host variables are becoming important, it did not make sense to sustain --override-hosts, with the ability
to create hosts that didn't have inventory information, but also existed, in various groups.
2012-05-07 23:16:20 -04:00
fc300723da Revert "fix indentation"
This reverts commit 42b55a07cd.
2012-05-07 21:46:45 -04:00
e1914a0e62 Revert "fix the get_hosts() error in get_groups returning a dict not a list of group objects"
This reverts commit f41fb90d78.
2012-05-07 21:46:36 -04:00
121d7c4ece Merge pull request #329 from skvidal/devel
inventory and indentation
2012-05-08 03:54:41 -07:00
1af461f476 Merge pull request #330 from jhoekx/yaml-inventory-list
Yaml inventory variable list
2012-05-08 03:53:46 -07:00
e09572a8e1 Inventory: undefined group -> ungrouped.
Also change test name collision.
2012-05-08 09:44:59 +02:00
d419a13ca7 Inventory: support list of vars in host. 2012-05-08 09:44:29 +02:00
9b741a1d16 Merge pull request #327 from mrproper/devel
Devel - fix issue #315
2012-05-07 18:18:00 -07:00
be32d3b00f Merge remote-tracking branch 'ansible-origin/devel' into devel
Conflicts:
	library/user
2012-05-08 10:33:18 +10:00
b460980d7d Merge pull request #324 from lcosmin/devel
Prevent facter stderr messages from confusing ansible
2012-05-07 17:07:49 -07:00
9b1add8c40 Reverting to apply correct attribution. 2012-05-07 20:07:12 -04:00
3a027c2749 Don't let facter spew things to stderr and confuse Ansible, which now blends stdout/stderr due to paramiko
pty usage.

(This commit from lcosmin, manually applied due to github crossup on my part -- MPD)
2012-05-07 20:05:10 -04:00
a5a64f089d Merge pull request #326 from pas256/patch-2
Fixing append when user already in desired groups
2012-05-07 17:01:50 -07:00
710d085def Fix non-sudo execution, add --sudo-user to ansible-playbook. 2012-05-07 19:58:16 -04:00
5059588b17 remove now useless append check in user_mod 2012-05-08 09:49:37 +10:00
2635195185 Fixes issue #315
This adds some logic when usings groups possibly in combination with append

  if just specifying groups and the current groups do not match the list
     set groups
  if specifying groups with append and any group thats not in the current groups
     set groups with -a
2012-05-08 09:43:51 +10:00
cd28d82639 fix indentation 2012-05-07 18:10:52 -04:00
279e5e4522 fix the get_hosts() error in get_groups returning a dict not a list of group objects 2012-05-07 18:09:57 -04:00
d814136ec9 Code was adding '-a' even when the user was already in the desired groups causing usermod to fail since there was no '-G' option. This is duplicate code (3 lines) from the 'group' section, so could be improved, but it works. 2012-05-07 11:35:45 -07:00
fba4d9232d Prevent facter stderr messages from confusing ansible 2012-05-07 18:08:50 +03:00
dcbe48e2d4 Update development changelog. 2012-05-06 18:34:07 -04:00
eda2333b24 Update manpage to mention -U (--sudo-user). 2012-05-06 18:32:43 -04:00
05545e3735 Fixup the previous sudo_user support patch to work with playbooks. 2012-05-06 18:24:45 -04:00
5582e6a4f1 sudo -u works now. Needs testing and maybe a bit of cleanup 2012-05-06 18:24:45 -04:00
2cd8d7b5e4 first bit of getting sudo -u functionality 2012-05-06 18:24:45 -04:00
8d39ae709c sudo -u works now. Needs testing and maybe a bit of cleanup 2012-05-06 18:24:44 -04:00
93a20a33e9 first bit of getting sudo -u functionality 2012-05-06 18:24:44 -04:00
36559badd5 Merge pull request #316 from skvidal/devel
fix up service module to make it start processes that are listed to be
2012-05-06 16:43:20 -07:00
f718fc99b7 Add missing file 2012-05-06 18:02:53 -04:00
157d21b1c3 Add tests for new advanced inventory features (groups of groups, group variables) in the default INI format file. 2012-05-06 18:01:11 -04:00
1a00e2635e Further work on making the YAML inventory parser use the new inventory objects. 2012-05-06 17:03:17 -04:00
a8f0b5fd06 Fix playbooks such that they work with external inventory scripts. We really want to find out why the 'all'
group isn't available, but group vars don't make sense for external inventory anyway.
2012-05-06 15:00:17 -04:00
256377166a Reinstate external inventory script support this time using the new more OO-ey inventory system.
Next up: YAML format.
2012-05-06 14:47:05 -04:00
5730a29814 missing files 2012-05-05 16:37:28 -04:00
0669ce858a Refactored inventory to make it object oriented, need to make YAML format and executable script
format compatible with this still, and add some tests for INI-style groups of groups
and variables.
2012-05-05 16:31:03 -04:00
39e791a1d3 Log last execution of ansible-pull to /var/log/ansible-pull.log
TODO: logrotate script and append to log instead.
2012-05-05 12:18:04 -04:00
99a7522b03 Update devel branch version in RPM spec. 2012-05-05 12:16:19 -04:00
81418afbbb fix up service module to make it start processes that are listed to be
running and to report errors from starting them
2012-05-04 01:20:51 -04:00
b300aac551 Include files are no longer to be considered Jinja2 templates, but individual THINGS
in included files can still be templated just like top level playbooks.  Resolves
some issues about statements being evaluated before facts were available
2012-05-02 00:28:19 -04:00
adca320490 Change defaults so people have to make conscious decisions about pull setup. 2012-05-01 23:25:19 -04:00
a33320d03d Add some comments to ansible-pull and the example playbook 2012-05-01 23:22:23 -04:00
e54eeb7629 Merge branch 'local' of https://github.com/sfromm/ansible into devel 2012-05-01 23:06:50 -04:00
84c9caa805 Simplify ansible-pull to just invoke ansible and ansible-playbook
This eliminates the creation of runner and playbook instances and just
invokes the processes ansible and ansible-playbook.
2012-05-02 09:51:19 -07:00
23ff967f38 Copy inventory file to client 2012-05-02 09:51:19 -07:00
672794f586 Add ansible-pull
A first stab at a pull-based model for ansible.  This does two things:
    1. Invoke the git module via Runner to set up a git repository on the
       localhost.  It sets up Runner to use transport='local' and forces
       the inventory to just 'localhost'.
    2. Run any playbooks provided.  By default, this wants to run the
       playbook local.yml.  This also sets transport='local' and sets
       the host_list to a list: localhost, fqdn, and hostname.
The reason for setting the host_list and not using override_hosts is
because there may be plays in the playbook that are not meant for a
specific host.  That is, if the git repository is for the entire site
and not host-specific, you don't want to override hosts and apply all
plays to any given host.  This has the downside of potentially running a
play three times if the play is defined for 'hosts: all'.
2012-05-02 09:51:19 -07:00
3803b27f6c Add playbook and template to set up ansible-pull
Playbook will install ansible, create directory where git checkout goes,
and set up a cron job to run ansible-pull.
2012-05-02 09:51:19 -07:00
df61a65344 Merge pull request #294 from jhoekx/inventory-hostname
Define 'inventory_hostname' variable for hosts.
2012-05-02 04:37:45 -07:00
fe90650311 Merge pull request #295 from jhoekx/user-existing-groups
Group order in user module should not matter.
2012-05-02 04:36:47 -07:00
2c8fbdbd20 Group order in user module should not matter.
Groups are not necessarily returned in the defined order, especially when the user already existed before ansible started managing the system.
2012-05-02 11:54:27 +02:00
03541baba7 Define 'inventory_hostname' variable for hosts. 2012-05-02 09:56:58 +02:00
28cf753756 changelog update 2012-05-01 23:05:40 -04:00
5b8ee8488d update development changelog 2012-05-01 23:03:00 -04:00
02efcdced6 Merge branch 'devel' of https://github.com/skvidal/ansible into skvidal-devel 2012-05-01 22:58:47 -04:00
38ea61054f if a playbook has no vars - still include the global vars 2012-05-03 15:52:02 -04:00
dc35dd99c0 inventory group 'all' variables are global variables
applied to every host and available all over
they are read in so that ones defined first can be used to define
the later ones.
2012-05-02 18:30:58 -04:00
3de61fb180 Fix: file module does not remove dangling symlinks 2012-05-01 22:56:37 -04:00
06e99ee75e Trivial fix: make PyDev happy (imports). 2012-05-01 22:56:21 -04:00
7ff1679007 Merge pull request #303 from jkleint/devel
Remove command timeout.
2012-05-03 15:45:40 -07:00
548b551bfd Merge pull request #306 from leucos/devel
Adds support for Ubuntu style update-rc.d
2012-05-03 05:37:11 -07:00
a7e4da92db Fixes _find_binaries not using globals
_find_binaries now sets the right globals
Binaries are now properly populated (reverse path/binary for loops)
2012-05-03 12:36:06 +02:00
572868c9e1 Adds support for Ubuntu style update-rc.d
Checks if update-rc.d (Ubuntu) or chkconfig (RHEL) should be used.
Adds basic bin path search for those binaries
Adds 'enable' and 'disable' options for 'enable' command since it's the
arguments that update-rc.d uses (this might be somewhat confusing to
have a command line with 'enable=enable', but probably mkes sense for
Ubuntu users).
Allows use of mixed case for 'list' and 'state' commands.
2012-05-03 08:34:36 +02:00
9ad4e7d149 Merge pull request #300 from sfromm/git
Updates for git module
2012-05-02 19:57:50 -07:00
5fe677645d Merge pull request #302 from sfromm/selinux
Set default selinux context with '_default'
2012-05-02 19:56:33 -07:00
52efc90326 Merge pull request #299 from pas256/patch-1
test-module arguments file location
2012-05-02 17:58:01 -07:00
cbbaee8a9f Remove command timeout. 2012-05-02 16:25:04 -04:00
f1b7149fcf Update how to set default selinux context for file
This removes the 'context' option and replaces it with checks for
'_default' value for seuser, serole, setype, or (maybe) selevel.
If '_default' is provided *and* there is a default context for the given
file, this will set the file context to the available default.
2012-05-02 11:38:44 -07:00
887d293a3b Add git reset --hard support to git module
Resets working tree to what is in HEAD and discards any
uncommitted changes.
2012-05-02 09:50:36 -07:00
40dbd9b3d7 Add exit_json and fail_json to git module
This adds exit_json() and fail_json() to git module.  It also sets
version to 'HEAD', if not provided.
2012-05-02 09:50:36 -07:00
f9a14cfec1 Adding a missing '~' to use the user's home directory instead of the root file system for the module arguments 2012-05-02 09:50:20 -07:00
594084bc9f Merge pull request #287 from dne/user-group-add-system
Add 'system' option for user and group modules
2012-05-01 15:54:45 -07:00
af74c741ef Merge pull request #285 from tbielawa/fixLicenseInDevel
Fix license in devel
2012-05-01 15:42:13 -07:00
27582ad313 Merge pull request #281 from dne/file-link-relative
File module fix for symlink from relative source path
2012-05-01 15:41:46 -07:00
dd380f67e6 Merge pull request #289 from skvidal/devel
service enable=on/off
2012-05-01 15:40:59 -07:00
f14c1e3e91 Async: print one poll message per polling cycle.
Instead of one per host per polling cycle.
2012-05-01 18:38:26 -04:00
029fe1273c Modify /usr/bin/ansible and playbooks to use the new async API. 2012-05-01 18:38:26 -04:00
ce9a8c9ffc Introduce Async API in Runner. 2012-05-01 18:38:26 -04:00
d96763346f Make async wrapper kill the process it supervises.
It wasn't killing the job at all before.
Indentation was 3 spaces.
2012-05-01 18:38:26 -04:00
cc13d34f91 changelog update 2012-05-01 18:38:26 -04:00
dc30b01c87 new service module handles enable=on/off for chkconfigging it on 2012-05-01 17:46:45 -04:00
a837009c5d Add 'system' option for user and group modules
Creates system accounts/groups; corresponds to the '-r' option for {user,group}add.

The option is only honored when users/groups are added, not when modified.
2012-05-01 21:38:55 +02:00
029d86be2f Include license file in MANIFEST.in. Fixes #284 2012-05-01 10:34:36 -04:00
82d3fcfd53 File module fix for symlink from relative source path 2012-05-01 03:12:13 +02:00
cfb7c94c49 Merge pull request #271 from jhoekx/group-names
Introduce group_names in template variables.
2012-04-28 13:55:08 -07:00
b87710a1df Introduce group_names in template variables.
This is a list of all the groups a host is in.
2012-04-28 18:23:44 +02:00
6341361a5b Clarify that stderr WILL crash your module. Can't redirect because if we do we lose tracebacks
from modules remotely when they fail to parse, and this is VERY useful data.
2012-04-27 01:36:31 -04:00
88a7f771cb update changelog 2012-04-27 01:27:47 -04:00
36e454c52f Because paramiko using a pty can't distinguish stderr and stdout, remove handling that
treated -D as a way to show stderr, and make sure modules don't include things on stderr.
Update docs and test module script to come into line.
2012-04-27 01:25:38 -04:00
076f1bc169 Revert "Revert "Unify normal and sudo remote command execution. Breaks stderr/stdout handling"
This reverts commit 40f603539c.
2012-04-27 00:46:26 -04:00
0d5bee2dfa Revert "Revert "Actually wait for password prompt in remote sudo execution. Totally breaks stderr debugging"
This reverts commit e16114034c.

Conflicts:

	lib/ansible/connection.py
2012-04-27 00:46:17 -04:00
40f603539c Revert "Unify normal and sudo remote command execution. Breaks stderr/stdout handling
This reverts commit 44486223ed.
2012-04-27 00:33:48 -04:00
e16114034c Revert "Actually wait for password prompt in remote sudo execution. Totally breaks stderr debugging
and various modules.

This reverts commit 6341a9547f.
2012-04-27 00:31:18 -04:00
f939f3fdaf Playbook can now take a 'name' which it will show when starting the play 2012-04-27 00:08:06 -04:00
67d1169674 fix code handling if module_vars is None 2012-04-26 23:18:29 -04:00
803672a769 Update development changelog 2012-04-26 23:12:09 -04:00
aa8ae85070 Add playbook example for first_available_file 2012-04-26 23:03:14 -04:00
3804910187 Some tweaks to first_available_file mostly in terms of error handling and not trying src= to be present
as it is not used here.
2012-04-26 23:00:33 -04:00
b56efa7809 Fix handling of list=status such that it will actually work in absense of the state command, to
make it useful from /usr/bin/ansible and the API.  Also some minor style fixes.
2012-04-26 22:43:36 -04:00
ea296e9edb Merge branch 'devel' of https://github.com/jkleint/ansible into jkleint-devel 2012-04-26 22:23:09 -04:00
47ec93b32f Merge pull request #270 from skvidal/devel
first_if_available and yum fixes
2012-04-27 18:44:48 -07:00
a3ab793ba8 fixes for yum module for rhel5 and issue 269 2012-04-27 17:38:46 -04:00
c701e59592 very minor indentation issue in librar/copy 2012-04-27 16:45:28 -04:00
72fd971822 fall through file source list: first_available_file support
add first_available_file look up to _execute_template and _execute_copy
to runner.

add this data to playbook handler so it can be included into module_vars
2012-04-27 16:43:55 -04:00
c6db4e8fa3 Get service module working with sudo, add list=status, better error messages.
When running the service module via sudo, `$PATH` didn't contain `/sbin`,
so the service binary couldn't be found.  This just runs `/sbin/service`
directly.  Output is spewed to stderr on error.

Added `list=status` to include the output of `service <cmd> status`.
2012-04-27 13:35:24 -04:00
40f5469869 minor personal style preference on 'not' 2012-04-26 20:42:20 -04:00
bee0e8db5b Merge pull request #266 from RWJMurphy/devel
[devel] ansible-playbook hangs if not passed --extra-vars
2012-04-26 19:22:22 -07:00
abf524405a shlex.split() tries to read from stdin if passed None 2012-04-27 11:25:43 +10:00
b9982fc17b Reinstate --extra-vars, which can do things in playbooks like:
ansible-playbook release-my-app.yml --extra-vars="version=123"

And make $version available in the playbook without re-editing the file
2012-04-26 19:57:07 -04:00
a0ac936a55 Merge pull request #249 from jhoekx/octal-file-mode
Return the octal mode of a file instead of decimal.
2012-04-26 16:25:39 -07:00
5aa5a48f7f Merge pull request #250 from jkleint/devel
Service module crashes if args has no "=".
2012-04-26 16:22:56 -07:00
b6bdb22341 Merge pull request #251 from sfromm/selinux
Selinux -- more fun
2012-04-26 16:20:49 -07:00
08b2f147e3 Merge pull request #252 from sfromm/facts
Use socket.getfqdn() to fix issue #243
2012-04-26 16:20:05 -07:00
a4b3b7a2cf Local connection doesn't need a port. 2012-04-26 19:19:34 -04:00
6341a9547f Actually wait for password prompt in remote sudo execution.
When running on lots of hosts with a large login banner on a slow network, it was still possible that the first recv() didn't to pull in the sudo password prompt, and sudo would fail intermittently.  This patch tells sudo to use a specific, randomly-generated prompt and then reads until it finds that prompt (or times out).  Only then is the password sent.  It also catches `socket.timeout` and thunks it to a more useful `AnsbileError` with the output of sudo so if something goes wrong you can see what's up.
2012-04-26 15:01:20 -03:00
7b41e22e56 Change to socket.getfqdn() for fqdn fact 2012-04-26 08:39:47 -07:00
5be20f8748 Update file module selinux-awareness
This adds selinux_mls_enabled() and selinux_enabled() to detect a)
whether selinux is MLS aware (ie supports selevel) and b) whether
selinux is enabled.  If selinux is not enabled, all selinux operations
are punted on -- same as if python's selinux module were not available.
In set_context_if_different(), I now iterate over the current context
instead of the context argument.  Even if the system supports MLS, it
may not return the selevel from selinux.lgetfilecon().  Lastly, this
drops selinux_has_selevel() in lieu of the current approach.
2012-04-26 07:31:25 -07:00
30ce430363 Service module crashes if args has no "=". 2012-04-26 10:48:50 -03:00
3594d1ce80 Return the octal mode of a file instead of decimal. 2012-04-26 14:55:31 +02:00
49c3ee9050 Merge pull request #247 from cocoy/devel
Do not override the value of user and hostname.
2012-04-26 05:20:04 -07:00
1220a46e3a Bugfix for issue #245.
Do not override the value of user and hostname.
Get port and identityfile only.
2012-04-26 14:46:32 +08:00
7773a62291 Development branch changelog update 2012-04-25 21:32:08 -04:00
66869c39b0 changelog and readme 2012-04-25 21:32:08 -04:00
acc6c9a9a5 Merge pull request #238 from sfromm/selinux
Fix issue #227
2012-04-25 10:33:04 -07:00
d9ee5db15a Merge pull request #237 from jkleint/devel
Unify normal and sudo remote exeuction.
2012-04-25 10:32:36 -07:00
44486223ed Unify normal and sudo remote command execution.
Commit SHA: 87b1cf45 that put temp files in `$HOME/.ansible` instead of `/home/<user>/.ansible` was producing a directory literally called `$HOME` (no expansion) with non-sudo remote execution.  I'll take the blame for this one, as `ParamikoConnection.exec_command()` was not using the shell for non-sudo commands.  This does sudo and non-sudo execution the same way, using the shell, so environment variables should get expanded.
2012-04-25 13:40:21 -03:00
5c74a524b9 Fix for issue #227
Older versions of selinux, such as that deployed on rhel5, only return a
context of user:role:type instead of user:role:type:level.  This detects
whether the tuple has three elements (old-style) or four.  If the
old-style, it keeps the secontext list at three elements.
2012-04-25 09:39:11 -07:00
bb1d72f2b4 Merge pull request #235 from skvidal/devel
fix for issue #230 - handle template taking 3  args
2012-04-25 09:05:55 -07:00
fa2aebc8a6 fix for issue #230 - handle template taking 3 args 2012-04-25 11:59:19 -04:00
283b897a1f Merge pull request #233 from cocoy/devel
Bugfix for issue #229
2012-04-25 06:17:51 -07:00
7a19a46e24 Bugfix for issue no. 229 2012-04-25 21:02:44 +08:00
87b1cf45a4 Merge pull request #226 from cread/make-osx-tests-pass
Use the $HOME env var instead of hard coding /home/<username>
2012-04-24 15:22:43 -07:00
decc46ad17 Merge pull request #224 from jkleint/devel
Support YAML list of hosts in playbook.
2012-04-24 13:58:18 -07:00
4e1bc43645 Support YAML lists of hosts in playbooks.
Reading the docs, I was a bit confused as to how to specify multiple hosts/groups in a playbook.  Being YAML, I assumed a normal YAML list would work:

    ---
    - hosts: [host1, host2]

But this crashes when inventory._matches() assumes hosts is a string.  This patch just checks if hosts is a list, and turns it into a string joined by ';'.
2012-04-24 17:54:00 -03:00
2999b6b2c4 Merge pull request #223 from jkleint/devel
runner._chain_file_module() calls .get() on error string
2012-04-24 13:52:58 -07:00
dbb4afff50 Use the /Users/cread env var instead of hard coding /home/<username> 2012-04-24 21:45:54 +01:00
b50c50748e _chain_file_module() calls .get() on error string
runner._return_from_module() normally returns a list (?) of `[str,bool,dict,str]`, but on error it returns `[str,bool,str,str]`.  runner._chain_file_module() then tries to call .get() on the third item (`data2`), which fails when it's a string.  This patch only accesses `data2` if the return value was `ok`.  It might be better to return consistent types in both cases, but I'm not sure where/how else the return value is used.
2012-04-24 17:21:01 -03:00
90a8893418 Merge pull request #220 from skvidal/devel
handle issues when the hostlist is inadvertently set executable
2012-04-24 08:19:17 -07:00
730cc89c5b Merge branch 'devel' of git://github.com/ansible/ansible into devel
* 'devel' of git://github.com/ansible/ansible: (2 commits)
  Fix tests for facter and the ansible_facts API.
  ...
2012-04-24 11:15:24 -04:00
5f1bbb1de2 Merge pull request #219 from jhoekx/revert-facts-modules
Ohai and facter modules should not use the ansible_facts API.
2012-04-24 08:05:32 -07:00
41619278e5 handle issues when the hostlist is inadvertently set executable
and/or executing it fails. This produces a nicer error message than
a traceback
2012-04-24 11:03:14 -04:00
cc579a8aa2 Fix tests for facter and the ansible_facts API. 2012-04-24 17:01:48 +02:00
d3d26d0cd6 Ohai and facter modules should not use the ansible_facts API. 2012-04-24 16:57:04 +02:00
02abb5a83b Merge pull request #217 from jhoekx/connection-fixes
Expand user in ssh identity file
2012-04-24 07:40:33 -07:00
46b3dc744e Merge pull request #218 from jhoekx/ansible-facts-fixes
Fix incorrect merge of custom-facts branch.
2012-04-24 07:38:49 -07:00
973b1fe02e Fix incorrect merge of custom-facts branch.
This fixes #216.
2012-04-24 16:11:56 +02:00
c2f1aefaf1 Expand user in SSH identity file. 2012-04-24 15:56:46 +02:00
1804df0bae Whitespace fixes in connection.py 2012-04-24 15:56:04 +02:00
80e6d83660 Fix bug in library/file 2012-04-24 08:10:50 -04:00
66ba31aeb0 Fix bug in library/file 2012-04-24 08:06:32 -04:00
3311763225 Merge pull request #213 from mcodd/devel
bugfix for ssh_config patch
2012-04-24 04:55:08 -07:00
d34160ed26 cast ssh port number as integer 2012-04-24 00:38:24 -04:00
89c013035e Merge branch 'integration' of https://github.com/cocoy/ansible into cocoy-integration 2012-04-23 21:30:44 -04:00
bced4c9db1 Merge branch 'jhoekx-custom-facts' into devel 2012-04-23 21:25:26 -04:00
49cca98f1e Merge branch 'custom-facts' of https://github.com/jhoekx/ansible into jhoekx-custom-facts
Conflicts:
	lib/ansible/runner.py
2012-04-23 21:24:52 -04:00
d60efc235d Merge branch 'jkleint-integration' into devel 2012-04-23 21:22:27 -04:00
346df537b4 Merge branch 'integration' of https://github.com/jkleint/ansible into jkleint-integration 2012-04-23 21:21:43 -04:00
d37a7115a8 Merge branch 'master' into devel 2012-04-23 21:19:10 -04:00
6ba91a715e Update README to indicate new branch name 2012-04-23 21:18:57 -04:00
7de90c4e64 Version bump for integration branch (soon to be renamed 'devel'), didn't update package
changelogs yet since this isn't released yet (but 0.3 is)
2012-04-23 21:14:48 -04:00
4d62510997 Version bump for 0.3 release 2012-04-23 21:11:43 -04:00
a8707e48e8 Fix merge issue 2012-04-23 21:06:47 -04:00
c00699d0ef Merge branch 'integration'
Conflicts:
	lib/ansible/playbook.py
	lib/ansible/runner.py
	library/apt
2012-04-23 21:05:06 -04:00
321ed53e3a Fetch module doesn't set invocation parameter as it invokes nothing, so don't let that be an error. 2012-04-23 21:02:39 -04:00
39f912899f Merge pull request #212 from sideswipewvu/debmake
Debmake
2012-04-23 17:10:13 -07:00
21269a845f debian packaging 2012-04-23 19:17:01 -04:00
e69e078569 More robust remote sudo.
The basic idea is sudo /bin/sh -c 'quoted_command'.  We use Paramiko's low-level API to set a timeout, get a pseudo tty, execute sudo and the (shell quoted) command atomically, wait just until sudo is ready to accept the password before sending it down the pipe, and then return the command's stdout and stderr.

This should be faster, as there are no unneeded sleeps.  There are no permissions issues reading the output.  It will raise socket.timeout if the command takes too long.  However, this is a per-read timeout, not a total execution timeout, so as long as the command is writing output and you are reading it, it will not time out.

Local and non-sudo commands remain unchanged, but should probably adopt a similar approach.

Since this is a significant change, it needs a lot of testing.  Also, someone smarter than I should double-check the quoting and execution, since it is a security issue.
2012-04-23 17:32:08 -03:00
2dc9a563ef Allow modules to return facts.
If the module result contains "ansible_facts", that will be added to the setup
cache.
2012-04-23 21:28:12 +02:00
c844a2d072 Fix to skip /.ssh/config if don't exist rather than raise an error. 2012-04-23 14:32:57 +08:00
645b7a2dff Add .ssh/config support 2012-04-23 09:48:42 +08:00
ec56b30248 Merge pull request #198 from dne/apt-default-release
Implemented optional "default-release" argument for apt module
2012-04-22 16:42:01 -07:00
d5f3760acc Add optional "default-release" argument for apt module
The value is passed to apt-get's "-t" option. Useful for installing backports, e.g.:

  ansible webservers -m apt -a "pkg=nginx state=latest default-release=squeeze-backports"
2012-04-23 00:17:07 +02:00
1c5bcb606e Merge pull request #197 from sfermigier/master
Add missing deps in setup.py
2012-04-22 07:42:17 -07:00
f7469f8704 Add requirements in setup.py. 2012-04-22 16:33:19 +02:00
9dc1b6d79d Merge pull request #192 from sfromm/selinux
Update secontext behavior in file module
2012-04-22 05:09:38 -07:00
bcfa6a7865 Add another example to file_secontext.yml
Demonstrate what happens when there is no default context in the policy.
2012-04-22 00:14:40 -07:00
0f044e64f8 Add example playbook of file module's selinux capabilities 2012-04-21 23:30:08 -07:00
be58995278 Add context=default option to file module
This adjusts behavior of file module such that removal of se* option
does not revert the file's selinux context to the default.  In order to
go back to the default context according to the policy, you can use the
context=default option.
2012-04-21 23:27:34 -07:00
e3967af656 Merge pull request #191 from dne/apt-install-version
Apt module support for installing/removing specific version of package
2012-04-21 18:38:11 -07:00
ec4d5fa287 Add apt module support for installing/removing specific version of package
This uses standard APT syntax, e.g.:

    ansible webservers -m apt -a "pkg=nginx=1.1.19-1 state=installed"
2012-04-22 02:48:58 +02:00
533c2c6126 Make it possible to use facts from hosts in templates for other hosts.
It works like this:

{{ hostvars['127.0.0.1']['ansible_eth0']['ipv4']['address'] }}
2012-04-21 12:45:37 -04:00
767282df2a Small style fixes for indentation and spacing. 2012-04-21 12:06:54 -04:00
e36a52f06c Ignore pyflakes E261 style output (multiple spaces before inline comment) 2012-04-21 12:05:15 -04:00
edb8988e8e Unused imports 2012-04-21 12:04:21 -04:00
bed5da6086 Remove unused assignment 2012-04-21 12:03:29 -04:00
ddc0342920 Unused import 2012-04-21 12:01:37 -04:00
1e7b60b9a5 Unused import 2012-04-21 12:01:26 -04:00
9d0f2a6e9b Unused import 2012-04-21 12:01:15 -04:00
f72114c657 Move setup.py stuff back to top level as "make sdist" was not working, plus, it makes sources cleaner. 2012-04-21 11:56:06 -04:00
a8d748220b Don't prompt for password info if the pattern wouldn't have matched any hosts. Also convert
Runner() usage to the new inventory model.
2012-04-21 11:46:32 -04:00
3081bb93f1 Use /var/tmp for root by default to avoid /tmp being mounted noexec, and segregate tmp files for other users
into their home directories.
2012-04-21 11:38:39 -04:00
3d72260887 Make it such that modules with no arguments work fine in playbooks (like ping, which is
non-sensical, but also if the user wrote a module that took none)
2012-04-21 11:26:48 -04:00
c0c691089d Fix bug in src. Should not code this early :) 2012-04-20 08:09:43 -04:00
7794493952 Don't try to expand path for None values 2012-04-20 08:02:12 -04:00
445e48b991 Call os.path.expanduser in modules so things work as expected even when using ./hacking/test-module script 2012-04-20 07:57:39 -04:00
c6b8e1621d A better fix for slurp, expand path in the module. 2012-04-20 07:54:38 -04:00
13ba31231e Fixup slurp module usage when not running as root, fix error handling path in slurp module. 2012-04-19 11:38:44 -04:00
70734f5968 Update bin/ansible to fix usage of inventory API + no more verbose option 2012-04-19 09:21:21 -04:00
8a433ecb96 Merge branch 'align-vars-syntax' of https://github.com/jhoekx/ansible into jhoekx-align-vars-syntax 2012-04-19 09:15:48 -04:00
d4a5c4aee1 Merge pull request #181 from leucos/integration
Adds PKGBUILD file for Arch linux
2012-04-19 15:57:22 -07:00
4f0ed20295 Merge pull request #182 from tbielawa/moveManifest
Move manifest
2012-04-19 15:56:46 -07:00
3a0c5c0b7b Merge pull request #183 from dne/apt-fixes
Apt module spelling fixes
2012-04-19 15:23:22 -07:00
626e8f3543 Fix two misspellings of the apt module's "fail_json" function 2012-04-19 23:40:44 +02:00
516df5f64f Move packaging related things out of the root directory:
Distutils MANIFEST.in, setup.py -> packaging/distutils/ directory.
2012-04-19 13:50:09 -04:00
3fae2ea8a4 Fixes Arch PKGBUILD dependencies
Adds missing depends (python2-yaml helps...)
Adds missing build deps (fakeroot)
2012-04-19 19:26:41 +02:00
a672a5549a Adds ArchLinux build file
Adds PKGBUILD file required to build Arch Linux packages
2012-04-19 15:30:26 +02:00
86878be288 Backport typo fix from integration branch for apt module error path. 2012-04-19 09:14:43 -04:00
4578b17213 Add missing file (slurp) used by templates in non-playbook mode. 2012-04-19 09:03:40 -04:00
cdb8213dcc Supported 'listed' vars in playbooks. 2012-04-19 09:40:17 +02:00
903e4f6eae Support dicts in inventory vars. 2012-04-19 09:40:17 +02:00
51dfb6f88c Add PyYAML requirement 2012-04-19 00:14:37 -04:00
b70ed206c6 Add debian build notes... makefile target pending, some changes to instructions possibly
needed due to moving into packaging/debian?
2012-04-18 23:26:08 -04:00
645e7afaa6 Make note that the integration branch is the development branch (will rename later) 2012-04-18 23:21:55 -04:00
5bf32f9c90 Add gentoo packaging pointer 2012-04-18 22:51:54 -04:00
8539f4926a Move debian under packaging subdirectory 2012-04-18 22:50:09 -04:00
9cd492befe make all templating happen locally, so no jinja2 deps are ever required 2012-04-18 22:43:17 -04:00
30d06dbcea Don't force down ansible facts back to setup, the setup module won't like parsing them on input and that
data is already there.
2012-04-18 22:23:33 -04:00
5fa3d9b148 Teach playbooks to template locally to eliminate the need for Jinja2 on remote nodes.
You still need jinja2 if using /usr/bin/ansible vs /usr/bin/ansible-playbook though
this could change later by fetching the ansible file with a 'slurp' module.
2012-04-18 22:19:25 -04:00
9c64ceb0f8 manpages and control file packaging changes 2012-04-18 21:29:08 -04:00
b501bbfb69 format change to control file 2012-04-18 21:29:08 -04:00
49ca5ba9f2 modified debian files 2012-04-18 21:29:08 -04:00
4f6be6e35b Initial debian packaging 2012-04-18 21:29:08 -04:00
f4132a0162 Merge pull request #160 from sfromm/facts
Facts
2012-04-18 18:28:48 -07:00
da0209dbc4 The fetch module really should preserve the whole directory structure being fetched to allow subsequent calls,
particularly in playbook, to recreate the host tree structure.  Making it thus.
2012-04-18 21:12:48 -04:00
2659c38720 Merge pull request #173 from tbielawa/manifest
Manifest
2012-04-18 13:55:03 -07:00
2beb6592c7 Update spec file location in MANIFEST.in 2012-04-18 16:41:11 -04:00
d79e59787a Merge pull request #166 from tbielawa/integration
Integration
2012-04-18 08:17:23 -07:00
66f294d5c1 Finish up moving spec file 2012-04-18 11:00:59 -04:00
f408060275 Merge pull request #163 from jhoekx/template-copy-args
Template the source file of the copy module.
2012-04-18 07:55:42 -07:00
0a70106b0f Move ansible.spec into subdir. Closes #162 2012-04-18 10:48:37 -04:00
22ff8282a8 Template template module source. 2012-04-18 14:26:33 +02:00
b678cf783c Template the source file of the copy module. 2012-04-18 11:40:15 +02:00
8da67ea4f5 Merge pull request #161 from sfromm/selinux
Rename serange to selevel to be consistent with selinux docs
2012-04-17 17:27:13 -07:00
28895f6615 Rename serange to selevel to be consistent with selinux docs 2012-04-17 17:12:09 -07:00
0c38bb2fd0 Add native facts to library/setup
This collects various facts from the host so that it isn't necessary to
have facter or ohai installed.  It gets various platform/distribution
facts, information about the type of hardware, whether a virtual
environment and what type, assorted interface facts, and ssh host public
keys.  Most facts are flat.  The two exceptions are 'processor' and all
interface facts.  Interface facts are presented as:
    ansible_lo : {
        "macaddress": "00:00:00:00:00:00",
        "ipv4": { "address": "127.0.0.1", "netmask": "255.0.0.0" },
        "ipv6": [
                    { "address": "::1", "prefix": "128", "scope": "host" }
                ]
    }
2012-04-17 16:59:23 -07:00
de70277173 No need to save setup files to disk, this is what SETUP_CACHE effectively does. 2012-04-17 19:39:11 -04:00
d7ad85340b Merge pull request #157 from tbielawa/packaging
Packaging
2012-04-17 08:41:35 -07:00
621dc83ac2 Fix man page option description indentation. 2012-04-17 11:39:51 -04:00
401ce5eb0d Merge pull request #156 from tbielawa/packaging
Packaging fixes
2012-04-17 08:08:54 -07:00
ff1b200644 Merge pull request #155 from jhoekx/inventory-error-fixes
Inventory: AnsibleError is not global...
2012-04-17 08:08:00 -07:00
e9e8417735 Update hacking with MANPATH. Correct hacking README details. 2012-04-17 10:53:32 -04:00
3f9a41b22d Docs build using version in the VERSION file.
Makefile upgraded with comments and some simplifications.

Remove useless 'PHONEY' and 'VPATH' directives from Makefile
2012-04-17 10:45:17 -04:00
d43cf592eb Renaming asciidoc sources to .in.
Change manpages to build from *.in and add comments about how the make
targets work.
2012-04-17 10:31:40 -04:00
b4ca288a7b Set %config(noreplace) on /etc/ansible instead of /etc/ansible/* so
the directory is removed on uninstall and its contents are considered
config files.
2012-04-17 10:31:40 -04:00
2d26d1fd71 Update %description and auto-add version to manpages
Conflicts:

	docs/man/man1/ansible-playbook.1
	docs/man/man1/ansible.1

Update %description
2012-04-17 10:31:34 -04:00
9316a504c5 Fixup specfile 2012-04-17 10:30:44 -04:00
c9ab5ebc6b Fix FSF address in virt header. 2012-04-17 10:30:44 -04:00
38128c4720 Remove asciidoc builddep 2012-04-17 10:30:44 -04:00
aa555b8b16 Inventory: AnsibleError is not global... 2012-04-17 15:29:59 +02:00
ff99a22e6c Merge pull request #154 from jhoekx/test-otheros
Fix tests on other distributions
2012-04-17 06:15:55 -07:00
6f09b41eb5 Don't test modules that aren't present.
Also account for path difference in sleep in different distributions.
2012-04-17 13:30:30 +02:00
9e0b7ee629 template: expand path if metadata is in user home. 2012-04-17 11:16:53 +02:00
da6cb1ca6e Less scrolling over constructor params now that documentation follows 2012-04-16 23:51:03 -04:00
3f26a1c7f6 verbose option is not being used, so remove it. debug variable still exists. 2012-04-16 23:47:41 -04:00
78b5cd64d0 Add pydoc for constructors. Some arguments can be trimmed as we plan to remove the need for them (like setup_cache and
maybe module_vars) with various pending refactoring.
2012-04-16 23:45:15 -04:00
377488aa2a remove --extra-vars since the new YAML inventory stuff wasn't quite using it as designed and it is getting hard to follow
with that and SETUP_CACHE etc
2012-04-16 23:06:27 -04:00
9ce27be878 Remove extra_vars tests 2012-04-16 23:03:04 -04:00
b24ec71ca3 Update docs to reflect --port going away and --sudo and --remote-user being added to ansible-playbook 2012-04-16 22:22:37 -04:00
35fdf6636b Allow --user for playbooks, no need for port setting as can specify in inventory file now. 2012-04-16 22:15:55 -04:00
8a027415d0 Port shouldn't be in this example 2012-04-16 22:04:23 -04:00
08468dcb0c Fixes to make ports DWIM. 2012-04-16 21:52:15 -04:00
957867e088 Merge branch 'yaml-inventory' of https://github.com/jhoekx/ansible into jhoekx-yaml-inventory
Conflicts:
	lib/ansible/runner.py
2012-04-16 21:14:44 -04:00
a5cb16c9d7 Use correct user to determine host files path, do not reuse .ansible
which is already taken for ansible management ops.
2012-04-16 21:12:54 -04:00
6307267cf3 As part of the support for access to external nodes information, save fact data into /var/lib/ansible/setup_data
OR a per-user directory when running from playbooks.  Technically this info is also available via the SETUP_CACHE
but that is a bit more complex of a construct and it would be better to not cross the streams.
2012-04-16 21:12:54 -04:00
8c3206c99f Return a copy of the host variables. 2012-04-16 10:59:34 +02:00
961ccdb2f4 List hosts in no group in the ungrouped group. 2012-04-16 10:55:08 +02:00
3a24aa9a70 Add YAML inventory format.
See test/yaml_hosts for an example.

Hosts can be part of multiple groups.

Groups can also have variables, inherited by the hosts.
There is no variable scope, last variable seen wins.
2012-04-16 10:38:35 +02:00
54f4526160 Export SSH port number as host variable. 2012-04-16 09:16:29 +02:00
f04041b37d Ignore port numbers in simple inventory format 2012-04-16 09:16:29 +02:00
746f1b92ae Reimplement the class method on Runner. 2012-04-16 09:16:29 +02:00
195e6d617b Add tests for Inventory class. 2012-04-16 09:16:29 +02:00
c5cae87eca Refactor inventory code out of Runner.
This introduces the Inventory class.

Playbook uses the internals of Runner to limit the number of hosts to poll
asynchronously. To accomodate this, Inventory can be restricted to specific
hosts.
2012-04-16 09:16:28 +02:00
80a7164057 Merge pull request #145 from rlewczuk/master
merge of: removing executable bits from py files, adding Eclipse/PyDev to .gitignore
2012-04-15 05:08:30 -07:00
d8f9d7c6c9 Use correct user to determine host files path, do not reuse .ansible
which is already taken for ansible management ops.
2012-04-14 16:08:04 -04:00
a87c77958c clean exec bits from lib/ansible/*.py, ignore Eclipse/PyDev files 2012-04-14 21:35:51 +02:00
3454fa9950 As part of the support for access to external nodes information, save fact data into /var/lib/ansible/setup_data
OR a per-user directory when running from playbooks.  Technically this info is also available via the SETUP_CACHE
but that is a bit more complex of a construct and it would be better to not cross the streams.
2012-04-14 11:58:08 -04:00
31d4ee32d1 Looping! With items! See examples/playbook/loop_with_items.yml for details 2012-04-14 09:55:24 -04:00
579bd1d417 manpage updates 2012-04-13 19:40:40 -04:00
347637339c Merge remote branch 'public/integration' into integration 2012-04-13 19:33:57 -04:00
c3cad50075 Update manpages, fix missing variable assignment 2012-04-13 19:33:19 -04:00
f2465e0571 Add support for specifying sudo passwords to both ansible & playbook. Nopasswd sudo is no longer required. 2012-04-13 19:06:11 -04:00
aaecf70586 Merge pull request #143 from jhoekx/fix-ansible-async
/bin/ansible async fixes
2012-04-13 07:18:23 -07:00
d366885367 Stop async /bin/ansible if completed on all hosts. 2012-04-13 15:32:41 +02:00
a975852f66 Module_arguments can be unicode. 2012-04-13 15:32:33 +02:00
b475294564 Merge pull request #142 from makuk66/integration
comment typos
2012-04-13 05:44:38 -07:00
f936616f91 comment typos 2012-04-13 11:19:36 +01:00
788d2bf8d1 Allow playbook to take --connection=local for easier reuse of playbook in kickstart w/o specifying connection: in
the playbook itself.  Both work.
2012-04-12 22:38:19 -04:00
6b50078881 Add defaults to command help to avoid a certain class of user questions. 2012-04-12 21:30:49 -04:00
6129574290 Include default value for connection in help. 2012-04-12 21:20:37 -04:00
09a0b9bea8 If the module return is not parsed, always show stderr information even if -D (debug)
is not specified.  This will help for users that don't have prerequisites installed
(like python-simplejson on old RHEL) and do not know to run -D.
2012-04-12 21:18:09 -04:00
a5df306aa3 Merge pull request #132 from sfromm/localconnection
Make use of LocalConnection explicit
2012-04-12 17:40:03 -07:00
ecef667134 Merge pull request #136 from sfromm/selinux
Add selinux support to file module - issue #46
2012-04-12 17:39:22 -07:00
0675f2511b Merge branch 'master' into localconnection
Merge the SortedOptParser bits and debug attribute commits into
localconnection.

Conflicts:
	bin/ansible
	lib/ansible/playbook.py
	lib/ansible/runner.py
	lib/ansible/utils.py
2012-04-12 11:18:35 -07:00
1e5d34ba35 Add selinux support to file module
This adds the options: seuser, serole, setype, and serange to the file
module.  If the python selinux module doesn't exist, this will set
HAVE_SELINUX to False and punt in the related modules.

This takes the options the user provides and applies those to the
default selinux context as provided from matchpathcon().  If there is no
default context, this uses the value from the current context.  This
implies that if you set the setype and later remove it, the file module
will rever the setype to the default if available.
2012-04-12 10:45:31 -07:00
08c593bee1 Warn if no hosts matched 2012-04-11 21:05:46 -04:00
f3489a53cd English error messages if src and dest are left off the copy, template, or fetch modules 2012-04-11 20:57:41 -04:00
ff5d329374 Yum package state defaults to installed 2012-04-11 20:47:38 -04:00
8152e44efd Also allow the apt module to use package or name as an alias for 'pkg' 2012-04-11 20:45:14 -04:00
b418632a8d Allow yum module to use package or name as an alias for 'pkg' 2012-04-11 20:44:15 -04:00
529a194950 Upgrade apt message if no python-apt to intercept potential user questions. 2012-04-11 20:31:24 -04:00
b9e3b053f9 Simplify playbook tests so things are not timing dependent in the poll section, which is leading to tests
not being consistent between runs, even though there wasn't an error.  Now we'll just check the final change
counts, which should be just as solid and lead to less churn in the events file.
2012-04-11 20:27:17 -04:00
a0480a1bc5 Block some paramiko warnings that are not relevant. 2012-04-11 20:20:55 -04:00
e103bdda93 Rename test class to match what it is testing 2012-04-11 20:16:28 -04:00
95e045d153 Remove remote logging as we're going to move this logging to the modules for performance reasons. 2012-04-11 20:15:17 -04:00
245aa9bf8e Some tweaks to the fetch module. 'err' return was for stderr, so that should be empty string.
Some minor code shortening.  Added a test to TestRunner.
2012-04-11 20:12:01 -04:00
62224271e9 Merge pull request #131 from mgwilliams/feature-fetch-module
Fetch Module
2012-04-11 16:55:51 -07:00
40fc9a3249 Update playbook to be transport aware
This adds transport variable to playbook.py.  It can be set with
'connection' in the playbook file.
2012-04-11 09:39:04 -07:00
b506567c37 Update ansible.1 manpage for option -c, --connection 2012-04-11 09:32:29 -07:00
31d3f52b28 fetch to host specific directory 2012-04-11 09:14:36 -07:00
611e3fec4c fetch 'module' -- working with paramiko and local connections 2012-04-10 20:23:25 -07:00
51e4faf7aa Update test file -- we probably should not include the poll as the number of polls is changing between test
runs (that's ok, it's not intended to be realtime accurate)
2012-04-10 21:17:02 -04:00
4c75b7f3ad Merge commit '8ae71cc' into integration
Conflicts:
	bin/ansible
	bin/ansible-playbook
	lib/ansible/utils.py
2012-04-10 21:13:01 -04:00
1d75a29ec9 Allow variables coming in from the playbook and the API to be expressed as dictionaries throughout their full life cycle
such that nested data can be made available in templates and playbooks.
2012-04-10 20:58:40 -04:00
fdee1d3459 Add support to bin/ansible for --connection option 2012-04-10 16:28:43 -07:00
b5061bb62e Verify that effective uid == remote_user when transport is local
Raise exception if effective uid of process is not the same as
remote_user.
2012-04-10 16:27:19 -07:00
5d74fedeb9 Update Runner to default to C.DEFAULT_TRANSPORT
This also uses self.transport when instantiating Connection object.
2012-04-10 16:17:39 -07:00
1391481523 Add support for -c, --connection argument to specify connection type
Adds support to specify connection type to use.  The option -c,
--connection is added when connect_opts is set to True.  connect_opts is
added to make_parser() and base_parser_options().
2012-04-10 16:13:18 -07:00
896f8de446 Add DEFAULT_TRANSPORT and DEFAULT_TRANSPORT_OPTS constants
DEFAULT_TRANSPORT is set to paramiko.  DEFAULT_TRANSPORT_OPTS is a list
of possible transport options; it is set to local and paramiko.
2012-04-10 16:09:20 -07:00
9213cf896e Change to transport is local *and* is localhost
Connection.connect() now requires that, in order to use LocalConnection,
you specify transport is local and that the hostname is localhost.
2012-04-10 13:20:03 -07:00
8ae71cc7b1 go back to using a normal optparser to add options instead of the dict
interface.

add very small subclass of OptionParser to sort the options so mdehaan is happy
2012-04-10 13:51:58 -04:00
5a4d4bc051 Added usage info to bin/ansible-playbook, now shows options on bare command line. NOTE: bin/ansible outputs usage to stdout, bin/ansible-playbook to stderr. Should they be consistent? 2012-04-10 15:17:25 +00:00
74b26da9ac began playbook tweak 2012-04-10 14:45:44 +00:00
f840c0d167 Wired in Michael's usage string optparse style. 2012-04-09 23:12:05 +00:00
6749903e57 Allow explicit request of the local connection. 2012-04-07 14:51:00 -04:00
eed4a2b717 Update tests to use 127.0.0.2 to bypass the smart "use local connection" code, and also to reflect
that commands now trigger change events.
2012-04-06 12:11:25 -04:00
da9d4eb29a Merge pull request #123 from sfromm/localconnection
Add LocalConnection class to connection.py
2012-04-07 11:41:13 -07:00
70a3fab79f Add LocalConnection class to connection.py
This creates a LocalConnection class for the case when operating on the
localhost.  If the host, argument to Connection.connect(), matches
127.0.0.1, localhost, or the name of the host as returned by
os.uname()[1], Connection.connect() will opt to use LocalConnection
instead of ParamikoConnection.  LocalConnection implements connect(),
exec_command(), put_file(), and close().
2012-04-06 16:38:27 -07:00
38c7f1db4d Added 'creates=filename' to the shell/command module, which can skip command execution if a file
already exists
2012-04-06 11:16:53 -04:00
7b9856bc0e Modification on top of skvidal's common options patch to keep options to command line tools sorted. 2012-04-06 10:59:15 -04:00
8e1f24b822 Amend option descriptions. 2012-04-06 10:18:52 -04:00
3c64292de5 Merge pull request #106 from skvidal/master
3 fixes/changes
2012-04-05 14:17:59 -07:00
7e50d170a8 move the bulk of the opt parsing out of ansible/ansible-playbook and into utils
for other scripts to use.
2012-04-05 17:06:23 -04:00
254f98bdf5 handle incorrect commands w/o '=' slightly better 2012-04-05 17:03:36 -04:00
2df6399176 make dist be ?dist so if it is not there you don't get any noise
as per fedora usage guidelines
https://fedoraproject.org/wiki/Packaging:DistTag
2012-04-05 13:28:56 -04:00
594b16a78b Merge pull request #114 from jhoekx/module-vars
Introduce task local variables.
2012-04-04 17:21:46 -07:00
c1461bc185 Merge pull request #120 from skvidal/master
virt module + idempotents (state=[shutdown|running])
2012-04-04 17:11:04 -07:00
66f9a697ed edit comment to reflect real args 2012-04-04 17:25:02 -04:00
8a61794234 add idempotent and ='s separated parameters to virt module to make people happy 2012-04-04 17:17:29 -04:00
9a84e30053 Merge pull request #115 from jeckersb/dist-in-release
Update spec to include %{dist} in Release
2012-04-04 12:32:49 -07:00
415f4baa15 add virt module for libvirt interaction/support 2012-04-04 14:30:24 -04:00
4a2259bab1 Update spec to include %{dist} in Release 2012-04-04 10:38:03 -04:00
ab86726a15 Introduce per task variables and push them to templates. 2012-04-04 13:39:03 +02:00
edd5baad8b Refactor _transfer_argsfile to generic string transfer function. 2012-04-04 13:36:21 +02:00
09e690fd7c Indentation error causing forks to not be parallel. Ahem :) 2012-04-03 20:20:55 -04:00
32f574b24d Merge pull request #113 from mgwilliams/bugfixes-apt-module
apt module bugfix
2012-04-03 17:08:37 -07:00
778fe8755f Merge pull request #110 from jhoekx/remote-vars
Always add vars to a play.
2012-04-03 17:07:00 -07:00
eae1fdd734 Merge pull request #109 from jeckersb/version-0.0.2
Bump release to 0.0.2
2012-04-03 17:06:09 -07:00
3c314143c4 Merge pull request #108 from jeckersb/pep8-fix
pep8 fix
2012-04-03 17:05:48 -07:00
c819f1715e apt module bugfix 2012-04-03 12:52:08 -07:00
e38ae18627 Always add vars to a play.
A play without vars section would fail to use variables given in an include. They would be added to the dict returned by play.get, but the dict would not be added to the play.
2012-04-03 17:35:05 +02:00
82b781925c pep8 fix 2012-04-03 09:58:00 -04:00
5aad4bacc7 Bump release to 0.0.2 2012-04-03 09:51:05 -04:00
02d5e831ce have apt module raise an error if apt cannot be imported 2012-04-02 20:54:31 -04:00
2cb89c12e3 Add documentation for vars_prompt 2012-04-02 20:37:59 -04:00
88dab0db2d Add prompts example 2012-04-02 20:35:51 -04:00
f92760de6c If missing state or name or list parameter on yum module, raise error explaining what is up. 2012-04-02 20:12:35 -04:00
2a2b5ed73c Polling timing change results in need to change events file for tests. 2012-04-02 20:10:06 -04:00
4792021f47 Fix syntax error in user patch 2012-04-02 20:08:40 -04:00
e5d5b072db Merge remote branch 'public/integration' 2012-04-02 20:02:46 -04:00
6472105496 Merge pull request #105 from skvidal/master
log a bit more and var_prompts
2012-04-02 17:00:36 -07:00
c2e2a47899 Merge pull request #100 from mgwilliams/file-module-bugs
file module: mode formatting & directory ownership
2012-04-02 16:59:55 -07:00
105a43a316 Merge pull request #104 from mgwilliams/feature-apt
improvements to apt module
2012-04-02 16:59:28 -07:00
17a3ce1278 Merge pull request #102 from mgwilliams/file-symlinks
support for 'link' state in file module
2012-04-02 16:58:58 -07:00
59a1ff31db Merge pull request #103 from jeckersb/no-shebang-on-callbacks.py
Remove shebang from callbacks.py
2012-04-02 16:58:41 -07:00
2f1f0d44b4 Merge pull request #106 from mgwilliams/honor-hostfile-returncode
raise error if executable host file execution fails
2012-04-02 16:58:20 -07:00
1c5af046c7 Merge pull request #105 from mgwilliams/bug-extra-vars
bugfix for extra-vars
2012-04-02 16:57:44 -07:00
461a4e78c2 raise error if executable host file execution fails 2012-04-02 15:00:28 -07:00
c742b8eb0b bugfix for extra-vars 2012-04-02 14:57:37 -07:00
bcef25f7eb add vars_prompt to playbooks
- this allows some vars to be prompted for at the start of the playbook
  setup
- defaults to no output since this would mostly be used for passwords
2012-04-02 17:46:02 -04:00
9fdd7a837e improvements to apt module: state=latest, update-cache=yes|no, purge=yes|no 2012-04-02 14:42:54 -07:00
2dad8cc27c Remove shebang from callbacks.py
It is not +x, and has no __main__.  It draws ire of rpmlint.
2012-04-02 17:19:05 -04:00
04aecdcf34 before we run the actual module/command - emit what that would be to the logs 2012-04-02 13:29:12 -04:00
ae38ee0b3f protecting against replacing file or directory with link 2012-04-02 10:21:14 -07:00
25eedcdb43 Merge pull request #104 from skvidal/master
yum module fix - catch more errors
2012-04-02 09:52:37 -07:00
08163c6251 catch all exceptions and emit sensible errors - if we have a config file error this lets us know about it 2012-04-02 12:33:46 -04:00
c5a02048d2 file module: mode formatting & directory ownership 2012-04-01 16:44:08 -07:00
24f61f1583 "link" state in file module 2012-04-01 16:10:23 -07:00
5764ccdb0e Eliminate possible race condition in async_wrapper where ansible deletes the argsfile while the module
is still kicking off.  Should not happen except in modules that are somewhat slow to load and probably
can be fixed better than the included sleep, i.e. some IPC communication that the process has
launched and is ok to exit.  This works pretty well for now though.
2012-04-01 02:35:19 -04:00
2511992659 Surface module debug (-D) to /usr/bin/ansible also 2012-03-31 11:45:29 -04:00
d7e5b390d5 Patch process info. 2012-03-31 11:38:06 -04:00
96a22b582b Disable ohai test as it's hanging for whatever reason, but doesn't do this in real life.
Run tests in verbose mode.
2012-03-31 11:18:26 -04:00
4a8dc50249 Merge pull request #97 from jhoekx/stderr-logging
Stderr logging
2012-03-31 08:01:48 -07:00
b4a07fea3b Merge pull request #98 from jhoekx/no-touch-file
Make copy module idempotent.
2012-03-31 05:21:20 -07:00
38a33c54a6 Make copy module idempotent.
Check md5sum before overwriting a file. Unconditionally copying changes the timestamp.
2012-03-31 13:59:41 +02:00
ba7d0b8cec Merge pull request #96 from jhoekx/playbook-defaults
Correct default user and port in playbook.
2012-03-31 04:53:53 -07:00
a6b8973938 Change debug flag to D instead of d.
d would be useful for --dry-run.
2012-03-31 12:48:19 +02:00
d9a16a3d67 Add --debug option to ansible-playbook.
This prints remote module stderr on local stderr.
2012-03-31 09:32:13 +02:00
74ae4b29ad Add a debug attribute to Runner/Playbook.
This prints the stderr of the executed modules on local stderr.
Most methods on Runner now return a fourth "err" parameter.
2012-03-31 09:32:13 +02:00
fb1a313974 Correct default user and port in playbook.
They are set in the constructor.
2012-03-31 09:11:07 +02:00
6275e57718 pyflakes: Unused import / unused var 2012-03-30 23:04:02 -04:00
d86dad76ba Be more flexible about where the service binary lives for better cross platform support. 2012-03-30 22:57:26 -04:00
9569be8bdb Need for quoting/unquoting problems go away once module_args are all treated as strings throughout. 2012-03-30 22:52:38 -04:00
3ded27fe35 Treat module args as strings everywhere to avoid unneccessary shlex and requoting 2012-03-30 22:47:58 -04:00
f11de2f5c9 --extra-vars option for ansible-playbook
Conflicts:

	lib/ansible/playbook.py

Removed unneccessary shlex and replaced with basic split, some repurcussions in runner
that can be eliminated once we consistently pass args as a string (soon).
2012-03-30 22:29:06 -04:00
6db87a5018 Minor style fix, update test to match quote fix patch 2012-03-30 21:57:34 -04:00
ec6f488d1f shell quoting fixes
(edited author's original commit comment -- MPD)
2012-03-30 21:57:33 -04:00
99d5796605 support for quotes strings in command module (via posix=False), related fixes to keep other things from breaking 2012-03-30 21:57:33 -04:00
f82cc59d43 Merge pull request #92 from jhoekx/fix-template-exception
Fix except statement in template module.
2012-03-30 04:50:14 -07:00
12d01a3444 Fix except statement in template module.
Python 2.4 (RHEL 5) does not know 'as'.
2012-03-30 11:46:30 +02:00
2372a3b734 Sudo support operational in both playbooks and main program. Implementation could use some cleanup. 2012-03-29 22:59:29 -04:00
81e3496037 Added preliminary support for --sudo to ansible, playbook support and further testing pending. 2012-03-29 01:37:06 -04:00
011a0ecbdf Merge pull request #90 from tbielawa/except
Fix exception catching for py2.4
2012-03-29 11:20:22 -07:00
5261d14aef Fix exception catching for py2.4 2012-03-29 14:19:09 -04:00
72cc99722d sudo tweaks 2012-03-28 23:30:31 -04:00
7ae75eb14b Default sudo is false 2012-03-28 22:55:59 -04:00
c2a6e2f97b Work in progress on sudo, hitting some issues with paramiko saying the connection is closed. 2012-03-28 22:51:16 -04:00
7133734d87 Remove debug statements (WIP on sudo) 2012-03-28 20:58:34 -04:00
4971101f27 Handler doesn't take this param. 2012-03-28 20:33:44 -04:00
706ba9fd9a Add paramiko/pycrypto atfork workaround (can back out later if not useful), tweak error
message text per skvidal's suggestion
2012-03-28 20:32:04 -04:00
b30ddc4520 Expose remote_port option in playbook 2012-03-28 19:31:17 -04:00
a05b75dbbb Merge pull request #101 from sfromm/user-module
Supplementary group membership now in users module
2012-03-28 16:24:26 -07:00
13df76d3d5 Merge pull request #100 from sfromm/group-module
Remove support for membership from group module
2012-03-28 16:24:15 -07:00
debd6fee68 Merge pull request #99 from chjohnst/master
Remote port and error check for old paramiko
2012-03-28 16:24:02 -07:00
509c330f53 add support for running and started - both will do the same thing 2012-03-28 16:06:26 -07:00
68818ad1fe add support for remote ports in playbooks 2012-03-28 15:02:50 -07:00
c1e223c424 Remove errant option on password modification action 2012-03-28 14:50:26 -07:00
e851f7542f Update group_commands playbook example
Remove the group membership example.  There's not much to twiddle with
the group module.  Give examples for what is possible.
2012-03-28 14:32:56 -07:00
a379d58cdb Drop support of group membership in group module
Use the user module to define group membership.
2012-03-28 14:29:23 -07:00
4ed971574f Update user_commands playbook with supplementary group examples 2012-03-28 14:24:13 -07:00
9041ad4d83 just some minor clean up single vs double in getopt parsing 2012-03-28 14:17:20 -07:00
416eb36b9a Update user module to support supplementary group membership
This adds two options to the user module: groups and append.  groups is
a comma-delimited list of supplementary groups a user should belong to.
If a user is currently a member of a group not listed in groups, the
user will be removed from it.  To change this behavior, use append=yes.
This will append the user to the list of supplementary groups and *not*
remove the user from unlisted groups.
2012-03-28 14:12:35 -07:00
15e781eb51 add exception check for paramiko if older then 1.7 2012-03-28 14:09:11 -07:00
f06ec76fdb add support for using an alternate remote port 2012-03-28 14:05:31 -07:00
9777653741 Merge remote branch 'origin/master' 2012-03-26 23:05:05 -04:00
5c95f6818e Merge pull request #96 from skvidal/master
fix up yum module for rhel5.
2012-03-27 11:24:30 -07:00
fc02992bcd make sure latest outputs an error if the thing you've listed as 'latest'
is not there at all or even updateable
2012-03-27 14:15:48 -04:00
2fe193034c fix up latest so it behaves like latest should
if installed and updated available: apply update
if not installed and available: install
2012-03-27 13:58:49 -04:00
f9043bf156 make yum module work with older yum for rhel5 :( 2012-03-27 12:06:21 -04:00
b373c6c223 Add playbook to demonstrate use of group module 2012-03-26 23:04:30 -04:00
db677954ac Add group module to manage groups and group membership
This relies on groupadd, groupmod, groupdel, and gpasswd utilities on
the system.  You can optionally modify the gid for the group.  You can
also add/remove a user to/from a group with the option member.  Member
state is defined with the option memberstate.
2012-03-26 23:04:19 -04:00
8592b3b40d Update user module to support group name
This changes the gid option to group.  One may provide a primary group
as either a gid or a name.  The module will then check to verify that
the group already exists.  If the group does not already exist, the
module will fail.
2012-03-26 23:03:09 -04:00
37f599efc3 make sure latest outputs an error if the thing you've listed as 'latest'
is not there at all or even updateable
2012-03-26 23:02:09 -04:00
1d04ec89b7 fix up latest so it behaves like latest should
if installed and updated available: apply update
if not installed and available: install
2012-03-26 23:02:09 -04:00
a27be2651c make yum module work with older yum for rhel5 :( 2012-03-26 23:02:09 -04:00
85a0709d8f Merge pull request #78 from mgwilliams/master
Catch jinja errors in template module
2012-03-27 17:00:49 -07:00
3046f743d9 removed hack 2012-03-27 11:12:33 -07:00
bf0f3eac1f catch jinja template errors in template module 2012-03-27 11:06:31 -07:00
9d9e3d8c56 resetting template.. previous commit not needed 2012-03-27 09:10:51 -07:00
d9a464ee9d added format_advanced jinja filter to template module 2012-03-27 08:51:37 -07:00
3f9f8de031 Update packaging WRT AUTHORS.md 2012-03-26 22:30:48 -04:00
61446fd6c8 Github maintains a nice contributors list, so remove this here as it's out of date. 2012-03-26 22:29:35 -04:00
01e51b120a Recieved quite a few requests to run python via /usr/bin/env, so doing that. 2012-03-26 22:07:04 -04:00
b1471bf857 Add more comments to the callbacks file 2012-03-26 22:05:11 -04:00
95670ce6b5 remove unused function 2012-03-26 21:58:28 -04:00
f9da7cb180 remove functions that are no longer used 2012-03-26 21:56:18 -04:00
35d77d0433 Make it an error to try to hit a change handler that doesn't exist 2012-03-26 21:31:48 -04:00
f693759252 Style fixes from pep8 makefile target 2012-03-26 21:25:43 -04:00
f074f1c4c4 Fixes from pyflakes 2012-03-26 21:23:28 -04:00
fc3b1cf973 Merge remote branch 'public/master' 2012-03-26 21:19:17 -04:00
35c8750bbb Fix tree view to work with callbacks 2012-03-26 21:18:48 -04:00
45a455a805 Make /usr/bin/ansible output realtime and also delete some code! 2012-03-26 21:17:11 -04:00
267faf7c90 Merge pull request #75 from mgwilliams/master
apt module
2012-03-26 16:36:55 -07:00
eb67a91c57 Merge remote branch 'public/master' 2012-03-26 19:36:03 -04:00
aaafd12b59 rm FIXMEs 2012-03-26 19:35:53 -04:00
e2deb94e73 trimmed superfluous code from apt module 2012-03-26 13:53:22 -07:00
e85355f054 cleaned up apt module style 2012-03-26 13:48:02 -07:00
90ba14d60e preliminary apt module 2012-03-26 12:49:13 -07:00
3b950086c7 Merge pull request #73 from jhoekx/include-arguments
Playbook: create one task per include instead of per argument.
2012-03-26 11:31:52 -07:00
a370261dce Playbook: create one task per include instead of per argument. 2012-03-26 10:30:48 +02:00
77a6315939 Merge remote branch 'public/master' 2012-03-25 19:06:41 -04:00
6dda6f12dc Applying callback model to runner, and using that in playbooks, so output can be more immediate in playbooks.
(Runner still does not use callbacks for default output)
2012-03-25 19:05:27 -04:00
50ce7e0ee1 Merge pull request #70 from tbielawa/manupdate
Show when options require arguments in the man pages.
2012-03-24 13:41:29 -07:00
3052d85a6a use capital O. 2012-03-24 16:32:16 -04:00
ab55fa4266 Revert "Only override the pattern if it is ommitted"
This reverts commit bb0bf9eef4.
2012-03-24 16:21:44 -04:00
bb0bf9eef4 Only override the pattern if it is ommitted 2012-03-24 16:20:47 -04:00
9df612f007 Add a "-o" override option so hosts not in a playbook can still be managed by a playbook. 2012-03-24 16:19:38 -04:00
446ba1795a Show when options require arguments in the man pages. 2012-03-23 22:29:51 -04:00
b213437bfa Added 'shell' to modules with a note that it doesn't really exist :) 2012-03-23 21:39:39 -04:00
292ac4aad2 Make it such that the 'name' element of each playbook line is optional. 2012-03-23 21:06:54 -04:00
44d4dede92 Split conditional imports in playbook into subfunction, fix small bug in event reporting on playbook
actions.
2012-03-23 21:03:25 -04:00
b43019f3a1 In playbooks, a return code (rc) of non-zero should fail the playbook. 2012-03-23 20:53:57 -04:00
6ab615c724 Code cleanup for playbooks, also added 'on_skipped' callback 2012-03-23 20:51:26 -04:00
24964b9660 Merge pull request #95 from skvidal/master
buglet - make sure sshpass is included in runner for setup in playbooks
2012-03-23 13:45:12 -07:00
53446d6d45 make sure the setup step in a playbook has the sshpass included 2012-03-23 16:41:44 -04:00
86590ea614 Merge pull request #94 from skvidal/master
minor typo of AnsibleException/AnsibleError
2012-03-23 09:06:17 -07:00
89d4bc542c fix up minor type - AnsibleException should be AnsibleError 2012-03-23 11:59:08 -04:00
1ea1994eca update authors file 2012-03-22 00:34:21 -04:00
3814a46bbf Move playbook for user commands into examples dir 2012-03-22 00:33:10 -04:00
6aa10691c1 Merge pull request #93 from sfromm/user-module
User module
2012-03-22 19:24:53 -07:00
999bead9ed Add playbook to test user module 2012-03-22 11:36:11 -07:00
f4835477b0 Add user module to create, modify, and delete user accounts
This relies on useradd, usermod, and userdel utilities on the system.
The argument name is required; if state is not provided, present is
assumed.  Other options supported for creating or modifying an existing
account:  uid, gid, comment, home, shell, and password.  If managing the
password, it must already be encrypted.  When creating an account, you
can also provide the argument createhome to control whether the home
directory is created.  Arguments supported for deleting an account are:
force (remove account even if user is logged in) and remove (remove home
directory).
2012-03-22 11:21:41 -07:00
a8c921cbcc Allow groups to be passed to runner API to make parse_hosts optional, misc fixes as a result of refactoring in Runner.
Cleanup in Playbooks module is next.
2012-03-22 00:30:05 -04:00
94605b811b Fixes from refactoring 2012-03-21 23:47:58 -04:00
6a7aac38c5 misc cleanup in the runner module, splitting some things out into utils, breaking up functions into smaller functions. 2012-03-21 23:39:09 -04:00
fef73393f0 rename example files 2012-03-21 22:21:42 -04:00
7ed734dfb2 move callbacks to seperate file, anticipating callbacks for Runner() as well 2012-03-21 22:18:57 -04:00
7883f414ce unused module 2012-03-21 21:03:32 -04:00
d75e69853a No need to comment this part of the example out. 2012-03-21 20:22:44 -04:00
e6406fa5a7 Allow variable expressions to be stored as variables themselves, do some things to allow setup strings
to more easily contain spaces without being mangled, which is neccessary because of the above.
2012-03-21 20:00:48 -04:00
e89131a3a2 playbooks -- Ratchet up logging prior to gating things with verbosity controls and upgrading output format 2012-03-20 22:37:51 -04:00
a5f4ca50b8 Ratchet up logging a few notches prior to controlling w/ verbosity settings 2012-03-20 22:29:21 -04:00
b5c62ec068 Enhance logging, way to gate verbosity levels pending. 2012-03-20 21:44:01 -04:00
05e27a419c HTML doesn't work like that on that page :) 2012-03-20 21:07:33 -04:00
d2141feada Add subscribe link to markdown index 2012-03-20 21:06:49 -04:00
54b98effdf Add demonstration of only_if capability 2012-03-20 19:55:24 -04:00
dfbe591cc0 Add "only_if" capability, which allows task steps to be skipped if they do not match a conditional. 2012-03-20 19:55:04 -04:00
149cc57b0f remove unneeded code 2012-03-19 23:54:47 -04:00
c05e7fd83e Fix typing issue 2012-03-19 22:46:54 -04:00
64471db94a Remove test from bottom of example that doesn't really make sense in the context of the example's use case. 2012-03-19 22:44:11 -04:00
4de7bbb169 Allow conditional imports, see examples/playbook3.yml comments for a full explanation. Extensive
refactoring of playbooks now warranted, which we'll do before we move on.   This variable assignment
system makes nearly all possible magic possible, for we can use these variables however we like,
even as module names!
2012-03-19 22:42:31 -04:00
5ed2b894d9 Add an additional way to dereference a variable in a playbook, $foo
(Using varReplace function originally from yum, thanks Seth)
2012-03-19 19:32:38 -04:00
af9596307d Move templating into a utils function. Reuse is our friend. 2012-03-19 19:23:14 -04:00
c1fe0dd719 Quote long variables in playbooks when feeding them to the setup command 2012-03-18 19:50:22 -04:00
5371a9e497 Regression tests for playbooks, logging the events they call via callbacks. 2012-03-18 19:25:56 -04:00
b15c8e9cb1 Playbook test upgrades, made some notes about callbacks we need to add. Still need to compare events
recieved vs expected so we can be notified if this test breaks, and also to add some more detailed
playbooks that use includes & async, etc.
2012-03-18 18:50:25 -04:00
45abe3c16b Add unit tests for playbooks, and fix an error caught by one 2012-03-18 18:29:11 -04:00
9c5ec886a6 Import cleanup, plus have /bin/ansible remind you if no hosts were matched by a pattern (for instance, assume
there was a typo in the pattern)
2012-03-18 17:53:58 -04:00
fde7de80df Remove "import *" from bin/ansible so we can use pyflakes, and remove unneeded imports in the process 2012-03-18 17:41:44 -04:00
faa59e5ef1 Add the failtest module, primarily for easy development testing purposes. RPMs/etc need not ship this. 2012-03-18 17:35:49 -04:00
a4b8cdf8b3 Remove "import *"'s from bin/ansible-playbook code so pyflakes will be usable. 2012-03-18 17:32:36 -04:00
9e07b24239 Fix indentation 2012-03-18 17:25:22 -04:00
6777268f4c Fixup the code that allows facter/ohai/other data to bubble up and be used in future action lines. It's a niche
case compared to usage in templates but it should work and will be wanted later for conditional support.
2012-03-18 17:24:09 -04:00
c861e0de55 Fix "import *" and resultant new things detectable from "make pyflakes" 2012-03-18 17:16:12 -04:00
33aa50eae7 Inside of runner, do not 'import *' from utils, so we can more easily tell where functions come from 2012-03-18 17:04:07 -04:00
9837a616eb Preserve changed data on file and template ops when the mode operations don't result in a change but the files
were still replaced.
2012-03-18 16:58:00 -04:00
82bd75c1e0 Update env-setup script to make it work better 2012-03-18 12:28:22 -04:00
f1c8fc63cb host file must be marked config(noreplace), update rpm build to use checked in manpage src 2012-03-31 11:08:42 -04:00
5905974737 Fix variable referenced before assignment when not using external variables script 2012-03-31 11:03:29 -04:00
3495d402c7 Default for class variable 2012-03-31 11:02:10 -04:00
b8eeb0f906 Add note about localhost/auth 2012-03-30 19:58:28 -04:00
3d2a7ebd68 Added cobbler external inventory script, which is a demo of how external inventory
works in Ansible in general.
2012-03-30 19:56:22 -04:00
9f6d98844e If the inventory file is EXECUTABLE, treat it as a file returning JSON, if called directly, return the host
and groups list.  If called with an argument of a host name, return the key=value data for that particular
host.
2012-03-30 19:06:14 -04:00
f200a467e7 Update example to use correct state name 2012-03-15 23:11:09 -04:00
f39944b89b Update example to use correct service state; minor pep8 indent/spacing fixes 2012-03-15 23:10:03 -04:00
24923c2e17 Purge unused imports per pyflakes 2012-03-15 23:06:59 -04:00
60bf82d266 Remove comment about FIXME that is already resolved 2012-03-15 23:05:34 -04:00
311b93995c Add some comments 2012-03-15 22:37:37 -04:00
6874d853c9 Fix changed=True/False detection when specifying mode= 2012-03-15 22:35:59 -04:00
63818000b4 auto call the file module after copy/template module usage in runner
note some changed=True/False detection bits in file module still need tweaking
2012-03-15 22:32:14 -04:00
4ad885111b Disable debug 2012-03-15 22:04:32 -04:00
5004d21f10 Return file info about the file regardless of changes made 2012-03-15 22:04:04 -04:00
be55145a1e Initial crack at the file module 2012-03-15 21:53:14 -04:00
8914a1a0d7 license file should be in source tree 2012-03-15 20:24:22 -04:00
9049b0e7ca Because folks on RHEL 6 (not CentOS 6) have some issues with asciidoc, do not build the asciidoc from
the rpm or make install targets.  Also modify the make install (only) to copy over files into the library
directory since the setup.py does not do this (due to a distutils bug, more or less).
2012-03-15 20:21:10 -04:00
1a0672fd1e Fix exception type 2012-03-14 12:51:26 -04:00
c909b66864 use iteritems vs items(), probably doesn't matter that much 2012-03-14 21:56:49 -04:00
a735dd2b17 Added the 'test-module' script, useful for testing modules without running them in Ansible. 2012-03-14 21:49:27 -04:00
4bde4926c3 Modules don't have to return JSON, key=value pairs is ok. 2012-03-14 21:16:15 -04:00
40fd778e2c 'shell' is a magic module that executes the command module with shell=True 2012-03-14 20:40:06 -04:00
73d20b81c6 Add basic test of yum module (VERY basic) just to make sure syntax remains ok. Need to do something
trivial for service and git module also.
2012-03-14 20:02:29 -04:00
e8751baaf3 Remove remote log function as it's only used in one place now 2012-03-14 20:00:09 -04:00
696b67f935 Fix async to use the new argfiles method (wrapping brain around rock, really must write module development guide) 2012-03-14 19:57:56 -04:00
aeea46678d Fix the copy module to use the argsfile method 2012-03-14 19:09:44 -04:00
917f929e86 Fix template module 2012-03-14 19:08:10 -04:00
6b152c94b9 Correct the setup module 2012-03-14 19:06:35 -04:00
3ea9174ed7 Split argsfile handling into subfunction, attempt to apply argsfile logic to setup 2012-03-14 19:05:19 -04:00
1f53c89b14 convert so they handle argsfiles rather than arguments 2012-03-14 18:47:13 -04:00
a9a9e3af65 modify yum to be used with argsfile and fix a number of items with
how it handles "advanced" pkgspecs for the state= cases
2012-03-14 18:46:53 -04:00
a9948f97c6 convert runner to use an args file rather than just arguments passed
on the command line
2012-03-14 18:46:33 -04:00
f681425453 Rename hosts file back since included by MANIFEST.in 2012-03-13 22:00:03 -04:00
ee69bd18cc Add -T to playbook manpage 2012-03-13 21:54:42 -04:00
db61e8f346 Fix -t tree mode so it writes out the value for each host, not everything, once again 2012-03-13 21:38:06 -04:00
e6f175e929 Allow the SSH timeout to be passed to playbooks just like it already could with /usr/bin/ansible 2012-03-13 21:35:34 -04:00
e3b4981feb Various additions to make sure timed out and failed hosts are taken out of the playbook even if failed
during setup, async tasks, or poll operations.  This introduced some redundancy so I've made some
notes in the code where things need to be refactored to remove them.
2012-03-13 21:30:34 -04:00
4ae98ed92d Upgrades to error handling, now general try/catch available. 2012-03-13 20:59:05 -04:00
2e1b59a9d2 restructure the examples directory 2012-03-13 19:27:13 -04:00
09a7119e74 Annotate more files, fix missing imports messing with playbooks. Hey Tim, please test your stuff :) 2012-03-13 19:19:54 -04:00
238fffd6ef Annotated playbook example 2012-03-13 18:55:04 -04:00
84c60c2750 Merge pull request #91 from skvidal/feature
modify build/spec file to work on el6 with quasi-broken setup process
2012-03-13 15:37:11 -07:00
8df0c92f3c Merge pull request #28 from tbielawa/DieGracefully
Die gracefully...
2012-03-13 15:35:38 -07:00
3567237dbc modify build/spec file to work on el6 with quasi-broken setup process
remove data_files section from setup.py for some reason or another
2012-03-13 11:08:10 -04:00
dfd2c6dce3 Implement friendlier error handling.
Generic AnsibleError exception + host inventory missing exception.
First shot at catching these in a generic way in bin/ansible*.
2012-03-12 23:11:54 -04:00
ce85222fa6 What we are running is actually a "play" (multiple plays per playbook) so rename the function to make it more clear 2012-03-12 22:05:51 -04:00
adf1492da2 Add external vars example 2012-03-12 22:03:20 -04:00
e582bd5ec5 Add fire and forget example 2012-03-12 21:02:38 -04:00
86e19cd8c8 This adds async poll support to playbooks. See examples. Some more testing due + docs
but this is more or less a mirror of what /bin/ansible does.  It also has a 'fire and
forget' mode if the poll interval is left off or set to 0.
2012-03-12 20:53:10 -04:00
32484f2156 Merge pull request #90 from skvidal/feature
yum module
2012-03-12 16:27:20 -07:00
0b94c78042 yum module
add state=latest and clean up the output cases
added some fixmes on verification
2012-03-12 16:31:13 -04:00
6660a5efbd add polling info to manpage 2012-03-11 21:09:44 -04:00
db7ba87111 Add polling logic in runner such that all actions get kicked off everywhere then polling
happens only on needed hosts, allowing some hosts to fail and drop out of the running.
2012-03-11 20:54:54 -04:00
49a636d8a0 Fixed up async and polling logic. 2012-03-11 19:27:43 -04:00
5be1a612d3 Add async polling logic to runner. Will add to playbook shortly, have to diagnose why paramiko
is not letting async_wrapper daemonize itself when it does work fine when directly executed.
2012-03-11 18:40:35 -04:00
60a13cf540 Ignore commented out lines in the ansible setup files. 2012-03-10 21:19:41 -05:00
e284c8cedc Merge pull request #16 from tbielawa/master
Updates for better RPM packaging (plus tasks)
2012-03-10 16:51:34 -08:00
82befee081 Change Source0 in spec file to point at github downloads 2012-03-10 16:25:46 -05:00
dce6074a81 Fixes for better RPM packaging
Spec file installs default example hosts files
Spec file marks the contents of /etc/ansible as %config(noreplace)

Example hosts file now with instructional comments
2012-03-10 16:19:33 -05:00
3f245498c4 Fixup RPM building: Makefile, Spec File, .gitignore
Fixup distutils: setup.py

Clean old cruft: section 5 man pages
2012-03-10 14:22:11 -05:00
4c9dd972b4 Fixes for output formatting 2012-03-10 13:40:08 -05:00
bb5e4fad48 Abstracted out transport from implementation so it can be pluggable. Also fixes for output format. 2012-03-10 13:35:46 -05:00
8808c814ea Merge pull request #89 from skvidal/feature
touch up to yum module.
2012-03-10 05:40:24 -08:00
a99b491b32 touch up to yum module.
fix a couple of bugs
comment about state='latest'
2012-03-10 02:19:57 -05:00
209760f8f6 Add manpage for ansible-playbook, since it is a command. More detailed docs will remain on the web site. 2012-03-09 22:06:44 -05:00
1699013fbd Rename 'ensure' to 'state' so it lines up with the service module 2012-03-09 18:50:34 -05:00
ae0e4ff0cf Merge pull request #88 from skvidal/feature
runner buglet and yum module
2012-03-09 15:44:54 -08:00
b576e389b1 runner buglet and yum module
runner: fix buglet causing logger output to include a u for the command
yum: add yum module:
   ensure=installed pkg=name/name.arch/name-ver-rel.arch
   ensure=removed pkg=name/name.arch/name-ver-rel.arch
   list=installed
   list=updates
   list=available
   list=pkgspec
   list=repos
2012-03-09 18:33:58 -05:00
c89a0f96bd Merge pull request #87 from skvidal/feature
if we've failed a connection to a host - we cannot set items to the
2012-03-09 04:19:03 -08:00
19b784e480 if we've failed a connection to a host - we cannot set items to the
'conn' object since it is not an object on failure - it's a string.
2012-03-09 00:19:55 -05:00
145 changed files with 13077 additions and 2645 deletions

7
.gitignore vendored
View File

@ -3,3 +3,10 @@ build
# Emacs backup files...
*~
.\#*
# (s)rpm building stuff
MANIFEST
dist
rpm-build
# Eclipse/PyDev stuff...
.project
.pydevproject

View File

@ -1,13 +0,0 @@
Patches and Contributions
=========================
* Michael DeHaan - michael.dehaan AT gmail DOT com
* Jeremy Katz - katzj AT fedoraproject DOT org
* Seth Vidal - skvidal AT fedoraproject DOT org
* Tim Bielawa - tbielawa AT redhat DOT com
* Christopher Johnston - chjohnst AT gmail DOT com
Send in a github pull request to get your name here.
Upstream: github.com/mpdehaan/ansible

181
CHANGELOG.md Normal file
View File

@ -0,0 +1,181 @@
Ansible Changes By Release
==========================
0.6 "Cabo" ------------ pending
playbooks:
* support to tag tasks and includes and use --tags in playbook CLI
* playbooks can now include other playbooks (example/playbooks/nested_playbooks.yml)
* vars_files now usable with with_items, provided file paths don't contain host specific facts
* error reporting if with_items value is unbound
* with_items no longer creates lots of tasks, creates one task that makes multiple calls
* can use host_specific facts inside with_items (see above)
* at the top level of a playbook, set 'gather_facts: False' to skip fact gathering
* first_available_file and with_items used together will now raise an error
* to catch typos, like 'var' for 'vars', playbooks and tasks now yell on invalid parameters
* automatically load (directory_of_inventory_file)/group_vars/groupname and /host_vars/hostname in vars_files
* playbook is now colorized, set ANSIBLE_NOCOLOR=1 if you do not like this, does not colorize if not a TTY
* hostvars now preserved between plays (regression in 0.5 from 0.4), useful for sharing vars in multinode configs
* ignore_errors: True on a task can be used to allow a task to fail and not stop the play
* with_items with the apt/yum module will install/remove/update everything in a single command
inventory:
* groups variable available as a hash to return the hosts in each group name
* in YAML inventory, hosts can list their groups in inverted order now also (see tests/yaml_hosts)
* YAML inventory is deprecated and will be removed in 0.7
* ec2 inventory script
* support ranges of hosts in the host file, like www[001-100].example.com (supports leading zeros and also not)
modules:
* fetch module now does not fail a system when requesting file paths (ex: logs) that don't exist
* apt module now takes an optional install-recommends=yes|no (default yes)
* fixes to the return codes of the copy module
* copy module takes a remote md5sum to avoid large file transfer
* various user and group module fixes (error handling, etc)
* apt module now takes an optional force parameter
* slightly better psychic service status handling for the service module
* fetch module fixes for SSH connection type
* modules now consistently all take yes/no for boolean parameters (and DWIM on true/false/1/0/y/n/etc)
* setup module no longer saves to disk, template module now only used in playbooks
* setup module no longer needs to run twice per playbook
* apt module now passes DEBIAN_FRONTEND=noninteractive
* mount module (manages active mounts + fstab)
* setup module fixes if no ipv6 support
* internals: template in common module boilerplate, also causes less SSH operations when used
* git module fixes
* setup module overhaul, more modular
* minor caching logic added to inventory to reduce hammering of inventory scripts.
* MySQL and PostgreSQL modules for user and db management
* vars_prompt now supports private password entry (see examples/playbooks/prompts.yml)
* yum module modified to be more tolerant of plugins spewing random console messages (ex: RHN)
internals:
* when sudoing to root, still use /etc/ansible/setup as the metadata path, as if root
* paramiko is now only imported if needed when running from source checkout
* cowsay support on Ubuntu
* various ssh connection fixes for old Ubuntu clients
* ./hacking/test-module now supports options like ansible takes and has a debugger mode
* sudoing to a user other than root now works more seamlessly (uses /tmp, avoids umask issues)
0.5 "Amsterdam" ------- July 04, 2012
* Service module gets more accurate service states when running with upstart
* Jinja2 usage in playbooks (not templates), reinstated, supports %include directive
* support for --connection ssh (supports Kerberos, bastion hosts, etc), requires ControlMaster
* misc tracebacks replaced with error messages
* various API/internals refactoring
* vars can be built from other variables
* support for exclusion of hosts/groups with "!groupname"
* various changes to support md5 tool differences for FreeBSD nodes & OS X clients
* "unparseable" command output shows in command output for easier debugging
* mktemp is no longer required on remotes (not available on BSD)
* support for older versions of python-apt in the apt module
* a new "assemble" module, for constructing files from pieces of files (inspired by Puppet "fragments" idiom)
* ability to override most default values with ANSIBLE_FOO environment variables
* --module-path parameter can support multiple directories seperated with the OS path seperator
* with_items can take a variable of type list
* ansible_python_interpreter variable available for systems with more than one Python
* BIOS and VMware "fact" upgrades
* cowsay is used by ansible-playbook if installed to improve output legibility (try installing it)
* authorized_key module
* SELinux facts now sourced from the python selinux library
* removed module debug option -D
* added --verbose, which shows output from successful playbook operations
* print the output of the raw command inside /usr/bin/ansible as with command/shell
* basic setup module support for Solaris
* ./library relative to the playbook is always in path so modules can be included in tarballs with playbooks
0.4 "Unchained" ------- May 23, 2012
Internals/Core
* internal inventory API now more object oriented, parsers decoupled
* async handling improvements
* misc fixes for running ansible on OS X (overlord only)
* sudo improvements, now works much more smoothly
* sudo to a particular user with -U/--sudo-user, or using 'sudo_user: foo' in a playbook
* --private-key CLI option to work with pem files
Inventory
* can use -i host1,host2,host3:port to specify hosts not in inventory (replaces --override-hosts)
* ansible INI style format can do groups of groups [groupname:children] and group vars [groupname:vars]
* groups and users module takes an optional system=yes|no on creation (default no)
* list of hosts in playbooks can be expressed as a YAML list in addition to ; delimited
Playbooks
* variables can be replaced like ${foo.nested_hash_key.nested_subkey[array_index]}
* unicode now ok in templates (assumes utf8)
* able to pass host specifier or group name in to "hosts:" with --extra-vars
* ansible-pull script and example playbook (extreme scaling, remediation)
* inventory_hostname variable available that contains the value of the host as ansible knows it
* variables in the 'all' section can be used to define other variables based on those values
* 'group_names' is now a variable made available to templates
* first_available_file feature, see selective_file_sources.yml in examples/playbooks for info
* --extra-vars="a=2 b=3" etc, now available to inject parameters into playbooks from CLI
Incompatible Changes
* jinja2 is only usable in templates, not playbooks, use $foo instead
* --override-hosts removed, can use -i with comma notation (-i "ahost,bhost")
* modules can no longer include stderr output (paramiko limitation from sudo)
Module Changes
* tweaks to SELinux implementation for file module
* fixes for yum module corner cases on EL5
* file module now correctly returns the mode in octal
* fix for symlink handling in the file module
* service takes an enable=yes|no which works with chkconfig or updates-rc.d as appropriate
* service module works better on Ubuntu
* git module now does resets and such to work more smoothly on updates
* modules all now log to syslog
* enabled=yes|no on a service can be used to toggle chkconfig & updates-rc.d states
* git module supports branch=
* service fixes to better detect status using return codes of the service script
* custom facts provided by the setup module mean no dependency on Ruby, facter, or ohai
* service now has a state=reloaded
* raw module for bootstrapping and talking to routers w/o Python, etc
Misc Bugfixes
* fixes for variable parsing in only_if lines
* misc fixes to key=value parsing
* variables with mixed case now legal
* fix to internals of hacking/test-module development script
0.3 "Baluchitherium" -- April 23, 2012
* Packaging for Debian, Gentoo, and Arch
* Improvements to the apt and yum modules
* A virt module
* SELinux support for the file module
* Ability to use facts from other systems in templates (aka exported
resources like support)
* Built in Ansible facts so you don't need ohai, facter, or Ruby
* tempdir selections that work with noexec mounted /tmp
* templates happen locally, not remotely, so no dependency on
python-jinja2 for remote computers
* advanced inventory format in YAML allows more control over variables
per host and per group
* variables in playbooks can be structured/nested versus just a flat namespace
* manpage upgrades (docs)
* various bugfixes
* can specify a default --user for playbooks rather than specifying it
in the playbook file
* able to specify ansible port in ansible host file (see docs)
* refactored Inventory API to make it easier to write scripts using Ansible
* looping capability for playbooks (with_items)
* support for using sudo with a password
* module arguments can be unicode
* A local connection type, --connection=local, for use with cron or
in kickstarts
* better module debugging with -D
* fetch module for pulling in files from remote hosts
* command task supports creates=foo for idempotent semantics, won't
run if file foo already exists
0.0.2 and 0.0.1
* Initial stages of project

675
COPYING Normal file
View File

@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -1,3 +1,6 @@
include README.md AUTHORS.md ansible.spec
include README.md packaging/rpm/ansible.spec COPYING
include examples/hosts
include packaging/distutils/setup.py
recursive-include docs *
recursive-include library *
include Makefile

137
Makefile
View File

@ -1,23 +1,67 @@
#!/usr/bin/make
########################################################
# Makefile for Ansible
#
# useful targets:
# make sdist ---------------- produce a tarball
# make rpm ----------------- produce RPMs
# make deb ------------------ produce a DEB
# make docs ----------------- rebuild the manpages (results are checked in)
# make tests ---------------- run the tests
# make pyflakes, make pep8 -- source code checks
########################################################
# variable section
NAME = "ansible"
# Manpages are currently built with asciidoc -- would like to move to markdown
# This doesn't evaluate until it's called. The -D argument is the
# directory of the target file ($@), kinda like `dirname`.
ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $<
ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml
MANPAGES := docs/man/man1/ansible.1
MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1
SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
# VERSION file provides one place to update the software version
VERSION := $(shell cat VERSION)
### Get the branch information from git
ifneq ($(shell which git),)
GIT_DATE := $(shell git log -n 1 --format="%ai")
endif
DATE := $(shell date --date="$(GIT_DATE)" +%Y%m%d%H%M)
# RPM build parameters
RPMSPECDIR= packaging/rpm
RPMSPEC = $(RPMSPECDIR)/ansible.spec
RPMDIST = $(shell rpm --eval '%dist')
RPMRELEASE = 1
ifeq ($(OFFICIAL),)
RPMRELEASE = 0.git$(DATE)
endif
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
########################################################
all: clean python
tests:
PYTHONPATH=./lib nosetests
PYTHONPATH=./lib nosetests -v
docs: manuals
# To force a rebuild of the docs run 'touch VERSION && make docs'
docs: $(MANPAGES)
manuals: $(MANPAGES)
# Regenerate %.1.asciidoc if %.1.asciidoc.in has been modified more
# recently than %.1.asciidoc.
%.1.asciidoc: %.1.asciidoc.in
sed "s/%VERSION%/$(VERSION)/" $< > $@
%.1: %.1.asciidoc
$(ASCII2MAN)
%.5: %.5.asciidoc
# Regenerate %.1 if %.1.asciidoc or VERSION has been modified more
# recently than %.1. (Implicitly runs the %.1.asciidoc recipe)
%.1: %.1.asciidoc VERSION
$(ASCII2MAN)
loc:
@ -27,38 +71,83 @@ pep8:
@echo "#############################################"
@echo "# Running PEP8 Compliance Tests"
@echo "#############################################"
pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225 lib/ bin/
pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261 lib/ bin/
pyflakes:
pyflakes lib/ansible/*.py
pyflakes lib/ansible/*.py bin/*
clean:
@echo "Cleaning up distutils stuff"
-rm -rf build
-rm -rf dist
rm -rf build
rm -rf dist
@echo "Cleaning up byte compiled python stuff"
find . -regex ".*\.py[co]$$" -delete
find . -type f -regex ".*\.py[co]$$" -delete
@echo "Cleaning up editor backup files"
find . -type f \( -name "*~" -or -name "#*" \) -delete
find . -type f \( -name "*.swp" \) -delete
@echo "Cleaning up asciidoc to man transformations and results"
find ./docs/man -type f \( -name "*.xml" -or -regex ".*\.[0-9]$$" \) -delete
find ./docs/man -type f -name "*.xml" -delete
find ./docs/man -type f -name "*.asciidoc" -delete
@echo "Cleaning up output from test runs"
-rm -rf test/test_data
@echo "Cleaning up RPM stuff"
-rm MANIFEST
rm -rf test/test_data
@echo "Cleaning up RPM building stuff"
rm -rf MANIFEST rpm-build
@echo "Cleaning up Debian building stuff"
rm -rf debian
rm -rf deb-build
python: docs
python:
python setup.py build
install: docs
install:
mkdir -p /usr/share/ansible
cp ./library/* /usr/share/ansible/
python setup.py install
rpm:
python setup.py sdist
rpmbuild -ta dist/ansible-1.0.tar.gz
sdist: clean
python setup.py sdist -t MANIFEST.in
.PHONEY: docs manual clean pep8
vpath %.asciidoc docs/man/man1
rpmcommon: sdist
@mkdir -p rpm-build
@cp dist/*.gz rpm-build/
@sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}#' $(RPMSPEC) >rpm-build/$(NAME).spec
srpm: rpmcommon
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
--define "_builddir %{_topdir}" \
--define "_rpmdir %{_topdir}" \
--define "_srcrpmdir %{_topdir}" \
--define "_specdir $(RPMSPECDIR)" \
--define "_sourcedir %{_topdir}" \
-bs rpm-build/$(NAME).spec
@rm -f rpm-build/$(NAME).spec
@echo "#############################################"
@echo "Ansible SRPM is built:"
@echo " rpm-build/$(RPMNVR).src.rpm"
@echo "#############################################"
rpm: rpmcommon
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
--define "_builddir %{_topdir}" \
--define "_rpmdir %{_topdir}" \
--define "_srcrpmdir %{_topdir}" \
--define "_specdir $(RPMSPECDIR)" \
--define "_sourcedir %{_topdir}" \
--define "_rpmfilename $(RPMNVR).%%{ARCH}.rpm" \
-ba rpm-build/$(NAME).spec
@rm -f rpm-build/$(NAME).spec
@echo "#############################################"
@echo "Ansible RPM is built:"
@echo " rpm-build/$(RPMNVR).noarch.rpm"
@echo "#############################################"
debian: sdist
deb: debian
cp -r packaging/debian ./
chmod 755 debian/rules
fakeroot debian/rules clean
fakeroot dh_install
fakeroot debian/rules binary
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory

View File

@ -4,7 +4,7 @@ Ansible
Ansible is a radically simple configuration-management, deployment, task-execution, and
multinode orchestration framework.
Read all about at it at (http://ansible.github.com)
Read the documentation at http://ansible.github.com
Design Principles
=================
@ -24,6 +24,37 @@ Get Involved
* [ansible-project mailing list](http://groups.google.com/group/ansible-project)
* irc.freenode.net: #ansible
Branch Info
===========
* Releases are named after Van Halen songs.
* The master branch corresponds to release 0.5 "Amsterdam".
* The devel branch corresponds to release 0.6 "Cabo".
* All feature work happens on the development branch.
* Major bug fixes will be made to the master branch, but not minor ones.
* See CHANGELOG.md for release notes to track each release.
Patch Instructions
==================
Contributions to the core and modules are greatly welcome.
* Required Process:
* Submit github pull requests to the "ansible/devel" branch for features
* Fixes for bugs may be submitted to "ansible/master"
* Make sure "make tests" passes before submitting any requests.
* Bonus points:
* Joining the mailing list
* Fixing bugs instead of sending bug reports.
* Using squash merges
* Updating the "rst/*" files in the docs project and "docs/" manpage content
* Adding more unit tests
* Avoid:
* Sending patches to the mailing list directly.
* Sending feature pull requests to the 'master' branch instead of the devel branch
* Sending pull requests to mpdehaan's personal ansible fork.
Author
======

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.6

View File

@ -1,50 +0,0 @@
%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
Summary: Minimal SSH command and control
Name: ansible
Version: 1.0
Release: 1
Source0: ansible-%{version}.tar.gz
License: GPLv3
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Url: http://github.com/mpdehaan/ansible/
BuildRequires: asciidoc
Requires: python-paramiko
Requires: python-jinja2
%description
Ansible is a extra-simple tool/API for doing 'parallel remote things' over SSH
executing commands, running "modules", or executing larger 'playbooks' that
can serve as a configuration management or deployment system.
%prep
%setup -n %{name}-%{version}
%build
python setup.py build
make docs
%install
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
mkdir -p $RPM_BUILD_ROOT/etc/ansible/
%clean
rm -rf $RPM_BUILD_ROOT
%files
%doc README.md AUTHORS.md PKG-INFO
%defattr(-,root,root)
%{_mandir}/man1/*.gz
%{_mandir}/man5/*.gz
%{python_sitelib}/*
%{_bindir}/ansible*
%{_datadir}/ansible/*
%{_sysconfdir}/ansible/
%changelog
* Mon Mar 5 2012 Seth Vidal <skvidal at fedoraproject.org>
- spec file

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
@ -19,20 +19,15 @@
########################################################
try:
import json
except ImportError:
import simplejson as json
from optparse import OptionParser
import sys
import os
import getpass
import shlex
import ansible.runner
import ansible.playbook
from ansible.runner import Runner
import ansible.constants as C
from optparse import OptionParser
from ansible.utils import *
from ansible import utils
from ansible import errors
from ansible import callbacks
from ansible import inventory
########################################################
@ -42,38 +37,24 @@ class Cli(object):
# ----------------------------------------------
def __init__(self):
pass
self.stats = callbacks.AggregateStats()
self.callbacks = callbacks.CliRunnerCallbacks()
# ----------------------------------------------
def parse(self):
''' create an options parser for bin/ansible '''
parser = OptionParser(usage = 'ansible <host-pattern> [options]')
parser.add_option("-a", "--args", dest="module_args",
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
parser.add_option("-B", "--background", dest="seconds", default=0,
help="run asynchronously, failing after X seconds")
parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
help='number of parallel processes to use')
parser.add_option("-i", "--inventory-file", dest="inventory",
help="inventory host file", default=C.DEFAULT_HOST_LIST)
parser.add_option("-k", "--ask-pass", default=False, action="store_true",
help="ask for SSH password")
parser.add_option("-M", "--module-path", dest="module_path",
help="path to module library", default=C.DEFAULT_MODULE_PATH)
parser.add_option("-m", "--module-name", dest="module_name",
help="module name to execute", default=C.DEFAULT_MODULE_NAME)
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
help="condense output")
parser.add_option('-t', '--tree', dest='tree', default=None,
help="log output to this directory")
parser.add_option('-T', '--timeout', default=C.DEFAULT_TIMEOUT, type='int',
dest='timeout', help="set the SSH timeout in seconds")
parser.add_option('-u', '--user', default=C.DEFAULT_REMOTE_USER,
dest='remote_user', help='connect as this user')
options, args = parser.parse_args()
parser = utils.base_parser(constants=C, runas_opts=True, async_opts=True,
output_opts=True, connect_opts=True, usage='%prog <host-pattern> [options]')
parser.add_option('-a', '--args', dest='module_args',
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
parser.add_option('-m', '--module-name', dest='module_name',
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
default=C.DEFAULT_MODULE_NAME)
options, args = parser.parse_args()
self.callbacks.options = options
if len(args) == 0 or len(args) > 1:
parser.print_help()
sys.exit(1)
@ -85,57 +66,69 @@ class Cli(object):
''' use Runner lib to do SSH things '''
pattern = args[0]
inventory_manager = inventory.Inventory(options.inventory)
hosts = inventory_manager.list_hosts(pattern)
if len(hosts) == 0:
print >>sys.stderr, "No hosts matched"
sys.exit(1)
sshpass = None
sudopass = None
if options.ask_pass:
sshpass = getpass.getpass(prompt="SSH password: ")
if options.ask_sudo_pass:
sudopass = getpass.getpass(prompt="sudo password: ")
options.sudo = True
if options.sudo_user:
options.sudo = True
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
if options.tree:
utils.prepare_writeable_dir(options.tree)
return ansible.runner.Runner(
module_name=options.module_name,
module_path=options.module_path,
module_args=shlex.split(options.module_args),
remote_user=options.remote_user,
remote_pass=sshpass,
host_list=options.inventory,
timeout=options.timeout,
forks=options.forks,
background=options.seconds,
pattern=pattern,
verbose=True,
).run()
runner = Runner(
module_name=options.module_name, module_path=options.module_path,
module_args=options.module_args,
remote_user=options.remote_user, remote_pass=sshpass,
inventory=inventory_manager, timeout=options.timeout,
private_key_file=options.private_key_file,
forks=options.forks,
pattern=pattern,
callbacks=self.callbacks, sudo=options.sudo,
sudo_pass=sudopass,sudo_user=options.sudo_user,
transport=options.connection, verbose=options.verbose
)
if options.seconds:
print "background launch...\n\n"
results, poller = runner.run_async(options.seconds)
results = self.poll_while_needed(poller, options)
else:
results = runner.run()
return (runner, results)
# ----------------------------------------------
def output(self, results, options, args):
def poll_while_needed(self, poller, options):
''' summarize results from Runner '''
if results is None:
exit("No hosts matched")
if options.tree:
prepare_writeable_dir(options.tree)
# BACKGROUND POLL LOGIC when -B and -P are specified
if options.seconds and options.poll_interval > 0:
poller.wait(options.seconds, options.poll_interval)
buf = ''
for hostname in contacted_hosts(results):
msg = host_report_msg(
hostname,
options.module_name,
contacted_host_result(results, hostname),
options.one_line
)
if options.tree:
write_tree_file(options.tree, hostname, bigjson(results))
buf += msg
if has_dark_hosts(results):
buf += dark_hosts_msg(results)
return poller.results
print buf
########################################################
if __name__ == '__main__':
cli = Cli()
(options, args) = cli.parse()
results = cli.run(options, args)
cli.output(results, options, args)
try:
(runner, results) = cli.run(options, args)
except errors.AnsibleError, e:
# Generic handler for ansible specific errors
print "ERROR: %s" % str(e)
sys.exit(1)

View File

@ -1,4 +1,4 @@
#!/usr/bin/python -tt
#!/usr/bin/env python
# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
# This file is part of Ansible
@ -18,87 +18,119 @@
#######################################################
import os
import sys
import getpass
import ansible.playbook
import ansible.constants as C
from ansible.utils import *
import getpass
from optparse import OptionParser
from ansible import errors
from ansible import callbacks
from ansible import utils
from ansible.color import ANSIBLE_COLOR, stringc
#######################################################
def colorize(lead, num, color):
""" Print 'lead' = 'num' in 'color' """
if num != 0 and ANSIBLE_COLOR:
return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color))
else:
return "%s=%-4s" % (lead, str(num))
class PlaybookCallbacks(object):
def __init__(self):
pass
def set_playbook(self, playbook):
self.playbook = playbook
def on_start(self):
print "\n"
def on_task_start(self, name, is_conditional):
print task_start_msg(name, is_conditional)
def on_unreachable(self, host, msg):
print "unreachable: [%s] => %s" % (host, msg)
def on_failed(self, host, results):
print "failed: [%s] => %s\n" % (host, smjson(results))
def on_ok(self, host):
print "ok: [%s]\n" % (host)
def on_play_start(self, pattern):
print "PLAY [%s] ****************************\n" % pattern
def hostcolor(host, stats):
if ANSIBLE_COLOR:
if stats['failures'] != 0 or stats['unreachable'] != 0:
return "%-41s" % stringc(host, 'red')
elif stats['changed'] != 0:
return "%-41s" % stringc(host, 'yellow')
else:
return "%-41s" % stringc(host, 'green')
return "%-30s" % host
def main(args):
''' run ansible-playbook operations '''
# create parser for CLI options
parser = OptionParser()
parser.usage = "ans-playbook playbook.yml ..."
parser.add_option('-f','--forks', dest='forks', default=C.DEFAULT_FORKS, type='int',
help='set the number of forks to start up')
parser.add_option("-i", "--inventory-file", dest="inventory",
help="inventory host file", default=C.DEFAULT_HOST_LIST)
parser.add_option("-k", "--ask-pass", default=False, action="store_true",
help="ask for SSH password")
parser.add_option("-M", "--module-path", dest="module_path",
help="path to module library", default=C.DEFAULT_MODULE_PATH)
usage = "%prog playbook.yml"
parser = utils.base_parser(constants=C, usage=usage, connect_opts=True, runas_opts=True)
parser.add_option('-e', '--extra-vars', dest="extra_vars", default=None,
help="set additional key=value variables from the CLI")
parser.add_option('-t', '--tags', dest='tags', default='all',
help="only run plays and tasks tagged with these values")
options, args = parser.parse_args(args)
if len(args) == 0:
print >> sys.stderr, "playbook path is a required argument"
parser.print_help(file=sys.stderr)
return 1
sshpass = None
sudopass = None
if options.ask_pass:
sshpass = getpass.getpass(prompt="SSH password: ")
if options.ask_sudo_pass:
sudopass = getpass.getpass(prompt="sudo password: ")
options.sudo = True
if options.sudo_user:
options.sudo = True
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
extra_vars = utils.parse_kv(options.extra_vars)
only_tags = options.tags.split(",")
# run all playbooks specified on the command line
for playbook in args:
stats = callbacks.AggregateStats()
playbook_cb = callbacks.PlaybookCallbacks(verbose=options.verbose)
runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=options.verbose)
pb = ansible.playbook.PlayBook(
playbook=playbook,
host_list=options.inventory,
module_path=options.module_path,
forks=options.forks,
verbose=True,
remote_pass=sshpass,
callbacks=PlaybookCallbacks()
host_list=options.inventory,
forks=options.forks,
verbose=options.verbose,
remote_user=options.remote_user,
remote_pass=sshpass,
callbacks=playbook_cb,
runner_callbacks=runner_cb,
stats=stats,
timeout=options.timeout,
transport=options.connection,
sudo=options.sudo,
sudo_user=options.sudo_user,
sudo_pass=sudopass,
extra_vars=extra_vars,
private_key_file=options.private_key_file,
only_tags=only_tags,
)
pb.run()
try:
pb.run()
hosts = sorted(pb.stats.processed.keys())
print callbacks.banner("PLAY RECAP")
for h in hosts:
t = pb.stats.summarize(h)
print "%-30s : %s %s %s %s " % (
hostcolor(h, t),
colorize('ok', t['ok'], 'green'),
colorize('changed', t['changed'], 'yellow'),
colorize('unreachable', t['unreachable'], 'red'),
colorize('failed', t['failures'], 'red'))
print "\n"
except errors.AnsibleError, e:
print >>sys.stderr, "ERROR: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
try:
sys.exit(main(sys.argv[1:]))
except errors.AnsibleError, e:
print >>sys.stderr, "ERROR: %s" % e
sys.exit(1)

75
bin/ansible-pull Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python
# ansible-pull is a script that runs ansible in local mode
# after checking out a playbooks directory from git. There is an
# example playbook to bootstrap this script in the examples/ dir which
# installs ansible and sets it up to run on cron.
#
# usage:
# ansible-pull -d /var/ansible/local -U http://wherever/content.git -C production
#
# the git repo must contain a playbook named 'local.yml'
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import subprocess
import sys
from optparse import OptionParser
DEFAULT_PLAYBOOK = 'local.yml'
def _run(cmd):
cmd = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
print out
if cmd.returncode != 0:
print err
return cmd.returncode
def main(args):
""" Set up and run a local playbook """
usage = "%prog [options]"
parser = OptionParser()
parser.add_option('-d', '--directory', dest='dest', default=None,
help='Directory to checkout git repository')
parser.add_option('-U', '--url', dest='url',
default=None,
help='URL of git repository')
parser.add_option('-C', '--checkout', dest='checkout',
default="HEAD",
help='Branch/Tag/Commit to checkout. Defaults to HEAD.')
options, args = parser.parse_args(args)
git_opts = "repo=%s dest=%s version=%s" % (options.url, options.dest, options.checkout)
cmd = 'ansible all -c local -m git -a "%s"' % git_opts
print "cmd=%s" % cmd
rc = _run(cmd)
if rc != 0:
return rc
os.chdir(options.dest)
cmd = 'ansible-playbook -c local %s' % DEFAULT_PLAYBOOK
rc = _run(cmd)
return rc
if __name__ == '__main__':
try:
sys.exit(main(sys.argv[1:]))
except KeyboardInterrupt, e:
print >>sys.stderr, "Exit on user request.\n"
sys.exit(1)

View File

@ -1,203 +0,0 @@
Comparisons
===========
Confused about how Ansible fits in? Here's a comparison with some common tools.
Accuracy is important, so corrections are VERY welcome if we've got something wrong.
For space reasons, we can't list everybody's favorite management tool.
Ansible mainly tries to be different in it's goals of minimalism, while also cutting across
the categories of configuration management, deployment, and ad-hoc task firing.
It strives to be a super-easy to use app, requiring no daemons or databases, and having
a super-simple configuration management language that takes minutes to learn.
Generally speaking, Ansible is answer to the question "why is the world's systems
management software so complicated?" The code does not try to show off. It is an
experiment in radical simplicity and encourages anyone to be able to easily contribute to it.
<table>
<tr>
<td></td>
<td><B><U>Ansible</U></B></font></td>
<td>Puppet</td>
<td>Chef</td>
<td>Func</td>
<td>Capistrano</td>
<td>mCollective</td>
</tr>
<tr>
<td>Purpose</td>
<td><B>Config, Deployment, Ad-Hoc</B></td>
<td>Config, Deployment</td>
<td>Config, Deployment</td>
<td>Ad-Hoc</td>
<td>Deployment</td>
<td>Ad-Hoc</td>
</tr>
<tr>
<td>Inspirations</td>
<td>Cobbler, Puppet, Func, Taboot</B></td>
<td>cfengine</td>
<td>puppet</td>
<td>sysadmin SSH scripts</td>
<td>sysadmin SSH scripts</td>
<td>Func, puppet</td>
</tr>
<tr>
<td>Config Language</td>
<td><B>Simple YAML format</B></td>
<td>Custom DSL</td>
<td>Ruby code</td>
<td>None</td>
<td>None</td>
<td>None</td>
</tr>
<tr>
<td>Config Language Style</td>
<td><B>Very simple</B></td>
<td>Very rich, complex</td>
<td>Very rich, complex</td>
<td>None</td>
<td>None</td>
<td>None</td>
</tr>
<tr>
<td>Config Ordering</td>
<td><B>Simply ordered, with notifiers</B></td>
<td>Strict dependency DAG</td>
<td>Simply ordered</td>
<td>None</td>
<td>Simply ordered</td>
<td>None</td>
</tr>
<tr>
<td>Communication</td>
<td>SSH push</td>
<td>SSL pull or push trigger</td>
<td>SSL pull or push trigger(?)</td>
<td>SSL push</td>
<td>SSH</td>
<td>AMQP bus</td>
</tr>
<tr>
<td>Daemons Required?</td>
<td><B>no</B></td>
<td>yes</td>
<td>yes</td>
<td>yes</td>
<td><B>no</B></td>
<td>yes</td>
</tr>
<tr>
<td>Database Needed?</td>
<td><B>no</B></td>
<td>some features</td>
<td>yes</td>
<td><B>no</B></td>
<td><B>no</B></td>
<td>some features</td>
</tr>
<tr>
<td>Message Bus Required?</td>
<td><B>no</B></td>
<td><B>no</B></td>
<td>yes</td>
<td><B>no</B></td>
<td><B>no</B></td>
<td>yes</td>
</tr>
<tr>
<td>Inventory Features</td>
<td>planned</td>
<td><B>yes</B></td>
<td><B>yes?</B></td>
<td>some</td>
<td>no</td>
<td>no</td>
</tr>
<tr>
<td>Implemented In</td>
<td>Python</td>
<td>Ruby</td>
<td>Ruby, Erlang</td>
<td>Python</td>
<td>Ruby</td>
<td>Ruby</td>
</tr>
<tr>
<td>Extensible In</td>
<td><B>Anything</B></td>
<td>Ruby</td>
<td>Ruby</td>
<td>Python</td>
<td>Ruby</td>
<td>Ruby</td>
</tr>
<tr>
<td>Codebase Size</td>
<td><B>Small</B></td>
<td>Large</td>
<td>Large</td>
<td>Medium</td>
<td>Medium</td>
<td>Medium</td>
</tr>
<tr>
<td>Module Support</td>
<td>Emerging</td>
<td><B>Wide/Established</B></td>
<td><B>Wide/Established</B></td>
<td>Medium/Established</td>
<td>Poor</td>
<td>Medium/Established</td>
</tr>
<tr>
<td>Users Targeted</td>
<td><B>Sysadmins, developers, QA, web admins</B></td>
<td>Sysadmins</td>
<td>Sysadmins, web admins</td>
<td>Sysadmins, developers</td>
<td>Web admins</td>
<td>Sysadmins</td>
</tr>
<tr>
<td>Learning & Setup Curve</td>
<td><B>Minimal<B></td>
<td>High</td>
<td>High</td>
<td>Medium</td>
<td>Medium</td>
<td>Medium</td>
</tr>
<tr>
<td>Can Easily Build Applications On It</td>
<td><B>Yes</B></td>
<td>No</td>
<td>No</td>
<td><B>Yes</B></td>
<td>No</td>
<td>Yes?</td>
</tr>
<tr>
<td>Parallelism & Scaling Tech</td>
<td>Fork/Merge</td>
<td>Add Servers / Staged Commands / No Server</td>
<td>Adding More Erlang</td>
<td>Fork/Merge</td>
<td>No</td>
<td>Message Bus</td>
</tr>
<tr>
<td>Delegated Hierachies</td>
<td>No</td>
<td>No</td>
<td>No</td>
<td><B>Yes</B></td>
<td>No</td>
<td><B>Yes</B></td>
</tr>
</td>

2
docs/man/.gitignore vendored
View File

@ -1,2 +1,2 @@
*.xml
*.[0-9]
*.asciidoc

View File

@ -0,0 +1,113 @@
'\" t
.\" Title: ansible-playbook
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 07/24/2012
.\" Manual: System administration commands
.\" Source: Ansible 0.6
.\" Language: English
.\"
.TH "ANSIBLE\-PLAYBOOK" "1" "07/24/2012" "Ansible 0\&.6" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
ansible-playbook \- run an ansible playbook
.SH "SYNOPSIS"
.sp
ansible\-playbook <filename\&.yml> \&... [options]
.SH "DESCRIPTION"
.sp
\fBAnsible playbooks\fR are a configuration and multinode deployment system\&. Ansible\-playbook is the tool used to run them\&. See the project home page (link below) for more information\&.
.SH "ARGUMENTS"
.PP
\fBfilename\&.yml\fR
.RS 4
The names of one or more YAML format files to run as ansible playbooks\&.
.RE
.SH "OPTIONS"
.sp
\fB\-v\fR, \fB\-\-verbose\fR
.sp
Verbose mode, more output from successful actions will be shown
.PP
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
.RS 4
The
\fIPATH\fR
to the inventory hosts file, which defaults to
\fI/etc/ansible/hosts\fR\&.
.RE
.PP
\fB\-M\fR \fIDIRECTORY\fR, \fB\-\-module\-path=\fR\fIDIRECTORY\fR
.RS 4
The
\fIDIRECTORY\fR
to load modules from\&. The default is
\fI/usr/share/ansible\fR\&.
.RE
.PP
\fB\-e\fR \fIVARS\fR, \fB\-\-extra\-vars=\fR\fIVARS\fR
.RS 4
Extra variables to inject into a playbook, in key=value key=value format\&.
.RE
.PP
\fB\-f\fR \fINUM\fR, \fB\-\-forks=\fR\fINUM\fR
.RS 4
Level of parallelism\&.
\fINUM\fR
is specified as an integer, the default is 5\&.
.RE
.PP
\fB\-k\fR, \fB\-\-ask\-pass\fR
.RS 4
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
.RE
.PP
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
.RS 4
Prompt for the password to use for playbook plays that request sudo access, if any
.RE
.PP
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
.RS 4
Connection timeout to use when trying to talk to hosts, in
\fISECONDS\fR\&.
.RE
.PP
\fB\-s\fR, \fB\-\-sudo\fR
.RS 4
Force all plays to use sudo, even if not marked as such\&.
.RE
.PP
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR
.RS 4
Use this remote user name on playbook steps that do not indicate a user name to run as\&.
.RE
.SH "ENVIRONMENT"
.sp
The following environment variables may specified\&.
.sp
ANSIBLE_HOSTS \(em Override the default ansible hosts file
.sp
ANSIBLE_LIBRARY \(em Override the default ansible module library path
.SH "AUTHOR"
.sp
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
.SH "COPYRIGHT"
.sp
Copyright \(co 2012, Michael DeHaan
.sp
Ansible is released under the terms of the GPLv3 License\&.
.SH "SEE ALSO"
.sp
\fBansible\fR(1)
.sp
Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/

View File

@ -0,0 +1,118 @@
ansible-playbook(1)
===================
:doctype:manpage
:man source: Ansible
:man version: %VERSION%
:man manual: System administration commands
NAME
----
ansible-playbook - run an ansible playbook
SYNOPSIS
--------
ansible-playbook <filename.yml> ... [options]
DESCRIPTION
-----------
*Ansible playbooks* are a configuration and multinode deployment
system. Ansible-playbook is the tool used to run them. See the
project home page (link below) for more information.
ARGUMENTS
---------
*filename.yml*::
The names of one or more YAML format files to run as ansible playbooks.
OPTIONS
-------
*-v*, *--verbose*
Verbose mode, more output from successful actions will be shown
*-i* 'PATH', *--inventory=*'PATH'::
The 'PATH' to the inventory hosts file, which defaults to '/etc/ansible/hosts'.
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'.
*-e* 'VARS', *--extra-vars=*'VARS'::
Extra variables to inject into a playbook, in key=value key=value format.
*-f* 'NUM', *--forks=*'NUM'::
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
*-k*, *--ask-pass*::
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
*-K*, *--ask-sudo-pass*::
Prompt for the password to use for playbook plays that request sudo access, if any
*-T* 'SECONDS', *--timeout=*'SECONDS'::
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
*-s*, *--sudo*::
Force all plays to use sudo, even if not marked as such.
*-u* 'USERNAME', *--remote-user=*'USERNAME'::
Use this remote user name on playbook steps that do not indicate a user name to run as.
ENVIRONMENT
-----------
The following environment variables may specified.
ANSIBLE_HOSTS -- Override the default ansible hosts file
ANSIBLE_LIBRARY -- Override the default ansible module library path
AUTHOR
------
Ansible was originally written by Michael DeHaan. See the AUTHORS file
for a complete list of contributors.
COPYRIGHT
---------
Copyright © 2012, Michael DeHaan
Ansible is released under the terms of the GPLv3 License.
SEE ALSO
--------
*ansible*(1)
Extensive documentation as well as IRC and mailing list info
is available on the ansible home page: <https://ansible.github.com/>

170
docs/man/man1/ansible.1 Normal file
View File

@ -0,0 +1,170 @@
'\" t
.\" Title: ansible
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
.\" Date: 07/24/2012
.\" Manual: System administration commands
.\" Source: Ansible 0.6
.\" Language: English
.\"
.TH "ANSIBLE" "1" "07/24/2012" "Ansible 0\&.6" "System administration commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
ansible \- run a command somewhere else
.SH "SYNOPSIS"
.sp
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
.SH "DESCRIPTION"
.sp
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
.SH "ARGUMENTS"
.PP
\fBhost\-pattern\fR
.RS 4
A name of a group in the inventory file, a shell\-like glob selecting hosts in inventory file, or any combination of the two separated by semicolons\&.
.RE
.SH "OPTIONS"
.PP
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
.RS 4
The
\fIPATH\fR
to the inventory hosts file, which defaults to
\fI/etc/ansible/hosts\fR\&.
.RE
.PP
\fB\-f\fR \fINUM\fR, \fB\-\-forks=\fR\fINUM\fR
.RS 4
Level of parallelism\&.
\fINUM\fR
is specified as an integer, the default is 5\&.
.RE
.PP
\fB\-m\fR \fINAME\fR, \fB\-\-module\-name=\fR\fINAME\fR
.RS 4
Execute the module called
\fINAME\fR\&.
.RE
.PP
\fB\-M\fR \fIDIRECTORY\fR, \fB\-\-module\-path=\fR\fIDIRECTORY\fR
.RS 4
The
\fIDIRECTORY\fR
to load modules from\&. The default is
\fI/usr/share/ansible\fR\&.
.RE
.PP
\fB\-a\fR \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\'
.RS 4
The
\fIARGUMENTS\fR
to pass to the module\&.
.RE
.PP
\fB\-k\fR, \fB\-\-ask\-pass\fR
.RS 4
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
.RE
.PP
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
.RS 4
Prompt for the password to use with \-\-sudo, if any
.RE
.PP
\fB\-o\fR, \fB\-\-one\-line\fR
.RS 4
Try to output everything on one line\&.
.RE
.PP
\fB\-s\fR, \fB\-\-sudo\fR
.RS 4
Run the command as the user given by \-u and sudo to root\&.
.RE
.PP
\fB\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR
.RS 4
Save contents in this output
\fIDIRECTORY\fR, with the results saved in a file named after each host\&.
.RE
.PP
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
.RS 4
Connection timeout to use when trying to talk to hosts, in
\fISECONDS\fR\&.
.RE
.PP
\fB\-B\fR \fINUM\fR, \fB\-\-background=\fR\fINUM\fR
.RS 4
Run commands in the background, killing the task after
\fINUM\fR
seconds\&.
.RE
.PP
\fB\-P\fR \fINUM\fR, \fB\-\-poll=\fR\fINUM\fR
.RS 4
Poll a background job every
\fINUM\fR
seconds\&. Requires
\fB\-B\fR\&.
.RE
.PP
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-remote\-user=\fR\fIUSERNAME\fR
.RS 4
Use this remote
\fIUSERNAME\fR
instead of root\&.
.RE
.PP
\fB\-U\fR \fISUDO_USERNAME\fR, \fB\-\-sudo\-user=\fR\fISUDO_USERNAME\fR
.RS 4
Sudo to
\fISUDO_USERNAME\fR
instead of root\&. Implies \-\-sudo\&.
.RE
.PP
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
.RS 4
Connection type to use\&. Possible options are
\fIparamiko\fR
(SSH) and
\fIlocal\fR\&. Local is mostly useful for crontab or kickstarts\&.
.RE
.SH "INVENTORY"
.sp
Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets that start the line\&.
.sp
Ranges of hosts are also supported\&. For more information and additional options, see the documentation on http://ansible\&.github\&.com/\&.
.SH "FILES"
.sp
/etc/ansible/hosts \(em Default inventory file
.sp
/usr/share/ansible/ \(em Default module library
.SH "ENVIRONMENT"
.sp
The following environment variables may specified\&.
.sp
ANSIBLE_HOSTS \(em Override the default ansible hosts file
.sp
ANSIBLE_LIBRARY \(em Override the default ansible module library path
.SH "AUTHOR"
.sp
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
.SH "COPYRIGHT"
.sp
Copyright \(co 2012, Michael DeHaan
.sp
Ansible is released under the terms of the GPLv3 License\&.
.SH "SEE ALSO"
.sp
\fBansible\-playbook\fR(1)
.sp
Extensive documentation as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/

View File

@ -1,108 +0,0 @@
ansible(1)
=========
:doctype:manpage
:man source: Ansible
:man version: 0.0.1
:man manual: System administration commands
NAME
----
ansible - run a command somewhere else
SYNOPSIS
--------
ansible <host-pattern> [-f forks] [-m module_name] [-a args]
DESCRIPTION
-----------
*Ansible* is an extra-simple tool/framework/API for doing \'remote things' over
SSH.
ARGUMENTS
---------
*host-pattern*::
A name of a group in the inventory file, a shell-like glob selecting hosts in inventory
file, or any combination of the two seperated by semicolons.
OPTIONS
-------
*-i*, *--inventory*::
Path to the inventory hosts file, which defaults to /etc/ansible/hosts.
*-f*, *--forks*::
Level of parallelism. Specify as an integer, the default is 5.
*-m*, *--module-name*::
Module name to execute.
*-p*, *--pattern*::
Hostname pattern. Accepts shell-like globs which can be seperated with ";"
The default is "*" which matches all hosts in the ansible hosts file. Group
names from the ansible inventory file can also be used.
See ansible --help for additional options.
INVENTORY
---------
Ansible stores the hosts it can potentially operate on in an inventory
file. The syntax is one host per line. Groups headers are allowed and
are included on their own line, enclosed in square brackets.
FILES
-----
/etc/ansible/hosts -- Default hosts file
/usr/share/ansible -- Default module library
ENVIRONMENT
-----------
The following environment variables may specified.
ANSIBLE_HOSTS -- Override the default ansible hosts file
ANSIBLE_LIBRARY -- Override the default ansible module library path
AUTHOR
------
Ansible was originally written by Michael DeHaan. See the AUTHORS file
for a complete list of contributors.
COPYRIGHT
---------
Copyright © 2012, Michael DeHaan
Ansible is released under the terms of the GPLv3 License.
SEE ALSO
--------
Ansible home page: <https://ansible.github.com/>

View File

@ -0,0 +1,159 @@
ansible(1)
=========
:doctype:manpage
:man source: Ansible
:man version: %VERSION%
:man manual: System administration commands
NAME
----
ansible - run a command somewhere else
SYNOPSIS
--------
ansible <host-pattern> [-f forks] [-m module_name] [-a args]
DESCRIPTION
-----------
*Ansible* is an extra-simple tool/framework/API for doing \'remote things' over
SSH.
ARGUMENTS
---------
*host-pattern*::
A name of a group in the inventory file, a shell-like glob selecting
hosts in inventory file, or any combination of the two separated by
semicolons.
OPTIONS
-------
*-i* 'PATH', *--inventory=*'PATH'::
The 'PATH' to the inventory hosts file, which defaults to '/etc/ansible/hosts'.
*-f* 'NUM', *--forks=*'NUM'::
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
*-m* 'NAME', *--module-name=*'NAME'::
Execute the module called 'NAME'.
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
The 'DIRECTORY' to load modules from. The default is '/usr/share/ansible'.
*-a* \'_ARGUMENTS_', *--args=*\'_ARGUMENTS_'::
The 'ARGUMENTS' to pass to the module.
*-k*, *--ask-pass*::
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
*-K*, *--ask-sudo-pass*::
Prompt for the password to use with --sudo, if any
*-o*, *--one-line*::
Try to output everything on one line.
*-s*, *--sudo*::
Run the command as the user given by -u and sudo to root.
*-t* 'DIRECTORY', *--tree=*'DIRECTORY'::
Save contents in this output 'DIRECTORY', with the results saved in a
file named after each host.
*-T* 'SECONDS', *--timeout=*'SECONDS'::
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
*-B* 'NUM', *--background=*'NUM'::
Run commands in the background, killing the task after 'NUM' seconds.
*-P* 'NUM', *--poll=*'NUM'::
Poll a background job every 'NUM' seconds. Requires *-B*.
*-u* 'USERNAME', *--remote-user=*'USERNAME'::
Use this remote 'USERNAME' instead of root.
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
Sudo to 'SUDO_USERNAME' instead of root. Implies --sudo.
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
Connection type to use. Possible options are 'paramiko' (SSH) and 'local'.
Local is mostly useful for crontab or kickstarts.
INVENTORY
---------
Ansible stores the hosts it can potentially operate on in an inventory
file. The syntax is one host per line. Groups headers are allowed and
are included on their own line, enclosed in square brackets that
start the line.
Ranges of hosts are also supported. For more information and
additional options, see the documentation on http://ansible.github.com/.
FILES
-----
/etc/ansible/hosts -- Default inventory file
/usr/share/ansible/ -- Default module library
ENVIRONMENT
-----------
The following environment variables may specified.
ANSIBLE_HOSTS -- Override the default ansible hosts file
ANSIBLE_LIBRARY -- Override the default ansible module library path
AUTHOR
------
Ansible was originally written by Michael DeHaan. See the AUTHORS file
for a complete list of contributors.
COPYRIGHT
---------
Copyright © 2012, Michael DeHaan
Ansible is released under the terms of the GPLv3 License.
SEE ALSO
--------
*ansible-playbook*(1)
Extensive documentation as well as IRC and mailing list info
is available on the ansible home page: <https://ansible.github.com/>

View File

@ -1,235 +0,0 @@
ansible-modules(5)
=================
:doctype:manpage
:man source: Ansible-modules
:man version: 0.0.1
:man manual: Ansible
NAME
----
ansible-modules - stock modules shipped with ansible
DESCRIPTION
-----------
Ansible ships with a number of modules that can be executed directly on remote hosts or through
ansible playbooks.
IDEMPOTENCE
-----------
Most modules other than command are idempotent, meaning they will seek to avoid changes
unless a change needs to be made. When using ansible playbooks, these modules can
trigger change events, as described in *ansible-playbooks*(5).
Unless otherwise noted, all modules support change hooks.
command
-------
The command module takes the command name followed by a list of arguments, space delimited.
This is the only module that does not use key=value style parameters.
Example usage::
/sbin/shutdown -t now
This module does not support change hooks.
Returns the return code from the program as well as timing information.
(Async command running and command execution time limits are in plan.)
copy
----
The copy module moves a file on the local box to remote locations.
*src=*::
Local absolute path to a file to copy to the remote server
*dest=*::
Remote absolute path where the file should end up
This module also returns md5sum information about the resultant file.
facter
------
Runs the discovery program 'facter' on the remote system, returning
JSON data that can be useful for inventory purposes.
Requires that 'facter' and 'ruby-json' be installed on the remote end.
This module is informative only - it takes no parameters & does not support change hooks,
nor does it make any changes on the system.
file
----
Ensures the ownership and permissions of files are as desired.
Use copy or template first if you need to make sure a file is on the box.
In plan.
git
---
Deploys software from git checkouts.
*repo=*::
git or http protocol address of the repo to checkout
*dest=*::
where to check it out, an absolute directory path
*version=*::
what version to check out -- either the git SHA, the literal string 'HEAD', or a tag name
ohai
----
Similar to the facter module, this returns JSON inventory data. Ohai
data is a bit more verbose and nested than facter.
Requires that 'ohai' be installed on the remote end.
This module is information only - it takes no parameters & does not
support change hooks, nor does it make any changes on the system.
ping
----
A trivial test module, this module always returns the integer '1' on
successful contact.
This module does not support change hooks.
This module is informative only - it takes no parameters & does not
support change hooks, nor does it make any changes on the system.
service
-------
Controls services on remote machines.
*state=*::
Values are 'started', 'stopped', or 'restarted'. Started/stopped
are idempotent actions that will not run commands unless neccessary.
'restarted' will always bounce the service
*name=*::
The name of the service
setup
-----
Writes a JSON file containing key/value data, for use in templating.
Call this once before using the template modules, usually as the very
first step in your playbook.
If facter or ohai are installed, variables from these programs will also
be snapshotted into the JSON file for usage in templating. These variables
are prefixed with 'facter_' and 'ohai_" so it's easy to tell their source.
*metadata=*::
Optionally overrides the default JSON file location of /etc/ansible/setup.
If used, also supply the metadata parameter to 'template'. Change if
running as a non-root remote user who does not have permissions on /etc/ansible.
*anything=*::
any other parameters can be named basically anything, and set a key=value
pair in the JSON file for use in templating.
template
--------
Templates a file out to a remote server. Call the setup module prior to usage.
*src=*::
path of a Jinja2 formatted template on the local server
*dest*::
location to render the template on the remote server
*metadata*::
location of a JSON file to use to supply template data. Default is /etc/ansible/setup
which is the same as the default for the setup module. Change if running as a non-root
remote user who does not have permissions on /etc/ansible.
This module also returns md5sum information about the resultant file.
user
----
This module is in plan.
yum
---
This module is in plan.
WRITING YOUR OWN MODULES
------------------------
To write your own modules, simply follow the convention of those already available in
/usr/share/ansible. Modules must return JSON but can be written in any language.
To support change hooks, modules should return hashes, with a changed: True/False
element at the top level. Modules can also choose to indicate a failure scenario
by returning a top level 'failure' element with a True value.
ENVIRONMENT
-----------
ANSIBLE_LIBRARY -- Override the default ansible module library path
AUTHOR
------
Ansible was originally written by Michael DeHaan. See the AUTHORS file
for a complete list of contributors.
SEE ALSO
--------
*ansible*(1)
*ansible-playbook*(5)
Ansible home page: <https://github.com/mpdehaan/ansible/>

View File

@ -1,123 +0,0 @@
ansible-modules(5)
=================
:doctype:manpage
:man source: Ansible-playbook
:man version: 0.0.1
:man manual: Ansible
NAME
----
ansible-playbook - format and function of an ansible playbook file
DESCRIPTION
-----------
Ansible ships with 'ansible-playbook', a tool for running playbooks.
Playbooks can represent frequent tasks, desired system configurations,
or deployment processes.
FORMAT
------
Playbooks are written in YAML.
EXAMPLE
-------
See:
- https://github.com/mpdehaan/ansible/blob/master/examples/playbook.yml
- https://github.com/mpdehaan/ansible/blob/master/examples/base.yml
- https://github.com/mpdehaan/ansible/blob/master/examples/handlers.yml
WHAT THE EXAMPLE MEANS
-----------------------
Here's what playbook.yml (above) will do.
The first pattern will select all hosts. The patterns are the same
as supported by /usr/bin/ansible.
First, it will run all the modules specified in base.yml. Includes can
be used to implement classes of things, and if you wanted, a playbook
could consist of nothing but include files. This is an example of an
include.
After processing base.yml, on each host we'll write for
a JSON file into /etc/ansible/setup on each remote system with the
values max_clients and http_port.
Next, we'll use a Jinja2 template locally residing at
/srv/templates/httpd.j2 to write the Apache config file on each host,
using the previous values in that setup file.
Next, We'll ensure that apache is running if stopped.
The template task set up a notifier, which means if the configuration
file actually changed, we have a named handler, in this case, 'restart apache'
to run. In this case, all the notifiers come from handlers.yml, though it's
also ok to express handlers directly in the main yaml file too. Using
the include promotes reuse.
What does the handler say? If and only if the config file changed, note that we need to restart
apache at the end of the run, otherwise, don't bother because we
already know it is running.
HIGH LEVEL EXPLANATION
----------------------
Playbooks are executed top down and can contain multiple references to
patterns. For instance, a playbook could do something to all
webservers, then do something to all database servers, then do
something different to all webservers again.
For each pattern, the tasks in the 'tasks' list are executed in order
for all hosts in the host file matching the pattern.
For each task, a name/action pair describes what the task is and what
ansible module to use to accomplish the task, along with any
arguments. Additional fields like 'comment:' can be added and will
be ignored, so feel free to take notes in the file.
Most modules accept key=value format arguments.
Handlers are like tasks, but are conditionally executed. If a module
reports a 'change', it can notify one or more handler by name. If
notified, it will run only for hosts that changed.
ERROR HANDLING
--------------
If a host has a failure, the host will be ignored for the remainder
of the playbook execution.
ENVIRONMENT
-----------
ANSIBLE_LIBRARY -- Override the default ansible module library path
AUTHOR
------
Ansible was originally written by Michael DeHaan. See the AUTHORS file
for a complete list of contributors.
SEE ALSO
--------
*ansible*(1)
*ansible-modules*(5)
Ansible home page: <https://github.com/mpdehaan/ansible/>

View File

@ -1,12 +0,0 @@
[webservers]
alpha.example.org
beta.example.org
192.168.1.100
192.168.1.110
[dbservers]
192.168.1.200
192.168.1.201
foo.example.org
bar.example.org

View File

@ -1,7 +0,0 @@
---
- name: no selinux
action: command /usr/sbin/setenforce 0
- name: no iptables
action: service name=iptables state=stopped
- name: this is just to show variables work here, favcolor={{ favcolor }}
action: command /bin/true

View File

@ -1,5 +0,0 @@
---
- name: restart apache
action: service name=httpd state=restarted
- name: restart memcached
action: service name=memcached state=restarted

44
examples/hosts Normal file
View File

@ -0,0 +1,44 @@
# This is the default ansible 'hosts' file.
#
# It should live in /etc/ansible/hosts
#
# - Comments begin with the '#' character
# - Blank lines are ignored
# - Groups of hosts are delimited by [header] elements
# - You can enter hostnames or ip addresses
# - A hostname/ip can be a member of multiple groups
# Ex 1: Ungrouped hosts, specify before any group headers.
green.example.com
blue.example.com
192.168.100.1
192.168.100.10
# Ex 2: A collection of hosts belonging to the 'webservers' group
[webservers]
alpha.example.org
beta.example.org
192.168.1.100
192.168.1.110
# If you have multiple hosts following a pattern you can specify
# them like this:
www[001:006].example.com
# Ex 3: A collection of database servers in the 'dbservers' group
[dbservers]
db01.intranet.mydomain.net
db02.intranet.mydomain.net
10.25.1.56
10.25.1.57
# Here's another example of host ranges, this time there are no
# leading 0s:
db-[99:101]-node.example.com

View File

@ -1,18 +0,0 @@
---
- hosts: all
user: root
vars:
http_port: 80
max_clients: 200
tasks:
- include: base.yml favcolor=blue
- name: write the foo config file using vars set above
action: template src=foo.j2 dest=/etc/some_random_foo.conf
notify:
- restart apache
- name: ensure apache is running
action: service name=httpd state=started
- name: pointless test action
action: command /bin/echo {{ http_port }}
handlers:
- include: handlers.yml

View File

@ -1,24 +0,0 @@
---
- hosts: '*'
vars:
a: 2
b: 3
c: 4
tasks:
- name: copy comand
action: copy src=/srv/a dest=/srv/b
notify:
- restart apache
- name: template step
action: template src=/srv/template.j2 dest=/srv/file.out
notify:
- restart apache
- name: execute bin false
comment: call something that will fail just to demo failure counts and such
action: command /bin/false
- name: execute bin true
comment: this will never be executed because previous will fail
action: command /bin/true
handlers:
- name: restart apache
action: service name=httpd state=restarted

View File

@ -0,0 +1,51 @@
# ansibple-pull setup
#
# on remote hosts, set up ansible to run periodically using the latest code
# from a particular checkout, in pull based fashion, inverting Ansible's
# usual push-based operating mode.
#
# This particular pull based mode is ideal for:
#
# (A) massive scale out
# (B) continual system remediation
#
# DO NOT RUN THIS AGAINST YOUR HOSTS WITHOUT CHANGING THE repo_url
# TO SOMETHING YOU HAVE PERSONALLY VERIFIED
#
#
---
- hosts: pull_mode_hosts
user: root
vars:
# schdule is fed directly to cron
schedule: '*/15 * * * *'
# User to run ansible-pull as from cron
cron_user: root
# Directory to where repository will be cloned
workdir: /var/lib/ansible/local
# Repository to check out -- YOU MUST CHANGE THIS
# repo must contain a local.yml file at top level
#repo_url: git://github.com/sfromm/ansible-playbooks.git
repo_url: SUPPLY_YOUR_OWN_GIT_URL_HERE
tasks:
- name: Install ansible
action: yum pkg=ansible state=installed
- name: Create local directory to work from
action: file path=$workdir state=directory owner=root group=root mode=0751
- name: Copy ansible inventory file to client
action: copy src=/etc/ansible/hosts dest=/etc/ansible/hosts
owner=root group=root mode=0644
- name: Create crontab entry to clone/pull git repository
action: template src=templates/ansible-pull.j2 dest=/etc/cron.d/ansible-pull owner=root group=root mode=0644

View File

@ -0,0 +1,51 @@
---
# this is a demo of conditional imports. This is a powerful concept
# and can be used to use the same recipe for different types of hosts,
# based on variables that bubble up from the hosts from tools such
# as ohai or facter.
#
# Here's an example use case:
#
# what to do if the service for apache is named 'httpd' on CentOS
# but is named 'apache' on Debian?
# there is only one play in this playbook, it runs on all hosts
# as root
- hosts: all
user: root
# we have a common list of variables stored in /vars/external_vars.yml
# that we will always import
# next, we want to import files that are different per operating system
# and if no per operating system file is found, load a defaults file.
# for instance, if the OS was "CentOS", we'd try to load vars/CentOS.yml.
# if that was found, we would immediately stop. However if that wasn't
# present, we'd try to load vars/defaults.yml. If that in turn was not
# found, we would fail immediately, because we had gotten to the end of
# the list without importing anything.
vars_files:
- "vars/external_vars.yml"
- [ "vars/$facter_operatingsystem.yml", "vars/defaults.yml" ]
# and this is just a regular task line from a playbook, as we're used to.
# but with variables in it that come from above. Note that the variables
# from above are *also* available in templates
tasks:
- name: ensure apache is latest
action: $packager pkg=$apache state=latest
- name: ensure apache is running
action: service name=$apache state=running
- name: fail
action: command /bin/false

View File

@ -0,0 +1,47 @@
---
# this is a demo of conditional executions using 'only_if', which can skip
# certain tasks on machines/platforms/etc where they do not apply.
- hosts: all
user: root
vars:
favcolor: "red"
ssn: 8675309
# Below we're going to define some expressions.
#
# Not only can we assign variables for reuse, but we can also assign conditional
# expressions. By keeping these in 'vars', the task section remains
# extraordinarily clean, and not littered with programming language
# constructs -- so it's easily skimmed by humans.
#
# Remember to quote any variables if they are not numbers!
#
# Interesting fact: aside from the $variables, these expressions are actually
# tiny bits of Python. They are evaluated in the context of each host, so different
# steps can be skipped on different hosts! They should evaluate to either True
# or False
is_favcolor_blue: "'$favcolor' == 'blue'"
is_centos: "'$facter_operatingsystem' == 'CentOS'"
# NOTE:
#
# facter and ohai variables can be used in only_if statements too
# ex: "'$facter_operatingsystem' == 'CentOS'", which bubble up automatically
# from the managed machines
#
# this example won't do that though, as you might not have facter or ohai,
# but you get the idea...
tasks:
- name: "do this if my favcolor is blue"
action: shell /bin/false
only_if: '$is_favcolor_blue'
- name: "do this if my favcolor is not blue"
action: shell /bin/true
only_if: 'not ($is_favcolor_blue)'

View File

@ -0,0 +1,18 @@
---
# This is a demo of how to manage the selinux context using the file module
- hosts: test
user: root
tasks:
- name: Change setype of /etc/exports to non-default value
action: file path=/etc/exports setype=etc_t
- name: Change seuser of /etc/exports to non-default value
action: file path=/etc/exports seuser=unconfined_u
- name: Set selinux context back to default value
action: file path=/etc/exports context=default
- name: Create empty file
action: command /bin/touch /tmp/foo
- name: Change setype of /tmp/foo
action: file path=/tmp/foo setype=default_t
- name: Try to set secontext to default, but this will fail
because of the lack of a default in the policy
action: file path=/tmp/foo context=default

View File

@ -0,0 +1,16 @@
---
- hosts: webservers
vars:
- jquery_directory: /var/www/html/javascript
- person: 'Susie%20Smith'
tasks:
- name: Create directory for jQuery
action: file dest=${jquery_directory} state=directory mode=0755
- name: Grab a bunch of jQuery stuff
action: get_url url=http://code.jquery.com/$item dest=${jquery_directory} mode=0444
with_items:
- jquery.min.js
- mobile/latest/jquery.mobile.min.js
- ui/jquery-ui-git.css
#- name: Pass urlencoded name to CGI
# action: get_url url=http://example.com/name.cgi?name='${person}' dest=/tmp/test

View File

@ -0,0 +1,18 @@
---
# This is a demo of how the group command works.
- hosts: all
user: root
sudo: True
tasks:
# Walk through group creation, modification, and deletion
- name: create a group
action: group name=tset
# You can only modify the group's gid
- action: group name=tset gid=7777
# And finally remove the group
- action: group name=tset state=absent

View File

@ -0,0 +1,10 @@
---
# this is an example to show that handlers can be included from yaml files,
# to promote reuse between different plays or even playbooks. They work
# just like normal handlers.
- name: restart apache
action: service name=httpd state=restarted
- name: restart memcached
action: service name=memcached state=restarted

View File

@ -0,0 +1,89 @@
---
# see examples.yml first!
# This file explains some more advanced features of playbooks.
# because of the comments it's less concise than it normally is. But feel
# free to comment your playbooks if you like.
- hosts: all
# we can define variables the normal way...
vars:
release: 2.0
# but they can also come from other files. This can be a relative
# or absolute path. This is a good way to store 'secret' variable
# files but still keep the playbook in public source control
vars_files:
- vars/external_vars.yml
# as with before, every play has a list of tasks in it
tasks:
# tasks can be written the normal way...
- name: arbitrary command
action: command /bin/true
# or we can promote reuse and simplicity by including tasks
# from other files, for instance, to reuse common tasks
- include: tasks/base.yml
# we could also have done something like:
# - include: wordpress.yml user=timmy
# and had access to the template variable $user in the
# included file, if we wanted to. Variables from vars
# and vars_files are also available inside include files
handlers:
# handlers can also be included from files, to promote reuse
# and simpler recipes, you may wish to only have one
# handler file for all your plays and playbooks
- include: handlers/handlers.yml
# you can mix things that are directly in the file with things
# that are included. Order is executed as written, but only
# handlers that have been notified get executed
- name: restart foo
action: service name=foo state=restarted
# ===============================================================
# Here's a second play in the same playbook. This will be run
# after the first playbook completes on all hosts. You may want
# a different play for each class of systems, or may want a different
# play for each stage in a complex multi-node deployment push
# process. How you use them are up to you.
# any play in a playbook can be executed by a user other than root
# if you want. sudo support is coming too.
- hosts: webservers
user: mdehaan
# vars must be specified again for the next play in the playbook
# but can be reused by including from vars_files if you want
# you can use vars, vars_files, or both. vars_files overrides
# those set in vars.
vars:
release: 2.0
vars_files:
- vars/external_vars.yml
# these all runs as the user 'mdehaan'. If there were any handlers
# they would as well.
tasks:
- name: some random command
action: command /bin/true

View File

@ -0,0 +1,76 @@
---
# this is an annotated example of some features available in playbooks
# it shows how to make sure packages are updated, how to make sure
# services are running, and how to template files. It also demos
# change handlers that can restart things (or trigger other actions)
# when resources change. For more advanced examples, see example2.yml
# on all hosts, run as the user root...
- name: example play
hosts: all
user: root
# could have also have done:
# user: mdehaan
# sudo: True
# make these variables available inside of templates
# for when we use the 'template' action/module later on...
vars:
http_port: 80
max_clients: 200
# define the tasks that are part of this play...
tasks:
# task #1 is to run an arbitrary command
# we'll simulate a long running task, wait for up to 45 seconds, poll every 5
# obviously this does nothing useful but you get the idea
- name: longrunner
action: command /bin/sleep 15
async: 45
poll: 5
# let's demo file operations.
#
# We can 'copy' files or 'template' them instead, using jinja2
# as the templating engine. This is done using the variables
# from the vars section above mixed in with variables bubbled up
# automatically from tools like facter and ohai. 'copy'
# works just like 'template' but does not do variable subsitution.
#
# If and only if the file changes, restart apache at the very
# end of the playbook run
- name: write some_random_foo configuration
action: template src=templates/foo.j2 dest=/etc/some_random_foo.conf
notify:
- restart apache
# make sure httpd is installed at the latest version
- name: install httpd
action: yum pkg=httpd state=latest
# make sure httpd is running
- name: httpd start
action: service name=httpd state=running
# handlers are only run when things change, at the very end of each
# play. Let's define some. The names are significant and must
# match the 'notify' sections above
handlers:
# this particular handler is run when some_random_foo.conf
# is changed, and only then
- name: restart apache
action: service name=httpd state=restarted

View File

@ -0,0 +1,30 @@
---
# this is an example of how to run repeated task elements over lists
# of items, for example, installing multiple packages or configuring
# multiple users
- hosts: all
user: root
tasks:
- name: install $item
action: yum pkg=$item state=installed
with_items:
- cobbler
- httpd
- name: configure user $item
action: user name=$item state=present groups=wheel
with_items:
- testuser1
- testuser2
- name: remove user $item
action: user name=$item state=absent
with_items:
- testuser1
- testuser2

View File

@ -0,0 +1,21 @@
##
# Example Ansible playbook that uses the MySQL module.
#
---
- hosts: all
user: root
vars:
mysql_root_password: 'password'
tasks:
- name: Create database user
action: mysql_user loginpass=$mysql_root_password user=bob passwd=12345 priv=*.*:ALL state=present
- name: Create database
action: mysql_db loginpass=$mysql_root_password db=bobdata state=present
- name: Ensure no user named 'sally' exists
action: mysql_user loginpass=$mysql_root_password user=sally state=absent

View File

@ -0,0 +1,26 @@
---
# it is possible to have top level playbook files import other playbook
# files. For example, a playbook called could include three
# different playbooks, such as webservers, workers, dbservers, etc.
#
# Running the site playbook would run all playbooks, while individual
# playbooks could still be run directly. This is somewhat like
# the tag feature and can be used in conjunction for very fine grained
# control over what you want to target when running ansible.
- name: this is a play at the top level of a file
hosts: all
user: root
tasks:
- name: say hi
tags: foo
action: shell echo "hi..."
# and this is how we include another playbook, be careful and
# don't recurse infinitely or anything. Note you can't use
# any variables here.
- include: intro_example.yml
# and if we wanted, we can continue with more includes here,
# or more plays inline in this file

View File

@ -0,0 +1,38 @@
##
# Example Ansible playbook that uses the PostgreSQL module.
#
# This installs PostgreSQL on an Ubuntu system, creates a database called
# "myapp" and a user called "django" with password "mysupersecretpassword"
# with access to the "myapp" database.
#
---
- hosts: webservers
sudo: True
gather_facts: False
tasks:
- name: ensure apt cache is up to date
action: apt update_cache=yes
- name: ensure packages are installed
action: apt pkg=$item
with_items:
- postgresql
- libpq-dev
- python-psycopg2
- hosts: webservers
sudo: True
sudo_user: postgres
gather_facts: False
vars:
dbname: myapp
dbuser: django
dbpassword: mysupersecreetpassword
tasks:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure user has access to database
action: postgresql_user db=$dbname user=$dbuser password=$dbpassword

View File

@ -0,0 +1,43 @@
---
# it is possible to ask for variables from the user at the start
# of a playbook run, for example, as part of a release script.
- hosts: all
user: root
# regular variables are a dictionary of keys and values
vars:
this_is_a_regular_var: 'moo'
so_is_this: 'quack'
# alternatively, they can ALSO be passed in from the outside:
# ansible-playbook foo.yml --extra-vars="foo=100 bar=101"
# or through external inventory scripts (see online API docs)
# here's basic mode prompting. Specify a hash of variable names and a prompt for
# each.
#
# vars_prompt:
# release_version: "product release version"
# prompts can also be specified like this, allowing for hiding the prompt as
# entered. In the future, this may also be used to support crypted variables
vars_prompt:
- name: "some_password"
prompt: "Enter password"
private: True
- name: "release_version"
prompt: "Product release version"
private: False
# this is just a simple example to show that vars_prompt works, but
# you might ask for a tag to use with the git module or perhaps
# a package version to use with the yum module.
tasks:
- name: imagine this did something interesting with $release_version
action: shell echo foo >> /tmp/$release_version-$alpha

View File

@ -0,0 +1,28 @@
---
# this is an example of how to template a file over using some variables derived
# from the system. For instance, if you wanted to have different configuration
# templates by OS version, this is a neat way to do it. Any Ansible facts, facter facts,
# or ohai facts could be used to do this.
- hosts: all
tasks:
- name: template a config file
action: template dest=/etc/imaginary_file.conf
first_available_file:
# first see if we have a file for this specific host
- /srv/whatever/${ansible_hostname}.conf
# next try to load something like CentOS6.2.conf
- /srv/whatever/${ansible_distribution}${ansible_distribution_version}.conf
# next see if there's a CentOS.conf
- /srv/whatever/${ansible_distribution}.conf
# finally give up and just use something generic
- /srv/whatever/default

View File

@ -0,0 +1,44 @@
---
# tags allow us to run all of a playbook or part of it.
#
# assume: ansible-playbook tags.yml --tags foo
#
# try this with:
# --tags foo
# --tags bar
# --tags extra
#
# the value of a 'tags:' element can be a string or list
# of tag names. Variables are not usable in tag names.
- name: example play one
hosts: all
user: root
# any tags applied to the play are shorthand to applying
# the tag to all tasks in it. Here, each task is given
# the tag extra
tags:
- extra
tasks:
# this task will run if you don't specify any tags,
# if you specify 'foo' or if you specify 'extra'
- name: hi
tags: foo
action: shell echo "first task ran"
- name: example play two
hosts: all
user: root
tasks:
- name: hi
tags:
- bar
action: shell echo "second task ran"
- include: tasks/base.yml tags=base

View File

@ -0,0 +1,22 @@
---
# this is the example of an included tasks file. It contains a flat list of tasks
# they can notify other tasks, and have full access to variables from 'vars'
# or 'vars_files' directives. Further, if ohai or facter were installed on
# the remote machines, variables from those tools can be accessed on the 'action'
# line or in templates. Just prefix with 'facter_' or 'ohai_' before the particular
# variable.
# possible uses for a included yaml file might be to represent a 'class' of a system
# like defining what makes up a webserver, or you might have a common 'base.yml'
# (like this) that might be applied to all your systems as well.
- name: no selinux
action: command /usr/sbin/setenforce 0
- name: no iptables
action: service name=iptables state=stopped
- name: made up task just to show variables work here
action: command /bin/echo release is $release

View File

@ -0,0 +1,2 @@
# Cron job to git clone/pull a repo and then run locally
{{ schedule }} {{ cron_user }} ansible-pull -d {{ workdir }} -U {{ repo_url }} >/var/log/ansible-pull.log 2>&1

View File

@ -0,0 +1,10 @@
# This is a very simple Jinja2 template representing an imaginary configuration file
# for an imaginary app.
# this is an example of loading a fact from the setup module
system={{ ansible_system }}
# here is a variable that could be set in a playbook or inventory file
http_port={{ http_port }}

View File

@ -0,0 +1,38 @@
---
# this is a demo of how the user commands work and how to reference salted passwords
# in vars sections. You could also use vars_files if you like (see other examples)
- hosts: all
user: root
vars:
# created with:
# crypt.crypt('This is my Password', '$1$SomeSalt')
password: $1$SomeSalt$UqddPX3r4kH3UL5jq5/ZI.
tasks:
# Walk through account creation, modification, and deletion
- name: test basic user account creation
action: user name=tset comment=TsetUser group=users shell=/sbin/nologin createhome=no
# the following is just a simple example of how you don't have to include
# the 'name' element for each task
- action: user name=tset comment=NyetUser
- action: user name=tset password=$password
# The following will add the user to supplementary groups.
# Add the user to the groups dialout and uucp.
- action: user name=tset groups=dialout,uucp
# Add the user to the groups dialout and wheel,
# This will remove tset from the group uucp.
- action: user name=tset groups=dialout,wheel
# Add the user to the group uucp. Because append=yes, the user
# will not be removed from the groups dialout and wheel.
- action: user name=tset groups=uucp append=yes
# Finally, remove the user.
- action: user name=tset state=absent

View File

@ -0,0 +1,3 @@
---
apache: httpd
packager: yum

View File

@ -0,0 +1,3 @@
---
packager: apt
apache: apache

View File

@ -0,0 +1,3 @@
---
alpha: one
beta: two

View File

@ -0,0 +1,131 @@
#!/usr/bin/python
"""
Cobbler external inventory script
=================================
Ansible has a feature where instead of reading from /etc/ansible/hosts
as a text file, it can query external programs to obtain the list
of hosts, groups the hosts are in, and even variables to assign to each host.
To use this, copy this file over /etc/ansible/hosts and chmod +x the file.
This, more or less, allows you to keep one central database containing
info about all of your managed instances.
This script is an example of sourcing that data from Cobbler
(http://cobbler.github.com). With cobbler each --mgmt-class in cobbler
will correspond to a group in Ansible, and --ks-meta variables will be
passed down for use in templates or even in argument lines.
NOTE: The cobbler system names will not be used. Make sure a
cobbler --dns-name is set for each cobbler system. If a system
appears with two DNS names we do not add it twice because we don't want
ansible talking to it twice. The first one found will be used. If no
--dns-name is set the system will NOT be visible to ansible. We do
not add cobbler system names because there is no requirement in cobbler
that those correspond to addresses.
See http://ansible.github.com/api.html for more info
Tested with Cobbler 2.0.11.
"""
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
import sys
import xmlrpclib
import shlex
try:
import json
except:
import simplejson as json
# NOTE -- this file assumes Ansible is being accessed FROM the cobbler
# server, so it does not attempt to login with a username and password.
# this will be addressed in a future version of this script.
conn = xmlrpclib.Server("http://127.0.0.1/cobbler_api", allow_none=True)
###################################################
# executed with no parameters, return the list of
# all groups and hosts
if len(sys.argv) == 2 and (sys.argv[1] == '--list'):
systems = conn.get_item_names('system')
groups = { 'ungrouped' : [] }
for system in systems:
data = conn.get_blended_data(None, system)
dns_name = None
interfaces = data['interfaces']
for (iname, ivalue) in interfaces.iteritems():
this_dns_name = ivalue.get('dns_name', None)
if this_dns_name is not None:
dns_name = this_dns_name
if dns_name is None:
continue
classes = data['mgmt_classes']
for cls in classes:
if cls not in groups:
groups[cls] = []
# hostname is not really what we want to insert, really insert the
# first DNS name but no further DNS names
groups[cls].append(dns_name)
print json.dumps(groups)
sys.exit(0)
#####################################################
# executed with a hostname as a parameter, return the
# variables for that host
elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
# look up the system record for the given DNS name
result = conn.find_system_by_dns_name(sys.argv[2])
system = result.get('name', None)
data = {}
if system is None:
print json.dumps({})
sys.exit(1)
data = conn.get_system_for_koan(system)
# return the ksmeta data for that system
metadata = data['ks_meta']
tokens = shlex.split(metadata)
results = {}
for t in tokens:
if t.find("=") != -1:
(k,v) = t.split("=",1)
results[k]=v
print json.dumps(results)
sys.exit(0)
else:
print "usage: --list ..OR.. --host <hostname>"
sys.exit(1)

42
examples/scripts/ec2.ini Normal file
View File

@ -0,0 +1,42 @@
# Ansible EC2 external inventory script settings
#
[ec2]
# AWS regions to make calls to. Set this to 'all' to make request to all regions
# in AWS and merge the results together. Alternatively, set this to a comma
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
regions = all
# When generating inventory, Ansible needs to know how to address a server.
# Each EC2 instance has a lot of variables associated with it. Here is the list:
# http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance
# Below are 2 variables that are used as the address of a server:
# - destination_variable
# - vpc_destination_variable
# This is the normal destination variable to use. If you are running Ansible
# from outside EC2, then 'public_dns_name' makes the most sense. If you are
# running Ansible from within EC2, then perhaps you want to use the internal
# address, and should set this to 'private_dns_name'.
destination_variable = public_dns_name
# For server inside a VPC, using DNS names may not make sense. When an instance
# has 'subnet_id' set, this variable is used. If the subnet is public, setting
# this to 'ip_address' will return the public IP address. For instances in a
# private subnet, this should be set to 'private_ip_address', and Ansible must
# be run from with EC2.
vpc_destination_variable = ip_address
# API calls to EC2 are slow. For this reason, we cache the results of an API
# call. Set this to the path you want cache files to be written to. Two files
# will be written to this directory:
# - ansible-ec2.cache
# - ansible-ec2.index
cache_path = /tmp
# The number of seconds a cache file is considered valid. After this many
# seconds, a new API call will be made, and the cache file will be updated.
cache_max_age = 300

View File

@ -0,0 +1,377 @@
#!/usr/bin/python -tt
'''
EC2 external inventory script
=================================
Generates inventory that Ansible can understand by making API request to
AWS EC2 using the Boto library.
NOTE: This script assumes Ansible is being executed where the environment
variables needed for Boto have already been set:
export AWS_ACCESS_KEY_ID='AK123'
export AWS_SECRET_ACCESS_KEY='abc123'
For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html
When run against a specific host, this script returns the following variables:
- ec2_ami_launch_index
- ec2_architecture
- ec2_association
- ec2_attachTime
- ec2_attachment
- ec2_attachmentId
- ec2_client_token
- ec2_deleteOnTermination
- ec2_description
- ec2_deviceIndex
- ec2_dns_name
- ec2_eventsSet
- ec2_group_name
- ec2_hypervisor
- ec2_id
- ec2_image_id
- ec2_instanceState
- ec2_instance_type
- ec2_ipOwnerId
- ec2_ip_address
- ec2_item
- ec2_kernel
- ec2_key_name
- ec2_launch_time
- ec2_monitored
- ec2_monitoring
- ec2_networkInterfaceId
- ec2_ownerId
- ec2_persistent
- ec2_placement
- ec2_platform
- ec2_previous_state
- ec2_private_dns_name
- ec2_private_ip_address
- ec2_publicIp
- ec2_public_dns_name
- ec2_ramdisk
- ec2_reason
- ec2_region
- ec2_requester_id
- ec2_root_device_name
- ec2_root_device_type
- ec2_security_group_ids
- ec2_security_group_names
- ec2_shutdown_state
- ec2_sourceDestCheck
- ec2_spot_instance_request_id
- ec2_state
- ec2_state_code
- ec2_state_reason
- ec2_status
- ec2_subnet_id
- ec2_tenancy
- ec2_virtualization_type
- ec2_vpc_id
These variables are pulled out of a boto.ec2.instance object. There is a lack of
consistency with variable spellings (camelCase and underscores) since this
just loops through all variables the object exposes. It is preferred to use the
ones with underscores when multiple exist.
In addition, if an instance has AWS Tags associated with it, each tag is a new
variable named:
- ec2_tag_[Key] = [Value]
Security groups are comma-separated in 'ec2_security_group_ids' and
'ec2_security_group_names'.
'''
# (c) 2012, Peter Sankauskas
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
import os
import argparse
import re
from time import time
from boto import ec2
import ConfigParser
try:
import json
except ImportError:
import simplejson as json
class Ec2Inventory(object):
def __init__(self):
''' Main execution path '''
# Inventory grouped by instance IDs, tags, security groups, regions,
# and availability zones
self.inventory = {}
# Index of hostname (address) to instance ID
self.index = {}
# Read settings and parse CLI arguments
self.read_settings()
self.parse_cli_args()
# Cache
if self.args.refresh_cache:
self.do_api_calls_update_cache()
elif not self.is_cache_valid():
self.do_api_calls_update_cache()
# Data to print
if self.args.host:
data_to_print = self.get_host_info()
elif self.args.list:
# Display list of instances for inventory
if len(self.inventory) == 0:
data_to_print = self.get_inventory_from_cache()
else:
data_to_print = self.json_format_dict(self.inventory, True)
print data_to_print
def is_cache_valid(self):
''' Determines if the cache files have expired, or if it is still valid '''
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if os.path.isfile(self.cache_path_index):
return True
return False
def read_settings(self):
''' Reads the settings from the ec2.ini file '''
config = ConfigParser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/ec2.ini')
# Regions
self.regions = []
configRegions = config.get('ec2', 'regions')
if (configRegions == 'all'):
for regionInfo in ec2.regions():
self.regions.append(regionInfo.name)
else:
self.regions = configRegions.split(",")
# Destination addresses
self.destination_variable = config.get('ec2', 'destination_variable')
self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable')
# Cache related
cache_path = config.get('ec2', 'cache_path')
self.cache_path_cache = cache_path + "/ansible-ec2.cache"
self.cache_path_index = cache_path + "/ansible-ec2.index"
self.cache_max_age = config.getint('ec2', 'cache_max_age')
def parse_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
self.args = parser.parse_args()
def do_api_calls_update_cache(self):
''' Do API calls to each region, and save data in cache files '''
for region in self.regions:
self.get_instances_by_region(region)
self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index)
def get_instances_by_region(self, region):
''' Makes an AWS EC2 API call to the list of instances in a particular
region '''
conn = ec2.connect_to_region(region)
reservations = conn.get_all_instances()
for reservation in reservations:
for instance in reservation.instances:
self.add_instance(instance, region)
def get_instance(self, region, instance_id):
''' Gets details about a specific instance '''
conn = ec2.connect_to_region(region)
reservations = conn.get_all_instances([instance_id])
for reservation in reservations:
for instance in reservation.instances:
return instance
def add_instance(self, instance, region):
''' Adds an instance to the inventory and index, as long as it is
addressable '''
# Only want running instances
if instance.state == 'terminated':
return
# Select the best destination address
if instance.subnet_id:
dest = getattr(instance, self.vpc_destination_variable)
else:
dest = getattr(instance, self.destination_variable)
if dest == None:
# Skip instances we cannot address (e.g. private VPC subnet)
return
# Add to index
self.index[dest] = [region, instance.id]
# Inventory: Group by instance ID (always a group of 1)
self.inventory[instance.id] = [dest]
# Inventory: Group by region
self.push(self.inventory, region, dest)
# Inventory: Group by availability zone
self.push(self.inventory, instance.placement, dest)
# Inventory: Group by security group
for group in instance.groups:
key = self.to_safe("security_group_" + group.name)
self.push(self.inventory, key, dest)
# Inventory: Group by tag keys
for k, v in instance.tags.iteritems():
key = self.to_safe("tag_" + k + "=" + v)
self.push(self.inventory, key, dest)
def get_host_info(self):
''' Get variables about a specific host '''
if len(self.index) == 0:
# Need to load index from cache
self.load_index_from_cache()
(region, instance_id) = self.index[self.args.host]
instance = self.get_instance(region, instance_id)
instance_vars = {}
for key in vars(instance):
value = getattr(instance, key)
key = self.to_safe('ec2_' + key)
# Handle complex types
if type(value) in [int, bool]:
instance_vars[key] = value
elif type(value) in [str, unicode]:
instance_vars[key] = value.strip()
elif type(value) == type(None):
instance_vars[key] = ''
elif key == 'ec2_region':
instance_vars[key] = value.name
elif key == 'ec2_tags':
for k, v in value.iteritems():
key = self.to_safe('ec2_tag_' + k)
instance_vars[key] = v
elif key == 'ec2_groups':
group_ids = []
group_names = []
for group in value:
group_ids.append(group.id)
group_names.append(group.name)
instance_vars["ec2_security_group_ids"] = ','.join(group_ids)
instance_vars["ec2_security_group_names"] = ','.join(group_names)
else:
pass
# TODO Product codes if someone finds them useful
#print key
#print type(value)
#print value
return self.json_format_dict(instance_vars, True)
def push(self, my_dict, key, element):
''' Pushed an element onto an array that may not have been defined in
the dict '''
if key in my_dict:
my_dict[key].append(element);
else:
my_dict[key] = [element]
def get_inventory_from_cache(self):
''' Reads the inventory from the cache file and returns it as a JSON
object '''
cache = open(self.cache_path_cache, 'r')
json_inventory = cache.read()
return json_inventory
def load_index_from_cache(self):
''' Reads the index from the cache file sets self.index '''
cache = open(self.cache_path_index, 'r')
json_index = cache.read()
self.index = json.loads(json_index)
def write_to_cache(self, data, filename):
''' Writes data in JSON format to a file '''
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be
used as Ansible groups '''
return re.sub("[^A-Za-z0-9\-]", "_", word)
def json_format_dict(self, data, pretty=False):
''' Converts a dict to a JSON object and dumps it as a formatted
string '''
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
# Run the script
Ec2Inventory()

View File

@ -0,0 +1,206 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import ansible.constants as C
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible import errors
from ansible import utils
import os
import yaml
import sys
class InventoryParserYaml(object):
''' Host inventory parser for ansible '''
def __init__(self, filename=C.DEFAULT_HOST_LIST):
sys.stderr.write("WARNING: YAML inventory files are deprecated in 0.6 and will be removed in 0.7, to migrate" +
" download and run https://github.com/ansible/ansible/blob/devel/examples/scripts/yaml_to_ini.py\n")
fh = open(filename)
data = fh.read()
fh.close()
self._hosts = {}
self._parse(data)
def _make_host(self, hostname):
if hostname in self._hosts:
return self._hosts[hostname]
else:
host = Host(hostname)
self._hosts[hostname] = host
return host
# see file 'test/yaml_hosts' for syntax
def _parse(self, data):
# FIXME: refactor into subfunctions
all = Group('all')
ungrouped = Group('ungrouped')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
grouped_hosts = []
yaml = utils.parse_yaml(data)
# first add all groups
for item in yaml:
if type(item) == dict and 'group' in item:
group = Group(item['group'])
for subresult in item.get('hosts',[]):
if type(subresult) in [ str, unicode ]:
host = self._make_host(subresult)
group.add_host(host)
grouped_hosts.append(host)
elif type(subresult) == dict:
host = self._make_host(subresult['host'])
vars = subresult.get('vars',{})
if type(vars) == list:
for subitem in vars:
for (k,v) in subitem.items():
host.set_variable(k,v)
elif type(vars) == dict:
for (k,v) in subresult.get('vars',{}).items():
host.set_variable(k,v)
else:
raise errors.AnsibleError("unexpected type for variable")
group.add_host(host)
grouped_hosts.append(host)
vars = item.get('vars',{})
if type(vars) == dict:
for (k,v) in item.get('vars',{}).items():
group.set_variable(k,v)
elif type(vars) == list:
for subitem in vars:
if type(subitem) != dict:
raise errors.AnsibleError("expected a dictionary")
for (k,v) in subitem.items():
group.set_variable(k,v)
self.groups[group.name] = group
all.add_child_group(group)
# add host definitions
for item in yaml:
if type(item) in [ str, unicode ]:
host = self._make_host(item)
if host not in grouped_hosts:
ungrouped.add_host(host)
elif type(item) == dict and 'host' in item:
host = self._make_host(item['host'])
vars = item.get('vars', {})
if type(vars)==list:
varlist, vars = vars, {}
for subitem in varlist:
vars.update(subitem)
for (k,v) in vars.items():
host.set_variable(k,v)
groups = item.get('groups', {})
if type(groups) in [ str, unicode ]:
groups = [ groups ]
if type(groups)==list:
for subitem in groups:
if subitem in self.groups:
group = self.groups[subitem]
else:
group = Group(subitem)
self.groups[group.name] = group
all.add_child_group(group)
group.add_host(host)
grouped_hosts.append(host)
if host not in grouped_hosts:
ungrouped.add_host(host)
# make sure ungrouped.hosts is the complement of grouped_hosts
ungrouped_hosts = [host for host in ungrouped.hosts if host not in grouped_hosts]
if __name__ == "__main__":
if len(sys.argv) != 2:
print "usage: yaml_to_ini.py /path/to/ansible/hosts"
sys.exit(1)
result = ""
original = sys.argv[1]
yamlp = InventoryParserYaml(filename=sys.argv[1])
dirname = os.path.dirname(original)
group_names = [ g.name for g in yamlp.groups.values() ]
for group_name in sorted(group_names):
record = yamlp.groups[group_name]
if group_name == 'all':
continue
hosts = record.hosts
result = result + "[%s]\n" % record.name
for h in hosts:
result = result + "%s\n" % h.name
result = result + "\n"
groupfiledir = os.path.join(dirname, "group_vars")
if not os.path.exists(groupfiledir):
print "* creating: %s" % groupfiledir
os.makedirs(groupfiledir)
groupfile = os.path.join(groupfiledir, group_name)
print "* writing group variables for %s into %s" % (group_name, groupfile)
groupfh = open(groupfile, 'w')
groupfh.write(yaml.dump(record.get_variables()))
groupfh.close()
for (host_name, host_record) in yamlp._hosts.iteritems():
hostfiledir = os.path.join(dirname, "host_vars")
if not os.path.exists(hostfiledir):
print "* creating: %s" % hostfiledir
os.makedirs(hostfiledir)
hostfile = os.path.join(hostfiledir, host_record.name)
print "* writing host variables for %s into %s" % (host_record.name, hostfile)
hostfh = open(hostfile, 'w')
hostfh.write(yaml.dump(host_record.get_variables()))
hostfh.close()
# also need to keep a hash of variables per each host
# and variables per each group
# and write those to disk
newfilepath = os.path.join(dirname, "hosts.new")
fdh = open(newfilepath, 'w')
fdh.write(result)
fdh.close()
print "* COMPLETE: review your new inventory file and replace your original when ready"
print "* new inventory file saved as %s" % newfilepath
print "* edit group specific variables in %s/group_vars/" % dirname
print "* edit host specific variables in %s/host_vars/" % dirname
# now need to write this to disk as (oldname).new
# and inform the user

View File

@ -6,6 +6,3 @@ To use it from the root of a checkout:
$ . ./hacking/env-setup
Note the space between the '.' and the './'
Man pages will not load until you run 'make docs' from the root of the
checkout.

View File

@ -1,18 +1,35 @@
# -*- mode: shell-script -*-
PREFIX_PYTHONPATH="`pwd`/lib"
PREFIX_PATH="`pwd`/bin"
PREFIX_MANPATH="`pwd`/docs/man"
PREFIX_ANS_LIBRARY="`pwd`/library"
#!/bin/bash
# usage: source ./hacking/env-setup [-q]
# modifies environment for running Ansible from checkout
# When run using source as directed, $0 gets set to bash, so we must use $BASH_SOURCE
if [ -n "$BASH_SOURCE" ] ; then
HACKING_DIR=`dirname $BASH_SOURCE`
else
HACKING_DIR="$PWD/hacking"
fi
# The below is an alternative to readlink -fn which doesn't exist on OS X
# Source: http://stackoverflow.com/a/1678636
FULL_PATH=`python -c "import os, sys; print os.path.realpath('$HACKING_DIR')"`
ANSIBLE_HOME=`dirname $FULL_PATH`
PREFIX_PYTHONPATH="$ANSIBLE_HOME/lib"
PREFIX_PATH="$ANSIBLE_HOME/bin"
PREFIX_MANPATH="$ANSIBLE_HOME/docs/man"
echo "Prefixing PYTHONPATH with $PREFIX_PYTHONPATH"
export PYTHONPATH=$PREFIX_PYTHONPATH:$PYTHONPATH
echo "Prefixing PATH with $PREFIX_PATH"
export PATH=$PREFIX_PATH:$PATH
echo $PATH
echo "Prefixing MANPATH with $PREFIX_MANPATH"
export ANSIBLE_LIBRARY="$ANSIBLE_HOME/library"
export MANPATH=$PREFIX_MANPATH:$MANPATH
echo "Setting ANSIBLE_LIBRARY to ${PREFIX_ANS_LIBRARY}"
export ANSIBLE_LIBRARY=${PREFIX_ANS_LIBRARY}
# Print out values unless -q is set
if [ $# -eq 0 -o "$1" != "-q" ] ; then
echo "PATH=$PATH"
echo "PYTHONPATH=$PYTHONPATH"
echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY"
echo "MANPATH=$MANPATH"
echo "Reminder: specify your host file with -i"
echo "Done."
fi

151
hacking/test-module Executable file
View File

@ -0,0 +1,151 @@
#!/usr/bin/python
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# this script is for testing modules without running through the
# entire guts of ansible, and is very helpful for when developing
# modules
#
# example:
# test-module -m ../library/command -a "/bin/sleep 3"
# test-module -m ../library/service -a "name=httpd ensure=restarted"
# test-module -m ../library/service -a "name=httpd ensure=restarted" --debugger /usr/bin/pdb
import sys
import base64
import os
import subprocess
import traceback
import optparse
import ansible.utils as utils
import ansible.module_common as module_common
try:
import json
except ImportError:
import simplejson as json
def parse():
"""parse command line
:return : (options, args)"""
parser = optparse.OptionParser()
parser.usage = "%prog -[options] (-h for help)"
parser.add_option('-m', '--module-path', dest='module_path',
help="REQUIRED: full path of module source to execute")
parser.add_option('-a', '--args', dest='module_args', default="",
help="module argument string")
parser.add_option('-D', '--debugger', dest='debugger',
help="path to python debugger (e.g. /usr/bin/pdb)")
options, args = parser.parse_args()
if not options.module_path:
parser.print_help()
sys.exit(1)
else:
return options, args
def write_argsfile(argstring):
""" Write args to a file for old-style module's use. """
argspath = os.path.expanduser("~/.ansible_test_module_arguments")
argsfile = open(argspath, 'w')
argsfile.write(argstring)
argsfile.close()
return argspath
def boilerplate_module(modfile, args):
""" simulate what ansible does with new style modules """
module_fh = open(modfile)
module_data = module_fh.read()
included_boilerplate = module_data.find(module_common.REPLACER) != -1
module_fh.close()
if included_boilerplate:
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
encoded_args = "\"\"\"%s\"\"\"" % args.replace("\"","\\\"")
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
modfile2_path = os.path.expanduser("~/.ansible_module_generated")
print "* including generated source, if any, saving to: %s" % modfile2_path
print "* this will offset any line numbers in tracebacks/debuggers!"
modfile2 = open(modfile2_path, 'w')
modfile2.write(module_data)
modfile2.close()
modfile = modfile2_path
return (modfile2_path, included_boilerplate)
else:
print "* module boilerplate substitution not requested in module, line numbers will be unaltered"
return (modfile, included_boilerplate)
def runtest( modfile, argspath):
"""Test run a module, piping it's output for reporting."""
os.system("chmod +x %s" % modfile)
invoke = "%s" % (modfile)
if argspath is not None:
invoke = "%s %s" % (modfile, argspath)
cmd = subprocess.Popen(invoke, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
try:
print "***********************************"
print "RAW OUTPUT"
print out
print err
results = utils.parse_json(out)
except:
print "***********************************"
print "INVALID OUTPUT FORMAT"
print out
traceback.print_exc()
sys.exit(1)
print "***********************************"
print "PARSED OUTPUT"
print utils.jsonify(results,format=True)
def rundebug(debugger, modfile, argspath):
"""Run interactively with console debugger."""
if argspath is not None:
subprocess.call("%s %s %s" % (debugger, modfile, argspath), shell=True)
else:
subprocess.call("%s %s" % (debugger, modfile), shell=True)
def main():
options, args = parse()
(modfile, is_new_style) = boilerplate_module(options.module_path, options.module_args)
argspath=None
if not is_new_style:
argspath = write_argsfile(options.module_args)
if options.debugger:
rundebug(options.debugger, modfile, argspath)
else:
runtest(modfile, argspath)
if __name__ == "__main__":
main()

2
lib/ansible/__init__.py Executable file → Normal file
View File

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

398
lib/ansible/callbacks.py Normal file
View File

@ -0,0 +1,398 @@
# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import utils
import sys
import getpass
import os
import subprocess
from ansible.color import stringc
cowsay = None
if os.path.exists("/usr/bin/cowsay"):
cowsay = "/usr/bin/cowsay"
elif os.path.exists("/usr/games/cowsay"):
cowsay = "/usr/games/cowsay"
class AggregateStats(object):
''' holds stats about per-host activity during playbook runs '''
def __init__(self):
self.processed = {}
self.failures = {}
self.ok = {}
self.dark = {}
self.changed = {}
self.skipped = {}
def _increment(self, what, host):
''' helper function to bump a statistic '''
self.processed[host] = 1
prev = (getattr(self, what)).get(host, 0)
getattr(self, what)[host] = prev+1
def compute(self, runner_results, setup=False, poll=False):
''' walk through all results and increment stats '''
for (host, value) in runner_results.get('contacted', {}).iteritems():
if ('failed' in value and bool(value['failed'])) or ('rc' in value and value['rc'] != 0):
self._increment('failures', host)
elif 'skipped' in value and bool(value['skipped']):
self._increment('skipped', host)
elif 'changed' in value and bool(value['changed']):
if not setup and not poll:
self._increment('changed', host)
self._increment('ok', host)
else:
if not poll or ('finished' in value and bool(value['finished'])):
self._increment('ok', host)
for (host, value) in runner_results.get('dark', {}).iteritems():
self._increment('dark', host)
def summarize(self, host):
''' return information about a particular host '''
return dict(
ok = self.ok.get(host, 0),
failures = self.failures.get(host, 0),
unreachable = self.dark.get(host,0),
changed = self.changed.get(host, 0),
skipped = self.skipped.get(host, 0)
)
########################################################################
def regular_generic_msg(hostname, result, oneline, caption):
''' output on the result of a module run that is not command '''
if not oneline:
return "%s | %s >> %s\n" % (hostname, caption, utils.jsonify(result,format=True))
else:
return "%s | %s >> %s\n" % (hostname, caption, utils.jsonify(result))
def banner(msg):
if cowsay != None:
cmd = subprocess.Popen("%s -W 60 \"%s\"" % (cowsay, msg),
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
(out, err) = cmd.communicate()
return "%s\n" % out
else:
return "\n%s ********************* " % msg
def command_generic_msg(hostname, result, oneline, caption):
''' output the result of a command run '''
rc = result.get('rc', '0')
stdout = result.get('stdout','')
stderr = result.get('stderr', '')
msg = result.get('msg', '')
if not oneline:
buf = "%s | %s | rc=%s >>\n" % (hostname, caption, result.get('rc',0))
if stdout:
buf += stdout
if stderr:
buf += stderr
if msg:
buf += msg
return buf + "\n"
else:
if stderr:
return "%s | %s | rc=%s | (stdout) %s (stderr) %s\n" % (hostname, caption, rc, stdout, stderr)
else:
return "%s | %s | rc=%s | (stdout) %s\n" % (hostname, caption, rc, stdout)
def host_report_msg(hostname, module_name, result, oneline):
''' summarize the JSON results for a particular host '''
failed = utils.is_failed(result)
msg = ''
if module_name in [ 'command', 'shell', 'raw' ] and 'ansible_job_id' not in result:
if not failed:
msg = command_generic_msg(hostname, result, oneline, 'success')
else:
msg = command_generic_msg(hostname, result, oneline, 'FAILED')
else:
if not failed:
msg = regular_generic_msg(hostname, result, oneline, 'success')
else:
msg = regular_generic_msg(hostname, result, oneline, 'FAILED')
return msg
###############################################
class DefaultRunnerCallbacks(object):
''' no-op callbacks for API usage of Runner() if no callbacks are specified '''
def __init__(self):
pass
def on_failed(self, host, res, ignore_errors=False):
pass
def on_ok(self, host, res):
pass
def on_error(self, host, msg):
pass
def on_skipped(self, host):
pass
def on_unreachable(self, host, res):
pass
def on_no_hosts(self):
pass
def on_async_poll(self, host, res, jid, clock):
pass
def on_async_ok(self, host, res, jid):
pass
def on_async_failed(self, host, res, jid):
pass
########################################################################
class CliRunnerCallbacks(DefaultRunnerCallbacks):
''' callbacks for use by /usr/bin/ansible '''
def __init__(self):
# set by /usr/bin/ansible later
self.options = None
self._async_notified = {}
def on_failed(self, host, res, ignore_errors=False):
self._on_any(host,res)
def on_ok(self, host, res):
self._on_any(host,res)
def on_unreachable(self, host, res):
if type(res) == dict:
res = res.get('msg','')
print "%s | FAILED => %s" % (host, res)
if self.options.tree:
utils.write_tree_file(
self.options.tree, host,
utils.jsonify(dict(failed=True, msg=res),format=True)
)
def on_skipped(self, host):
pass
def on_error(self, host, err):
print >>sys.stderr, "err: [%s] => %s\n" % (host, err)
def on_no_hosts(self):
print >>sys.stderr, "no hosts matched\n"
def on_async_poll(self, host, res, jid, clock):
if jid not in self._async_notified:
self._async_notified[jid] = clock + 1
if self._async_notified[jid] > clock:
self._async_notified[jid] = clock
print "<job %s> polling, %ss remaining"%(jid, clock)
def on_async_ok(self, host, res, jid):
print "<job %s> finished on %s => %s"%(jid, host, utils.jsonify(res,format=True))
def on_async_failed(self, host, res, jid):
print "<job %s> FAILED on %s => %s"%(jid, host, utils.jsonify(res,format=True))
def _on_any(self, host, result):
print host_report_msg(host, self.options.module_name, result, self.options.one_line)
if self.options.tree:
utils.write_tree_file(self.options.tree, host, utils.jsonify(result,format=True))
########################################################################
class PlaybookRunnerCallbacks(DefaultRunnerCallbacks):
''' callbacks used for Runner() from /usr/bin/ansible-playbook '''
def __init__(self, stats, verbose=False):
self.stats = stats
self._async_notified = {}
self.verbose = verbose
def on_unreachable(self, host, msg):
item = None
if type(msg) == dict:
item = msg.get('item', None)
if item:
print "fatal: [%s] => (item=%s) => %s" % (host, item, msg)
else:
print "fatal: [%s] => %s" % (host, msg)
def on_failed(self, host, results, ignore_errors=False):
item = results.get('item', None)
if item:
msg = "failed: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(results))
else:
msg = "failed: [%s] => %s" % (host, utils.jsonify(results))
print stringc(msg, 'red')
if ignore_errors:
print stringc("...ignoring", 'yellow')
def on_ok(self, host, host_result):
item = host_result.get('item', None)
# show verbose output for non-setup module results if --verbose is used
msg = ''
if not self.verbose or host_result.get("verbose_override",None) is not None:
if item:
msg = "ok: [%s] => (item=%s)" % (host,item)
else:
if 'ansible_job_id' not in host_result or 'finished' in host_result:
msg = "ok: [%s]" % (host)
else:
# verbose ...
if item:
msg = "ok: [%s] => (item=%s) => %s" % (host, item, utils.jsonify(host_result))
else:
if 'ansible_job_id' not in host_result or 'finished' in host_result:
msg = "ok: [%s] => %s" % (host, utils.jsonify(host_result))
if msg != '':
if not 'changed' in host_result or not host_result['changed']:
print stringc(msg, 'green')
else:
print stringc(msg, 'yellow')
def on_error(self, host, err):
item = err.get('item', None)
msg = ''
if item:
msg = "err: [%s] => (item=%s) => %s" % (host, item, err)
else:
msg = "err: [%s] => %s" % (host, err)
msg = stringc(msg, 'red')
print >>sys.stderr, msg
def on_skipped(self, host, item=None):
msg = ''
if item:
msg = "skipping: [%s] => (item=%s)" % (host, item)
else:
msg = "skipping: [%s]" % host
print stringc(msg, 'yellow')
def on_no_hosts(self):
print stringc("no hosts matched or remaining\n", 'red')
def on_async_poll(self, host, res, jid, clock):
if jid not in self._async_notified:
self._async_notified[jid] = clock + 1
if self._async_notified[jid] > clock:
self._async_notified[jid] = clock
msg = "<job %s> polling, %ss remaining"%(jid, clock)
print stringc(msg, 'cyan')
def on_async_ok(self, host, res, jid):
msg = "<job %s> finished on %s"%(jid, host)
print stringc(msg, 'cyan')
def on_async_failed(self, host, res, jid):
msg = "<job %s> FAILED on %s"%(jid, host)
print stringc(msg, 'red')
########################################################################
class PlaybookCallbacks(object):
''' playbook.py callbacks used by /usr/bin/ansible-playbook '''
def __init__(self, verbose=False):
self.verbose = verbose
def on_start(self):
pass
def on_notify(self, host, handler):
pass
def on_task_start(self, name, is_conditional):
msg = "TASK: [%s]" % name
if is_conditional:
msg = "NOTIFIED: [%s]" % name
print banner(msg)
def on_vars_prompt(self, varname, private=True, prompt=None):
if prompt:
msg = prompt
else:
msg = 'input for %s: ' % varname
if private:
return getpass.getpass(msg)
return raw_input(msg)
def on_setup(self):
print banner("GATHERING FACTS")
def on_import_for_host(self, host, imported_file):
msg = "%s: importing %s" % (host, imported_file)
print stringc(msg, 'cyan')
def on_not_import_for_host(self, host, missing_file):
msg = "%s: not importing file: %s" % (host, missing_file)
print stringc(msg, 'cyan')
def on_play_start(self, pattern):
print banner("PLAY [%s]" % pattern)

59
lib/ansible/color.py Normal file
View File

@ -0,0 +1,59 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
ANSIBLE_COLOR=True
if os.getenv("ANSIBLE_NOCOLOR") is not None:
ANSIBLE_COLOR=False
if not sys.stdout.isatty():
ANSIBLE_COLOR=False
# --- begin "pretty"
#
# pretty - A miniature library that provides a Python print and stdout
# wrapper that makes colored terminal text easier to use (eg. without
# having to mess around with ANSI escape sequences). This code is public
# domain - there is no license except that you must leave this header.
#
# Copyright (C) 2008 Brian Nez <thedude at bri1 dot com>
#
# http://nezzen.net/2008/06/23/colored-text-in-python-using-ansi-escape-sequences/
codeCodes = {
'black': '0;30', 'bright gray': '0;37',
'blue': '0;34', 'white': '1;37',
'green': '0;32', 'bright blue': '1;34',
'cyan': '0;36', 'bright green': '1;32',
'red': '0;31', 'bright cyan': '1;36',
'purple': '0;35', 'bright red': '1;31',
'yellow': '0;33', 'bright purple': '1;35',
'dark gray': '1;30', 'bright yellow': '1;33',
'normal': '0'
}
def stringc(text, color):
"""String in color."""
if ANSIBLE_COLOR:
return "\033["+codeCodes[color]+"m"+text+"\033[0m"
else:
return text
# --- end "pretty"

View File

@ -14,20 +14,25 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import os
DEFAULT_HOST_LIST = os.environ.get('ANSIBLE_HOSTS', '/etc/ansible/hosts')
DEFAULT_MODULE_PATH = os.environ.get('ANSIBLE_LIBRARY', '/usr/share/ansible')
DEFAULT_REMOTE_TMP = os.environ.get('ANSIBLE_REMOTE_TMP', '$HOME/.ansible/tmp')
# control side (aka 'overlord')
DEFAULT_HOST_LIST = os.environ.get('ANSIBLE_HOSTS',
'/etc/ansible/hosts')
DEFAULT_MODULE_PATH = os.environ.get('ANSIBLE_LIBRARY',
'/usr/share/ansible')
DEFAULT_MODULE_NAME = 'command'
DEFAULT_PATTERN = '*'
DEFAULT_FORKS = 5
DEFAULT_MODULE_ARGS = ''
DEFAULT_TIMEOUT = 10
DEFAULT_REMOTE_USER = 'root'
DEFAULT_FORKS = os.environ.get('ANSIBLE_FORKS',5)
DEFAULT_MODULE_ARGS = os.environ.get('ANSIBLE_MODULE_ARGS','')
DEFAULT_TIMEOUT = os.environ.get('ANSIBLE_TIMEOUT',10)
DEFAULT_POLL_INTERVAL = os.environ.get('ANSIBLE_POLL_INTERVAL',15)
DEFAULT_REMOTE_USER = os.environ.get('ANSIBLE_REMOTE_USER','root')
DEFAULT_REMOTE_PASS = None
DEFAULT_PRIVATE_KEY_FILE = os.environ.get('ANSIBLE_PRIVATE_KEY_FILE',None)
DEFAULT_SUDO_PASS = None
DEFAULT_SUDO_USER = os.environ.get('ANSIBLE_SUDO_USER','root')
DEFAULT_REMOTE_PORT = 22
DEFAULT_TRANSPORT = os.environ.get('ANSIBLE_TRANSPORT','paramiko')
DEFAULT_TRANSPORT_OPTS = ['local', 'paramiko', 'ssh']

32
lib/ansible/errors.py Normal file
View File

@ -0,0 +1,32 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
class AnsibleError(Exception):
''' The base Ansible exception from which all others should subclass '''
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class AnsibleFileNotFound(AnsibleError):
pass
class AnsibleConnectionFailed(AnsibleError):
pass

View File

@ -0,0 +1,213 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
import fnmatch
import os
import subprocess
import ansible.constants as C
from ansible.inventory.ini import InventoryParser
from ansible.inventory.yaml import InventoryParserYaml
from ansible.inventory.script import InventoryScript
from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible import errors
from ansible import utils
class Inventory(object):
"""
Host inventory for ansible.
"""
__slots__ = [ 'host_list', 'groups', '_restriction', '_is_script',
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache' ]
def __init__(self, host_list=C.DEFAULT_HOST_LIST):
# the host file file, or script path, or list of hosts
# if a list, inventory data will NOT be loaded
self.host_list = host_list
# caching to avoid repeated calculations, particularly with
# external inventory scripts.
self._vars_per_host = {}
self._vars_per_group = {}
self._hosts_cache = {}
# the inventory object holds a list of groups
self.groups = []
# a list of host(names) to contain current inquiries to
self._restriction = None
# whether the inventory file is a script
self._is_script = False
if type(host_list) in [ str, unicode ]:
if host_list.find(",") != -1:
host_list = host_list.split(",")
host_list = [ h for h in host_list if h and h.strip() ]
if type(host_list) == list:
all = Group('all')
self.groups = [ all ]
for x in host_list:
if x.find(":") != -1:
tokens = x.split(":",1)
all.add_host(Host(tokens[0], tokens[1]))
else:
all.add_host(Host(x))
elif os.access(host_list, os.X_OK):
self._is_script = True
self.parser = InventoryScript(filename=host_list)
self.groups = self.parser.groups.values()
else:
data = file(host_list).read()
if not data.startswith("---"):
self.parser = InventoryParser(filename=host_list)
self.groups = self.parser.groups.values()
else:
self.parser = InventoryParserYaml(filename=host_list)
self.groups = self.parser.groups.values()
def _match(self, str, pattern_str):
return fnmatch.fnmatch(str, pattern_str)
# TODO: cache this logic so if called a second time the result is not recalculated
def get_hosts(self, pattern="all"):
""" Get all host objects matching the pattern """
hosts = {}
patterns = pattern.replace(";",":").split(":")
groups = self.get_groups()
for pat in patterns:
if pat.startswith("!"):
pat = pat[1:]
inverted = True
else:
inverted = False
for group in groups:
for host in group.get_hosts():
if self._match(group.name, pat) or pat == 'all' or self._match(host.name, pat):
# must test explicitly for None because [] means no hosts allowed
if self._restriction==None or host.name in self._restriction:
if inverted:
if host.name in hosts:
del hosts[host.name]
else:
hosts[host.name] = host
return sorted(hosts.values(), key=lambda x: x.name)
def get_groups(self):
return self.groups
def get_host(self, hostname):
if hostname not in self._hosts_cache:
self._hosts_cache[hostname] = self._get_host(hostname)
return self._hosts_cache[hostname]
def _get_host(self, hostname):
for group in self.groups:
for host in group.get_hosts():
if hostname == host.name:
return host
return None
def get_group(self, groupname):
for group in self.groups:
if group.name == groupname:
return group
return None
def get_group_variables(self, groupname):
if groupname not in self._vars_per_group:
self._vars_per_group[groupname] = self._get_group_variables(groupname)
return self._vars_per_group[groupname]
def _get_group_variables(self, groupname):
group = self.get_group(groupname)
if group is None:
raise Exception("group not found: %s" % groupname)
return group.get_variables()
def get_variables(self, hostname):
if hostname not in self._vars_per_host:
self._vars_per_host[hostname] = self._get_variables(hostname)
return self._vars_per_host[hostname]
def _get_variables(self, hostname):
if self._is_script:
host = self.get_host(hostname)
cmd = subprocess.Popen(
[self.host_list,"--host",hostname],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
(out, err) = cmd.communicate()
results = utils.parse_json(out)
# FIXME: this is a bit redundant with host.py and should share code
results['inventory_hostname'] = hostname
results['inventory_hostname_short'] = hostname.split('.')[0]
groups = [ g.name for g in host.get_groups() if g.name != 'all' ]
results['group_names'] = sorted(groups)
return results
host = self.get_host(hostname)
if host is None:
raise Exception("host not found: %s" % hostname)
return host.get_variables()
def add_group(self, group):
self.groups.append(group)
def list_hosts(self, pattern="all"):
return [ h.name for h in self.get_hosts(pattern) ]
def list_groups(self):
return [ g.name for g in self.groups ]
def get_restriction(self):
return self._restriction
def restrict_to(self, restriction, append_missing=False):
""" Restrict list operations to the hosts given in restriction """
if type(restriction) != list:
restriction = [ restriction ]
self._restriction = restriction
def lift_restriction(self):
""" Do not restrict list operations """
self._restriction = None
def is_file(self):
""" did inventory come from a file? """
if not isinstance(self.host_list, basestring):
return False
return os.path.exists(self.host_list)
def basedir(self):
""" if inventory came from a file, what's the directory? """
if not self.is_file():
return None
return os.path.dirname(self.host_list)

View File

@ -0,0 +1,97 @@
# (c) 2012, Zettar Inc.
# Written by Chin Fang <fangchin@zettar.com>
#
# This file is part of Ansible
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
#
'''
This module is for enhancing ansible's inventory parsing capability such
that it can deal with hostnames specified using a simple pattern in the
form of [beg:end], example: [1:5] where if beg is not specified, it
defaults to 0.
If beg is given and is left-zero-padded, e.g. '001', it is taken as a
formatting hint when the range is expanded. e.g. [001:010] is to be
expanded into 001, 002 ...009, 010.
Note that when beg is specified with left zero padding, then the length of
end must be the same as that of beg, else a exception is raised.
'''
from ansible import errors
def detect_range(line = None):
'''
A helper function that checks a given host line to see if it contains
a range pattern descibed in the docstring above.
Returnes True if the given line contains a pattern, else False.
'''
if (not line.startswith("[") and
line.find("[") != -1 and
line.find(":") != -1 and
line.find("]") != -1 and
line.index("[") < line.index(":") < line.index("]")):
return True
else:
return False
def expand_hostname_range(line = None):
'''
A helper function that expands a given line that contains a pattern
specified in top docstring, and returns a list that consists of the
expanded version.
The '[' and ']' characters are used to maintain the pseudo-code
appearance. They are replaced in this function with '|' to ease
string splitting.
References: http://ansible.github.com/patterns.html#hosts-and-groups
'''
all_hosts = []
if line:
# A hostname such as db[1:6]-node is considered to consists
# three parts:
# head: 'db'
# nrange: [1:6]; range() is a built-in. Can't use the name
# tail: '-node'
(head, nrange, tail) = line.replace('[','|').replace(']','|').split('|')
bounds = nrange.split(":")
if len(bounds) != 2:
raise errors.AnsibleError("host range incorrectly specified")
beg = bounds[0]
end = bounds[1]
if not beg:
beg = "0"
if not end:
raise errors.AnsibleError("host range end value missing")
if beg[0] == '0' and len(beg) > 1:
rlen = len(beg) # range length formatting hint
else:
rlen = None
if rlen > 1 and rlen != len(end):
raise errors.AnsibleError("host range format incorrectly specified!")
for _ in range(int(beg), int(end)+1):
if rlen:
rseq = str(_).zfill(rlen) # range sequence
else:
rseq = str(_)
hname = ''.join((head, rseq, tail))
all_hosts.append(hname)
return all_hosts

View File

@ -0,0 +1,77 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
class Group(object):
''' a group of ansible hosts '''
__slots__ = [ 'name', 'hosts', 'vars', 'child_groups', 'parent_groups' ]
def __init__(self, name=None):
self.name = name
self.hosts = []
self.vars = {}
self.child_groups = []
self.parent_groups = []
if self.name is None:
raise Exception("group name is required")
def add_child_group(self, group):
if self == group:
raise Exception("can't add group to itself")
self.child_groups.append(group)
group.parent_groups.append(self)
def add_host(self, host):
self.hosts.append(host)
host.add_group(self)
def set_variable(self, key, value):
self.vars[key] = value
def get_hosts(self):
hosts = []
for kid in self.child_groups:
hosts.extend(kid.get_hosts())
hosts.extend(self.hosts)
return hosts
def get_variables(self):
vars = {}
# FIXME: verify this variable override order is what we want
for ancestor in self.get_ancestors():
vars.update(ancestor.get_variables())
vars.update(self.vars)
return vars
def _get_ancestors(self):
results = {}
for g in self.parent_groups:
results[g.name] = g
results.update(g._get_ancestors())
return results
def get_ancestors(self):
return self._get_ancestors().values()

View File

@ -0,0 +1,66 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible import errors
import ansible.constants as C
class Host(object):
''' a single ansible host '''
__slots__ = [ 'name', 'vars', 'groups' ]
def __init__(self, name=None, port=None):
self.name = name
self.vars = {}
self.groups = []
if port and port != C.DEFAULT_REMOTE_PORT:
self.set_variable('ansible_ssh_port', int(port))
if self.name is None:
raise Exception("host name is required")
def add_group(self, group):
self.groups.append(group)
def set_variable(self, key, value):
self.vars[key]=value
def get_groups(self):
groups = {}
for g in self.groups:
groups[g.name] = g
ancestors = g.get_ancestors()
for a in ancestors:
groups[a.name] = a
return groups.values()
def get_variables(self):
results = {}
for group in self.groups:
results.update(group.get_variables())
results.update(self.vars)
results['inventory_hostname'] = self.name
results['inventory_hostname_short'] = self.name.split('.')[0]
groups = self.get_groups()
results['group_names'] = sorted([ g.name for g in groups if g.name != 'all'])
return results

View File

@ -0,0 +1,174 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
import fnmatch
import os
import subprocess
import ansible.constants as C
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible.inventory.expand_hosts import detect_range
from ansible.inventory.expand_hosts import expand_hostname_range
from ansible import errors
from ansible import utils
class InventoryParser(object):
"""
Host inventory for ansible.
"""
def __init__(self, filename=C.DEFAULT_HOST_LIST):
fh = open(filename)
self.lines = fh.readlines()
self.groups = {}
self.hosts = {}
self._parse()
def _parse(self):
self._parse_base_groups()
self._parse_group_children()
self._parse_group_variables()
return self.groups
# [webservers]
# alpha
# beta:2345
# gamma sudo=True user=root
# delta asdf=jkl favcolor=red
def _parse_base_groups(self):
# FIXME: refactor
ungrouped = Group(name='ungrouped')
all = Group(name='all')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
active_group_name = 'ungrouped'
for line in self.lines:
if line.startswith("["):
active_group_name = line.replace("[","").replace("]","").strip()
if line.find(":vars") != -1 or line.find(":children") != -1:
active_group_name = None
else:
new_group = self.groups[active_group_name] = Group(name=active_group_name)
all.add_child_group(new_group)
elif line.startswith("#") or line == '':
pass
elif active_group_name:
tokens = line.split()
if len(tokens) == 0:
continue
hostname = tokens[0]
port = C.DEFAULT_REMOTE_PORT
# Two cases to check:
# 0. A hostname that contains a range pesudo-code and a port
# 1. A hostname that contains just a port
if (hostname.find("[") != -1 and
hostname.find("]") != -1 and
hostname.find(":") != -1 and
(hostname.rindex("]") < hostname.rindex(":")) or
(hostname.find("]") == -1 and hostname.find(":") != -1)):
tokens2 = hostname.rsplit(":", 1)
hostname = tokens2[0]
port = tokens2[1]
host = None
_all_hosts = []
if hostname in self.hosts:
host = self.hosts[hostname]
_all_hosts.append(host)
else:
if detect_range(hostname):
_hosts = expand_hostname_range(hostname)
for _ in _hosts:
host = Host(name=_, port=port)
self.hosts[_] = host
_all_hosts.append(host)
else:
host = Host(name=hostname, port=port)
self.hosts[hostname] = host
_all_hosts.append(host)
if len(tokens) > 1:
for t in tokens[1:]:
(k,v) = t.split("=")
host.set_variable(k,v)
for _ in _all_hosts:
self.groups[active_group_name].add_host(_)
# [southeast:children]
# atlanta
# raleigh
def _parse_group_children(self):
group = None
for line in self.lines:
line = line.strip()
if line is None or line == '':
continue
if line.startswith("[") and line.find(":children]") != -1:
line = line.replace("[","").replace(":children]","")
group = self.groups.get(line, None)
if group is None:
group = self.groups[line] = Group(name=line)
elif line.startswith("#"):
pass
elif line.startswith("["):
group = None
elif group:
kid_group = self.groups.get(line, None)
if kid_group is None:
raise errors.AnsibleError("child group is not defined: (%s)" % line)
else:
group.add_child_group(kid_group)
# [webservers:vars]
# http_port=1234
# maxRequestsPerChild=200
def _parse_group_variables(self):
group = None
for line in self.lines:
line = line.strip()
if line.startswith("[") and line.find(":vars]") != -1:
line = line.replace("[","").replace(":vars]","")
group = self.groups.get(line, None)
if group is None:
raise errors.AnsibleError("can't add vars to undefined group: %s" % line)
elif line.startswith("#"):
pass
elif line.startswith("["):
group = None
elif line == '':
pass
elif group:
if line.find("=") == -1:
raise errors.AnsibleError("variables assigned to group must be in key=value form")
else:
(k,v) = line.split("=",1)
group.set_variable(k,v)

View File

@ -0,0 +1,56 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
import os
import subprocess
import ansible.constants as C
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible import errors
from ansible import utils
class InventoryScript(object):
''' Host inventory parser for ansible using external inventory scripts. '''
def __init__(self, filename=C.DEFAULT_HOST_LIST):
cmd = [ filename, "--list" ]
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = sp.communicate()
self.data = stdout
self.groups = self._parse()
def _parse(self):
groups = {}
self.raw = utils.parse_json(self.data)
all=Group('all')
self.groups = dict(all=all)
group = None
for (group_name, hosts) in self.raw.items():
group = groups[group_name] = Group(group_name)
host = None
for hostname in hosts:
host = Host(hostname)
group.add_host(host)
# FIXME: hack shouldn't be needed
all.add_host(host)
all.add_child_group(group)
return groups

View File

@ -0,0 +1,142 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import ansible.constants as C
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible import errors
from ansible import utils
import sys
class InventoryParserYaml(object):
''' Host inventory parser for ansible '''
__slots__ = [ '_hosts', 'groups' ]
def __init__(self, filename=C.DEFAULT_HOST_LIST):
sys.stderr.write("WARNING: YAML inventory files are deprecated in 0.6 and will be removed in 0.7, to migrate" +
" download and run https://github.com/ansible/ansible/blob/devel/examples/scripts/yaml_to_ini.py\n")
fh = open(filename)
data = fh.read()
fh.close()
self._hosts = {}
self._parse(data)
def _make_host(self, hostname):
if hostname in self._hosts:
return self._hosts[hostname]
else:
host = Host(hostname)
self._hosts[hostname] = host
return host
# see file 'test/yaml_hosts' for syntax
def _parse(self, data):
# FIXME: refactor into subfunctions
all = Group('all')
ungrouped = Group('ungrouped')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
grouped_hosts = []
yaml = utils.parse_yaml(data)
# first add all groups
for item in yaml:
if type(item) == dict and 'group' in item:
group = Group(item['group'])
for subresult in item.get('hosts',[]):
if type(subresult) in [ str, unicode ]:
host = self._make_host(subresult)
group.add_host(host)
grouped_hosts.append(host)
elif type(subresult) == dict:
host = self._make_host(subresult['host'])
vars = subresult.get('vars',{})
if type(vars) == list:
for subitem in vars:
for (k,v) in subitem.items():
host.set_variable(k,v)
elif type(vars) == dict:
for (k,v) in subresult.get('vars',{}).items():
host.set_variable(k,v)
else:
raise errors.AnsibleError("unexpected type for variable")
group.add_host(host)
grouped_hosts.append(host)
vars = item.get('vars',{})
if type(vars) == dict:
for (k,v) in item.get('vars',{}).items():
group.set_variable(k,v)
elif type(vars) == list:
for subitem in vars:
if type(subitem) != dict:
raise errors.AnsibleError("expected a dictionary")
for (k,v) in subitem.items():
group.set_variable(k,v)
self.groups[group.name] = group
all.add_child_group(group)
# add host definitions
for item in yaml:
if type(item) in [ str, unicode ]:
host = self._make_host(item)
if host not in grouped_hosts:
ungrouped.add_host(host)
elif type(item) == dict and 'host' in item:
host = self._make_host(item['host'])
vars = item.get('vars', {})
if type(vars)==list:
varlist, vars = vars, {}
for subitem in varlist:
vars.update(subitem)
for (k,v) in vars.items():
host.set_variable(k,v)
groups = item.get('groups', {})
if type(groups) in [ str, unicode ]:
groups = [ groups ]
if type(groups)==list:
for subitem in groups:
if subitem in self.groups:
group = self.groups[subitem]
else:
group = Group(subitem)
self.groups[group.name] = group
all.add_child_group(group)
group.add_host(host)
grouped_hosts.append(host)
if host not in grouped_hosts:
ungrouped.add_host(host)
# make sure ungrouped.hosts is the complement of grouped_hosts
ungrouped_hosts = [host for host in ungrouped.hosts if host not in grouped_hosts]

View File

@ -0,0 +1,211 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
REPLACER = "#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
REPLACER_ARGS = "<<INCLUDE_ANSIBLE_MODULE_ARGS>>"
MODULE_COMMON = """
# == BEGIN DYNAMICALLY INSERTED CODE ==
MODULE_ARGS = <<INCLUDE_ANSIBLE_MODULE_ARGS>>
BOOLEANS_TRUE = ['yes', 'on', '1', 'true', 1]
BOOLEANS_FALSE = ['no', 'off', '0', 'false', 0]
BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE
# ansible modules can be written in any language. To simplify
# development of Python modules, the functions available here
# can be inserted in any module source automatically by including
# #<<INCLUDE_ANSIBLE_MODULE_COMMON>> on a blank line by itself inside
# of an ansible module. The source of this common code lives
# in lib/ansible/module_common.py
try:
import json
except ImportError:
import simplejson as json
import base64
import os
import re
import shlex
import subprocess
import sys
import syslog
import types
try:
from hashlib import md5 as _md5
except ImportError:
from md5 import md5 as _md5
class AnsibleModule(object):
def __init__(self, argument_spec, bypass_checks=False, no_log=False, check_invalid_arguments=True):
'''
common code for quickly building an ansible module in Python
(although you can write modules in anything that can return JSON)
see library/slurp and others for examples
'''
self.argument_spec = argument_spec
(self.params, self.args) = self._load_params()
self._legal_inputs = []
self._handle_aliases()
# this may be disabled where modules are going to daisy chain into others
if check_invalid_arguments:
self._check_invalid_arguments()
self._set_defaults(pre=True)
if not bypass_checks:
self._check_required_arguments()
self._check_argument_types()
self._set_defaults(pre=False)
if not no_log:
self._log_invocation()
def _handle_aliases(self):
for (k,v) in self.argument_spec.iteritems():
self._legal_inputs.append(k)
aliases = v.get('aliases', None)
default = v.get('default', None)
required = v.get('required', False)
if default is not None and required:
# not alias specific but this is a good place to check this
self.fail_json(msg="internal error: required and default are mutally exclusive for %s" % k)
if aliases is None:
continue
if type(aliases) != list:
self.fail_json(msg='internal error: aliases must be a list')
for alias in aliases:
self._legal_inputs.append(alias)
if alias in self.params:
self.params[k] = self.params[alias]
def _check_invalid_arguments(self):
for (k,v) in self.params.iteritems():
if k not in self._legal_inputs:
self.fail_json(msg="unsupported parameter for module: %s" % k)
def _check_required_arguments(self):
''' ensure all required arguments are present '''
missing = []
for (k,v) in self.argument_spec.iteritems():
required = v.get('required', False)
if required and k not in self.params:
missing.append(k)
if len(missing) > 0:
self.fail_json(msg="missing required arguments: %s" % ",".join(missing))
def _check_argument_types(self):
''' ensure all arguments have the requested values, and there are no stray arguments '''
for (k,v) in self.argument_spec.iteritems():
choices = v.get('choices',None)
if choices is None:
continue
if type(choices) == list:
if k in self.params:
if self.params[k] not in choices:
choices_str=",".join(choices)
msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k])
self.fail_json(msg=msg)
else:
self.fail_json(msg="internal error: do not know how to interpret argument_spec")
def _set_defaults(self, pre=True):
for (k,v) in self.argument_spec.iteritems():
default = v.get('default', None)
if pre == True:
# this prevents setting defaults on required items
if default and k not in self.params:
self.params[k] = default
else:
# make sure things without a default still get set None
if k not in self.params:
self.params[k] = default
def _load_params(self):
''' read the input and return a dictionary and the arguments string '''
args = MODULE_ARGS
items = shlex.split(args)
params = {}
for x in items:
(k, v) = x.split("=",1)
params[k] = v
return (params, args)
def _log_invocation(self):
''' log that ansible ran the module '''
syslog.openlog('ansible-%s' % os.path.basename(__file__))
# Sanitize possible password argument when logging
log_args = re.sub(r'password=.+ (.*)', r"password=NOT_LOGGING_PASSWORD \1", self.args)
log_args = re.sub(r'login_password=.+ (.*)', r"login_password=NOT_LOGGING_PASSWORD \1", log_args)
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % log_args)
def boolean(self, arg):
''' return a bool for the arg '''
if arg is None or type(arg) == bool:
return arg
if type(arg) in types.StringTypes:
arg = arg.lower()
if arg in BOOLEANS_TRUE:
return True
elif arg in BOOLEANS_FALSE:
return False
else:
self.fail_json(msg='Boolean %s not in either boolean list' % arg)
def jsonify(self, data):
return json.dumps(data)
def exit_json(self, **kwargs):
''' return from the module, without error '''
print self.jsonify(kwargs)
sys.exit(0)
def fail_json(self, **kwargs):
''' return from the module, with an error message '''
assert 'msg' in kwargs, "implementation error -- msg to explain the error is required"
kwargs['failed'] = True
print self.jsonify(kwargs)
sys.exit(1)
def md5(self, filename):
''' Return MD5 hex digest of local file, or None if file is not present. '''
if not os.path.exists(filename):
return None
if os.path.isdir(filename):
self.fail_json(msg="attempted to take md5sum of directory: %s" % filename)
digest = _md5()
blocksize = 64 * 1024
infile = open(filename, 'rb')
block = infile.read(blocksize)
while block:
digest.update(block)
block = infile.read(blocksize)
infile.close()
return digest.hexdigest()
# == END DYNAMICALLY INSERTED CODE ===
"""

View File

@ -1,368 +0,0 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
import ansible.runner
import ansible.constants as C
from ansible.utils import *
import yaml
import shlex
import os
import jinja2
# used to transfer variables to Runner
SETUP_CACHE={ }
#############################################
class PlayBook(object):
'''
runs an ansible playbook, given as a datastructure
or YAML filename. a playbook is a deployment, config
management, or automation based set of commands to
run in series.
multiple patterns do not execute simultaneously,
but tasks in each pattern do execute in parallel
according to the number of forks requested.
'''
def __init__(self,
playbook =None,
host_list =C.DEFAULT_HOST_LIST,
module_path =C.DEFAULT_MODULE_PATH,
forks =C.DEFAULT_FORKS,
timeout =C.DEFAULT_TIMEOUT,
remote_user =C.DEFAULT_REMOTE_USER,
remote_pass =C.DEFAULT_REMOTE_PASS,
verbose=False,
callbacks=None):
# TODO, once ansible-playbook is it's own script this will
# have much LESS parameters to the constructor and will
# read most everything per pattern from the playbook
# and this will be greatly simplified
self.host_list = host_list
self.module_path = module_path
self.forks = forks
self.timeout = timeout
self.remote_user = remote_user
self.remote_pass = remote_pass
self.verbose = verbose
self.callbacks = callbacks
self.callbacks.set_playbook(self)
# store the list of changes/invocations/failure counts
# as a dictionary of integers keyed off the hostname
self.processed = {}
self.dark = {}
self.changed = {}
self.invocations = {}
self.failures = {}
# playbook file can be passed in as a path or
# as file contents (to support API usage)
self.basedir = os.path.dirname(playbook)
self.playbook = self._parse_playbook(playbook)
def _include_tasks(self, play, task, dirname, new_tasks):
# an include line looks like:
# include: some.yml a=2 b=3 c=4
include_tokens = task['include'].split()
path = path_dwim(dirname, include_tokens[0])
inject_vars = play.get('vars', {})
for i,x in enumerate(include_tokens):
if x.find("=") != -1:
(k,v) = x.split("=")
inject_vars[k] = v
included = file(path).read()
template = jinja2.Template(included)
included = template.render(inject_vars)
included = yaml.load(included)
for x in included:
new_tasks.append(x)
def _include_handlers(self, play, handler, dirname, new_handlers):
path = path_dwim(dirname, handler['include'])
included = file(path).read()
inject_vars = play.get('vars', {})
template = jinja2.Template(included)
included = template.render(inject_vars)
included = yaml.load(included)
for x in included:
new_handlers.append(x)
def _parse_playbook(self, playbook):
''' load YAML file, including handling for imported files '''
dirname = os.path.dirname(playbook)
playbook = yaml.load(file(playbook).read())
for play in playbook:
tasks = play.get('tasks',[])
handlers = play.get('handlers', [])
# process tasks in this file as well as imported tasks
new_tasks = []
for task in tasks:
if 'include' in task:
self._include_tasks(play, task, dirname, new_tasks)
else:
new_tasks.append(task)
play['tasks'] = new_tasks
# process handlers as well as imported handlers
new_handlers = []
for handler in handlers:
if 'include' in handler:
self._include_handlers(play, handler, dirname, new_handlers)
else:
new_handlers.append(handler)
play['handlers'] = new_handlers
return playbook
def run(self):
''' run all patterns in the playbook '''
# loop through all patterns and run them
self.callbacks.on_start()
for pattern in self.playbook:
self._run_pattern(pattern)
# summarize the results
results = {}
for host in self.processed.keys():
results[host] = {
'resources' : self.invocations.get(host, 0),
'changed' : self.changed.get(host, 0),
'dark' : self.dark.get(host, 0),
'failed' : self.failures.get(host, 0)
}
return results
def _prune_failed_hosts(self, host_list):
new_hosts = []
for x in host_list:
if not x in self.failures and not x in self.dark:
new_hosts.append(x)
return new_hosts
def _run_module(self, pattern, module, args, hosts, remote_user):
''' run a particular module step in a playbook '''
return ansible.runner.Runner(
pattern=pattern,
module_name=module,
module_args=args,
host_list=hosts,
forks=self.forks,
remote_pass=self.remote_pass,
module_path=self.module_path,
timeout=self.timeout,
remote_user=remote_user,
setup_cache=SETUP_CACHE,
basedir=self.basedir
).run()
def _run_task(self, pattern=None, task=None, host_list=None,
remote_user=None, handlers=None, conditional=False):
'''
run a single task in the playbook and
recursively run any subtasks.
'''
if host_list is None:
# pruned host lists occur when running triggered
# actions where not all hosts have changed
# though top-level tasks will pass in "None" here
host_list = self.host_list
(host_list, groups) = ansible.runner.Runner.parse_hosts(host_list)
# do not continue to run tasks on hosts that have had failures
host_list = self._prune_failed_hosts(host_list)
# load the module name and parameters from the task entry
name = task['name']
action = task['action']
# comment = task.get('comment', '')
tokens = shlex.split(action)
module_name = tokens[0]
module_args = tokens[1:]
# tasks can be direct (run on all nodes matching
# the pattern) or conditional, where they ran
# as the result of a change handler on a subset
# of all of the hosts
self.callbacks.on_task_start(name, conditional)
# load up an appropriate ansible runner to
# run the task in parallel
results = self._run_module(pattern, module_name,
module_args, host_list, remote_user)
# if no hosts are matched, carry on, unlike /bin/ansible
# which would warn you about this
if results is None:
results = {}
# walk through the results and build up
# summary information about successes and
# failures. TODO: split into subfunction
dark = results.get("dark", {})
contacted = results.get("contacted", {})
for host, msg in dark.items():
self.processed[host] = 1
self.callbacks.on_unreachable(host, msg)
if not host in self.dark:
self.dark[host] = 1
else:
self.dark[host] = self.dark[host] + 1
for host, results in contacted.items():
self.processed[host] = 1
if is_failed(results):
self.callbacks.on_failed(host, results)
if not host in self.failures:
self.failures[host] = 1
else:
self.failures[host] = self.failures[host] + 1
else:
self.callbacks.on_ok(host)
if not host in self.invocations:
self.invocations[host] = 1
else:
self.invocations[host] = self.invocations[host] + 1
if results.get('changed', False):
if not host in self.changed:
self.changed[host] = 1
else:
self.changed[host] = self.changed[host] + 1
# flag which notify handlers need to be run
# this will be on a SUBSET of the actual host list. For instance
# a file might need to be written on only half of the nodes so
# we would only trigger restarting Apache on half of the nodes
subtasks = task.get('notify', [])
if len(subtasks) > 0:
for host, results in contacted.items():
if results.get('changed', False):
for subtask in subtasks:
self._flag_handler(handlers, subtask, host)
def _flag_handler(self, handlers, match_name, host):
'''
if a task has any notify elements, flag handlers for run
at end of execution cycle for hosts that have indicated
changes have been made
'''
# for all registered handlers in the ansible playbook
# for this particular pattern group
for x in handlers:
name = x['name']
if match_name == name:
# flag the handler with the list of hosts
# it needs to be run on, it will be run later
if not 'run' in x:
x['run'] = []
x['run'].append(host)
def _run_pattern(self, pg):
'''
run a list of tasks for a given pattern, in order
'''
# get configuration information about the pattern
pattern = pg['hosts']
vars = pg.get('vars', {})
tasks = pg['tasks']
handlers = pg['handlers']
user = pg.get('user', C.DEFAULT_REMOTE_USER)
self.host_list, groups = ansible.runner.Runner.parse_hosts(self.host_list)
self.callbacks.on_play_start(pattern)
# first run the setup task on every node, which gets the variables
# written to the JSON file and will also bubble facts back up via
# magic in Runner()
push_var_str=''
for (k,v) in vars.items():
push_var_str += "%s=%s " % (k,v)
# push any variables down to the system
setup_results = ansible.runner.Runner(
pattern=pattern,
module_name='setup',
module_args=push_var_str,
host_list=self.host_list,
forks=self.forks,
module_path=self.module_path,
timeout=self.timeout,
remote_user=user,
setup_cache=SETUP_CACHE
).run()
# now for each result, load into the setup cache so we can
# let runner template out future commands
setup_ok = setup_results.get('contacted', {})
for (host, result) in setup_ok.items():
SETUP_CACHE[host] = result
# run all the top level tasks, these get run on every node
for task in tasks:
self._run_task(
pattern=pattern,
task=task,
handlers=handlers,
remote_user=user
)
# handlers only run on certain nodes, they are flagged by _flag_handlers
# above. They only run on nodes when things mark them as changed, and
# handlers only get run once. For instance, the system is designed
# such that multiple config files if changed can ask for an Apache restart
# but Apache will only be restarted once (at the end).
for task in handlers:
if type(task.get("run", None)) == list:
self._run_task(
pattern=pattern,
task=task,
handlers=handlers,
host_list=task.get('run',[]),
conditional=True,
remote_user=user
)
# end of execution for this particular pattern. Multiple patterns
# can be in a single playbook file

View File

@ -0,0 +1,324 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import ansible.inventory
import ansible.runner
import ansible.constants as C
from ansible import utils
from ansible import errors
import os
import collections
from play import Play
SETUP_CACHE = collections.defaultdict(dict)
class PlayBook(object):
'''
runs an ansible playbook, given as a datastructure or YAML filename.
A playbook is a deployment, config management, or automation based
set of commands to run in series.
multiple plays/tasks do not execute simultaneously, but tasks in each
pattern do execute in parallel (according to the number of forks
requested) among the hosts they address
'''
# *****************************************************
def __init__(self,
playbook = None,
host_list = C.DEFAULT_HOST_LIST,
module_path = C.DEFAULT_MODULE_PATH,
forks = C.DEFAULT_FORKS,
timeout = C.DEFAULT_TIMEOUT,
remote_user = C.DEFAULT_REMOTE_USER,
remote_pass = C.DEFAULT_REMOTE_PASS,
sudo_pass = C.DEFAULT_SUDO_PASS,
remote_port = C.DEFAULT_REMOTE_PORT,
transport = C.DEFAULT_TRANSPORT,
private_key_file = C.DEFAULT_PRIVATE_KEY_FILE,
verbose = False,
callbacks = None,
runner_callbacks = None,
stats = None,
sudo = False,
sudo_user = C.DEFAULT_SUDO_USER,
extra_vars = None,
only_tags = None):
"""
playbook: path to a playbook file
host_list: path to a file like /etc/ansible/hosts
module_path: path to ansible modules, like /usr/share/ansible/
forks: desired level of paralellism
timeout: connection timeout
remote_user: run as this user if not specified in a particular play
remote_pass: use this remote password (for all plays) vs using SSH keys
sudo_pass: if sudo==True, and a password is required, this is the sudo password
remote_port: default remote port to use if not specified with the host or play
transport: how to connect to hosts that don't specify a transport (local, paramiko, etc)
callbacks output callbacks for the playbook
runner_callbacks: more callbacks, this time for the runner API
stats: holds aggregrate data about events occuring to each host
sudo: if not specified per play, requests all plays use sudo mode
"""
self.SETUP_CACHE = SETUP_CACHE
if playbook is None or callbacks is None or runner_callbacks is None or stats is None:
raise Exception('missing required arguments')
if extra_vars is None:
extra_vars = {}
if only_tags is None:
only_tags = [ 'all' ]
self.module_path = module_path
self.forks = forks
self.timeout = timeout
self.remote_user = remote_user
self.remote_pass = remote_pass
self.remote_port = remote_port
self.transport = transport
self.verbose = verbose
self.callbacks = callbacks
self.runner_callbacks = runner_callbacks
self.stats = stats
self.sudo = sudo
self.sudo_pass = sudo_pass
self.sudo_user = sudo_user
self.extra_vars = extra_vars
self.global_vars = {}
self.private_key_file = private_key_file
self.only_tags = only_tags
self.inventory = ansible.inventory.Inventory(host_list)
if not self.inventory._is_script:
self.global_vars.update(self.inventory.get_group_variables('all'))
self.basedir = os.path.dirname(playbook)
self.playbook = self._load_playbook_from_file(playbook)
self.module_path = self.module_path + os.pathsep + os.path.join(self.basedir, "library")
# *****************************************************
def _load_playbook_from_file(self, path):
'''
run top level error checking on playbooks and allow them to include other playbooks.
'''
playbook_data = utils.parse_yaml_from_file(path)
accumulated_plays = []
if type(playbook_data) != list:
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list")
for play in playbook_data:
if type(play) != dict:
raise errors.AnsibleError("parse error: each play in a playbook must a YAML dictionary (hash), recieved: %s" % play)
if 'include' in play:
if len(play.keys()) == 1:
included_path = utils.path_dwim(self.basedir, play['include'])
accumulated_plays.extend(self._load_playbook_from_file(included_path))
else:
raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play)
else:
accumulated_plays.append(play)
return accumulated_plays
# *****************************************************
def run(self):
''' run all patterns in the playbook '''
# loop through all patterns and run them
self.callbacks.on_start()
for play_ds in self.playbook:
self._run_play(Play(self,play_ds))
# summarize the results
results = {}
for host in self.stats.processed.keys():
results[host] = self.stats.summarize(host)
return results
# *****************************************************
def _async_poll(self, poller, async_seconds, async_poll_interval):
''' launch an async job, if poll_interval is set, wait for completion '''
results = poller.wait(async_seconds, async_poll_interval)
# mark any hosts that are still listed as started as failed
# since these likely got killed by async_wrapper
for host in poller.hosts_to_poll:
reason = { 'failed' : 1, 'rc' : None, 'msg' : 'timed out' }
self.runner_callbacks.on_failed(host, reason)
results['contacted'][host] = reason
return results
# *****************************************************
def _run_task_internal(self, task):
''' run a particular module step in a playbook '''
hosts = [ h for h in self.inventory.list_hosts() if (h not in self.stats.failures) and (h not in self.stats.dark)]
self.inventory.restrict_to(hosts)
runner = ansible.runner.Runner(
pattern=task.play.hosts, inventory=self.inventory, module_name=task.module_name,
module_args=task.module_args, forks=self.forks,
remote_pass=self.remote_pass, module_path=self.module_path,
timeout=self.timeout, remote_user=task.play.remote_user,
remote_port=task.play.remote_port, module_vars=task.module_vars,
private_key_file=self.private_key_file,
setup_cache=self.SETUP_CACHE, basedir=self.basedir,
conditional=task.only_if, callbacks=self.runner_callbacks,
verbose=self.verbose, sudo=task.play.sudo, sudo_user=task.play.sudo_user,
transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True
)
if task.async_seconds == 0:
results = runner.run()
else:
results, poller = runner.run_async(task.async_seconds)
self.stats.compute(results)
if task.async_poll_interval > 0:
# if not polling, playbook requested fire and forget, so don't poll
results = self._async_poll(poller, task.async_seconds, task.async_poll_interval)
self.inventory.lift_restriction()
return results
# *****************************************************
def _run_task(self, play, task, is_handler):
''' run a single task in the playbook and recursively run any subtasks. '''
self.callbacks.on_task_start(task.name, is_handler)
# load up an appropriate ansible runner to run the task in parallel
results = self._run_task_internal(task)
# if no hosts are matched, carry on
if results is None:
results = {}
self.stats.compute(results)
# add facts to the global setup cache
for host, result in results['contacted'].iteritems():
facts = result.get('ansible_facts', {})
self.SETUP_CACHE[host].update(facts)
# flag which notify handlers need to be run
if len(task.notify) > 0:
for host, results in results.get('contacted',{}).iteritems():
if results.get('changed', False):
for handler_name in task.notify:
self._flag_handler(play.handlers(), utils.template(handler_name, task.module_vars), host)
# *****************************************************
def _flag_handler(self, handlers, handler_name, host):
'''
if a task has any notify elements, flag handlers for run
at end of execution cycle for hosts that have indicated
changes have been made
'''
found = False
for x in handlers:
if handler_name == x.name:
found = True
self.callbacks.on_notify(host, x.name)
x.notified_by.append(host)
if not found:
raise errors.AnsibleError("change handler (%s) is not defined" % handler_name)
# *****************************************************
def _do_setup_step(self, play):
''' get facts from the remote system '''
host_list = [ h for h in self.inventory.list_hosts(play.hosts)
if not (h in self.stats.failures or h in self.stats.dark) ]
if not play.gather_facts:
return {}
self.callbacks.on_setup()
self.inventory.restrict_to(host_list)
# push any variables down to the system
setup_results = ansible.runner.Runner(
pattern=play.hosts, module_name='setup', module_args={}, inventory=self.inventory,
forks=self.forks, module_path=self.module_path, timeout=self.timeout, remote_user=play.remote_user,
remote_pass=self.remote_pass, remote_port=play.remote_port, private_key_file=self.private_key_file,
setup_cache=self.SETUP_CACHE, callbacks=self.runner_callbacks, sudo=play.sudo, sudo_user=play.sudo_user,
verbose=self.verbose, transport=play.transport, sudo_pass=self.sudo_pass, is_playbook=True
).run()
self.stats.compute(setup_results, setup=True)
self.inventory.lift_restriction()
# now for each result, load into the setup cache so we can
# let runner template out future commands
setup_ok = setup_results.get('contacted', {})
for (host, result) in setup_ok.iteritems():
facts = result.get('ansible_facts', {})
self.SETUP_CACHE[host] = result.get('ansible_facts', {})
return setup_results
# *****************************************************
def _run_play(self, play):
''' run a list of tasks for a given pattern, in order '''
if not play.should_run(self.only_tags):
return
self.callbacks.on_play_start(play.name)
# get facts from system
rc = self._do_setup_step(play)
# now with that data, handle contentional variable file imports!
if play.vars_files and len(play.vars_files) > 0:
play.update_vars_files(self.inventory.list_hosts(play.hosts))
for task in play.tasks():
# only run the task if the requested tags match
should_run = False
for x in self.only_tags:
for y in task.tags:
if (x==y):
should_run = True
break
if should_run:
self._run_task(play, task, False)
# run notify actions
for handler in play.handlers():
if len(handler.notified_by) > 0:
self.inventory.restrict_to(handler.notified_by)
self._run_task(play, handler, True)
self.inventory.lift_restriction()

View File

@ -0,0 +1,295 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
from ansible import utils
from ansible import errors
from ansible.playbook.task import Task
import shlex
import os
class Play(object):
__slots__ = [
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
'handlers', 'remote_user', 'remote_port',
'sudo', 'sudo_user', 'transport', 'playbook',
'tags', 'gather_facts', '_ds', '_handlers', '_tasks'
]
# to catch typos and so forth -- these are userland names
# and don't line up 1:1 with how they are stored
VALID_KEYS = [
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
'tasks', 'handlers', 'user', 'port', 'include',
'sudo', 'sudo_user', 'connection', 'tags', 'gather_facts'
]
# *************************************************
def __init__(self, playbook, ds):
''' constructor loads from a play datastructure '''
for x in ds.keys():
if not x in Play.VALID_KEYS:
raise errors.AnsibleError("%s is not a legal parameter in an Ansible Playbook" % x)
# TODO: more error handling
hosts = ds.get('hosts')
if hosts is None:
raise errors.AnsibleError('hosts declaration is required')
elif isinstance(hosts, list):
hosts = ';'.join(hosts)
hosts = utils.template(hosts, playbook.extra_vars)
self._ds = ds
self.playbook = playbook
self.hosts = hosts
self.name = ds.get('name', self.hosts)
self.vars = ds.get('vars', {})
self.vars_files = ds.get('vars_files', [])
self.vars_prompt = ds.get('vars_prompt', {})
self.vars = self._get_vars(self.playbook.basedir)
self._tasks = ds.get('tasks', [])
self._handlers = ds.get('handlers', [])
self.remote_user = ds.get('user', self.playbook.remote_user)
self.remote_port = ds.get('port', self.playbook.remote_port)
self.sudo = ds.get('sudo', self.playbook.sudo)
self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user)
self.transport = ds.get('connection', self.playbook.transport)
self.tags = ds.get('tags', None)
self.gather_facts = ds.get('gather_facts', True)
self._update_vars_files_for_host(None)
self._tasks = self._load_tasks(self._ds, 'tasks')
self._handlers = self._load_tasks(self._ds, 'handlers')
if self.tags is None:
self.tags = []
elif type(self.tags) in [ str, unicode ]:
self.tags = [ self.tags ]
elif type(self.tags) != list:
self.tags = []
if self.sudo_user != 'root':
self.sudo = True
# *************************************************
def _load_tasks(self, ds, keyname):
''' handle task and handler include statements '''
tasks = ds.get(keyname, [])
results = []
for x in tasks:
task_vars = self.vars.copy()
if 'include' in x:
tokens = shlex.split(x['include'])
for t in tokens[1:]:
(k,v) = t.split("=", 1)
task_vars[k]=v
include_file = utils.template(tokens[0], task_vars)
data = utils.parse_yaml_from_file(utils.path_dwim(self.playbook.basedir, include_file))
elif type(x) == dict:
data = [x]
else:
raise Exception("unexpected task type")
for y in data:
mv = task_vars.copy()
results.append(Task(self,y,module_vars=mv))
for x in results:
if self.tags is not None:
x.tags.extend(self.tags)
return results
# *************************************************
def tasks(self):
''' return task objects for this play '''
return self._tasks
def handlers(self):
''' return handler objects for this play '''
return self._handlers
# *************************************************
def _get_vars(self, dirname):
''' load the vars section from a play, accounting for all sorts of variable features
including loading from yaml files, prompting, and conditional includes of the first
file found in a list. '''
if self.vars is None:
self.vars = {}
if type(self.vars) not in [dict, list]:
raise errors.AnsibleError("'vars' section must contain only key/value pairs")
vars = self.playbook.global_vars
# translate a list of vars into a dict
if type(self.vars) == list:
for item in self.vars:
k, v = item.items()[0]
vars[k] = v
else:
vars.update(self.vars)
if type(self.vars_prompt) == list:
for var in self.vars_prompt:
if not 'name' in var:
raise errors.AnsibleError("'vars_prompt' item is missing 'name:'")
vname = var['name']
prompt = "%s: " % var.get("prompt", vname)
private = var.get("private", True)
vars[vname] = self.playbook.callbacks.on_vars_prompt(vname, private, prompt)
elif type(self.vars_prompt) == dict:
for (vname, prompt) in self.vars_prompt.iteritems():
prompt = "%s: " % prompt
vars[vname] = self.playbook.callbacks.on_vars_prompt(vname, False, prompt)
else:
raise errors.AnsibleError("'vars_prompt' section is malformed, see docs")
results = self.playbook.extra_vars.copy()
results.update(vars)
return results
# *************************************************
def update_vars_files(self, hosts):
''' calculate vars_files, which requires that setup runs first so ansible facts can be mixed in '''
# now loop through all the hosts...
for h in hosts:
self._update_vars_files_for_host(h)
# *************************************************
def should_run(self, tags):
''' does the play match any of the tags? '''
tags_counted = 0
for task in self._tasks:
for task_tag in task.tags:
tags_counted = tags_counted + 1
if task_tag in tags:
return True
if tags_counted > 0:
return False
# didn't tag the play, and the play contains no steps
# so assume we just want to gather facts
return True
# *************************************************
def _has_vars_in(self, msg):
return ((msg.find("$") != -1) or (msg.find("{{") != -1))
# *************************************************
def _update_vars_files_for_host(self, host):
if type(self.vars_files) != list:
self.vars_files = [ self.vars_files ]
if (host is not None):
inventory = self.playbook.inventory
hostrec = inventory.get_host(host)
groups = [ g.name for g in hostrec.groups ]
basedir = inventory.basedir()
if basedir is not None:
for x in groups:
path = os.path.join(basedir, "group_vars/%s" % x)
if os.path.exists(path):
data = utils.parse_yaml_from_file(path)
if type(data) != dict:
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path)
self.playbook.SETUP_CACHE[host].update(data)
path = os.path.join(basedir, "host_vars/%s" % hostrec.name)
if os.path.exists(path):
data = utils.parse_yaml_from_file(path)
if type(data) != dict:
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % path)
self.playbook.SETUP_CACHE[host].update(data)
for filename in self.vars_files:
if type(filename) == list:
# loop over all filenames, loading the first one, and failing if # none found
found = False
sequence = []
for real_filename in filename:
filename2 = utils.template(real_filename, self.vars)
filename3 = filename2
if host is not None:
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
filename4 = utils.path_dwim(self.playbook.basedir, filename3)
sequence.append(filename4)
if os.path.exists(filename4):
found = True
data = utils.parse_yaml_from_file(filename4)
if type(data) != dict:
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % filename4)
if host is not None:
if self._has_vars_in(filename2) and not self._has_vars_in(filename3):
# this filename has variables in it that were fact specific
# so it needs to be loaded into the per host SETUP_CACHE
self.playbook.SETUP_CACHE[host].update(data)
self.playbook.callbacks.on_import_for_host(host, filename4)
elif not self._has_vars_in(filename4):
# found a non-host specific variable, load into vars and NOT
# the setup cache
self.vars.update(data)
elif host is not None:
self.playbook.callbacks.on_not_import_for_host(host, filename4)
if found:
break
if not found:
raise errors.AnsibleError(
"%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence)
)
else:
# just one filename supplied, load it!
filename2 = utils.template(filename, self.vars)
filename3 = filename2
if host is not None:
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
filename4 = utils.path_dwim(self.playbook.basedir, filename3)
if self._has_vars_in(filename4):
return
new_vars = utils.parse_yaml_from_file(filename4)
if new_vars:
if type(new_vars) != dict:
raise errors.AnsibleError("%s must be stored as dictonary/hash: %s" % filename4)
if host is not None and self._has_vars_in(filename2) and not self._has_vars_in(filename3):
# running a host specific pass and has host specific variables
# load into setup cache
self.playbook.SETUP_CACHE[host].update(new_vars)
elif host is None:
# running a non-host specific pass and we can update the global vars instead
self.vars.update(new_vars)

View File

@ -0,0 +1,112 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from ansible import errors
from ansible import utils
class Task(object):
__slots__ = [
'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval',
'notify', 'module_name', 'module_args', 'module_vars',
'play', 'notified_by', 'tags', 'with_items', 'first_available_file', 'ignore_errors'
]
# to prevent typos and such
VALID_KEYS = [
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items', 'first_available_file',
'include', 'tags', 'ignore_errors'
]
def __init__(self, play, ds, module_vars=None):
''' constructor loads from a task or handler datastructure '''
for x in ds.keys():
if not x in Task.VALID_KEYS:
raise errors.AnsibleError("%s is not a legal parameter in an Ansible task or handler" % x)
self.module_vars = module_vars
self.play = play
# load various attributes
self.name = ds.get('name', None)
self.action = ds.get('action', '')
self.tags = [ 'all' ]
# notified by is used by Playbook code to flag which hosts
# need to run a notifier
self.notified_by = []
# if no name is specified, use the action line as the name
if self.name is None:
self.name = self.action
# load various attributes
self.only_if = ds.get('only_if', 'True')
self.async_seconds = int(ds.get('async', 0)) # not async by default
self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds
self.notify = ds.get('notify', [])
self.first_available_file = ds.get('first_available_file', None)
self.with_items = ds.get('with_items', None)
self.ignore_errors = ds.get('ignore_errors', False)
# notify can be a string or a list, store as a list
if isinstance(self.notify, basestring):
self.notify = [ self.notify ]
# split the action line into a module name + arguments
tokens = self.action.split(None, 1)
if len(tokens) < 1:
raise errors.AnsibleError("invalid/missing action in task. name: %s" % self.name)
self.module_name = tokens[0]
self.module_args = ''
if len(tokens) > 1:
self.module_args = tokens[1]
import_tags = self.module_vars.get('tags',[])
if type(import_tags) in [str,unicode]:
# allow the user to list comma delimited tags
import_tags = import_tags.split(",")
self.name = utils.template(self.name, self.module_vars)
self.action = utils.template(self.action, self.module_vars)
# handle mutually incompatible options
if self.with_items is not None and self.first_available_file is not None:
raise errors.AnsibleError("with_items and first_available_file are mutually incompatible in a single task")
# make first_available_file accessable to Runner code
if self.first_available_file:
self.module_vars['first_available_file'] = self.first_available_file
# process with_items so it can be used by Runner code
if self.with_items is None:
self.with_items = [ ]
self.module_vars['items'] = self.with_items
# make ignore_errors accessable to Runner code
self.module_vars['ignore_errors'] = self.ignore_errors
# tags allow certain parts of a playbook to be run without running the whole playbook
apply_tags = ds.get('tags', None)
if apply_tags is not None:
if type(apply_tags) in [ str, unicode ]:
self.tags.append(apply_tags)
elif type(apply_tags) == list:
self.tags.extend(apply_tags)
self.tags.extend(import_tags)

View File

@ -1,475 +0,0 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
################################################
try:
import json
except ImportError:
import simplejson as json
import fnmatch
import multiprocessing
import signal
import os
import ansible.constants as C
import Queue
import random
import paramiko
import jinja2
from ansible.utils import *
################################################
def noop(*args, **kwargs):
pass
def _executor_hook(job_queue, result_queue):
''' callback used by multiprocessing pool '''
signal.signal(signal.SIGINT, signal.SIG_IGN)
while not job_queue.empty():
try:
job = job_queue.get(block=False)
runner, host = job
result_queue.put(runner._executor(host))
except Queue.Empty:
pass
class Runner(object):
def __init__(self,
host_list=C.DEFAULT_HOST_LIST,
module_path=C.DEFAULT_MODULE_PATH,
module_name=C.DEFAULT_MODULE_NAME,
module_args=C.DEFAULT_MODULE_ARGS,
forks=C.DEFAULT_FORKS,
timeout=C.DEFAULT_TIMEOUT,
pattern=C.DEFAULT_PATTERN,
remote_user=C.DEFAULT_REMOTE_USER,
remote_pass=C.DEFAULT_REMOTE_PASS,
background=0,
basedir=None,
setup_cache={},
verbose=False):
'''
Constructor
host_list -- file on disk listing hosts to manage, or an array of hostnames
pattern ------ a fnmatch pattern selecting some of the hosts in host_list
module_path -- location of ansible library on disk
module_name -- which module to run
module_args -- arguments to pass to module
forks -------- how parallel should we be? 1 is extra debuggable.
remote_user -- who to login as (default root)
remote_pass -- provide only if you don't want to use keys or ssh-agent
background --- if non 0, run async, failing after X seconds, -1 == infinite
setup_cache -- used only by playbook (complex explanation pending)
'''
self.setup_cache = setup_cache
self.host_list, self.groups = self.parse_hosts(host_list)
self.module_path = module_path
self.module_name = module_name
self.forks = int(forks)
self.pattern = pattern
self.module_args = module_args
self.timeout = timeout
self.verbose = verbose
self.remote_user = remote_user
self.remote_pass = remote_pass
self.background = background
if basedir is None:
basedir = os.getcwd()
self.basedir = basedir
# hosts in each group name in the inventory file
self._tmp_paths = {}
random.seed()
self.generated_jid = str(random.randint(0, 999999999999))
@classmethod
def parse_hosts(cls, host_list):
'''
parse the host inventory file, returns (hosts, groups)
[groupname]
host1
host2
'''
if type(host_list) == list:
return (host_list, {})
host_list = os.path.expanduser(host_list)
lines = file(host_list).read().split("\n")
groups = {}
groups['ungrouped'] = []
group_name = 'ungrouped'
results = []
for item in lines:
if item.startswith("["):
group_name = item.replace("[","").replace("]","").lstrip().rstrip()
groups[group_name] = []
else:
groups[group_name].append(item)
results.append(item)
return (results, groups)
def _matches(self, host_name, pattern=None):
''' returns if a hostname is matched by the pattern '''
# a pattern is in fnmatch format but more than one pattern
# can be strung together with semicolons. ex:
# atlanta-web*.example.com;dc-web*.example.com
if host_name == '':
return False
pattern = pattern.replace(";",":")
subpatterns = pattern.split(":")
for subpattern in subpatterns:
# the pattern could be a real glob
if subpattern == 'all':
return True
if fnmatch.fnmatch(host_name, subpattern):
return True
# or it could be a literal group name instead
if subpattern in self.groups:
if host_name in self.groups[subpattern]:
return True
return False
def _connect(self, host):
'''
obtains a paramiko connection to the host.
on success, returns (True, connection)
on failure, returns (False, traceback str)
'''
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# try paramiko
ssh.connect(host, username=self.remote_user, allow_agent=True,
look_for_keys=True, password=self.remote_pass, timeout=self.timeout)
return [ True, ssh ]
except Exception, e:
# it failed somehow, return the failure string
return [ False, "FAILED: %s" % str(e) ]
def _return_from_module(self, conn, host, result):
''' helper function to handle JSON parsing of results '''
try:
# try to parse the JSON response
return [ host, True, json.loads(result) ]
except Exception, e:
# it failed, say so, but return the string anyway
return [ host, False, "%s/%s" % (str(e), result) ]
def _delete_remote_files(self, conn, files):
''' deletes one or more remote files '''
if type(files) == str:
files = [ files ]
for filename in files:
if not filename.startswith('/tmp/'):
raise Exception("not going to happen")
self._exec_command(conn, "rm -rf %s" % filename)
def _transfer_file(self, conn, source, dest):
''' transfers a remote file '''
self.remote_log(conn, 'COPY remote:%s local:%s' % (source, dest))
sftp = conn.open_sftp()
sftp.put(source, dest)
sftp.close()
def _transfer_module(self, conn, tmp, module):
'''
transfers a module file to the remote side to execute it,
but does not execute it yet
'''
outpath = self._copy_module(conn, tmp, module)
self._exec_command(conn, "chmod +x %s" % outpath)
return outpath
def _execute_module(self, conn, tmp, remote_module_path, module_args):
'''
runs a module that has already been transferred, but first
modifies the command using setup_cache variables (see playbook)
'''
args = module_args
if type(args) == list:
args = [ str(x) for x in module_args ]
args = " ".join(args)
inject_vars = self.setup_cache.get(conn._host,{})
# the metadata location for the setup module is transparently managed
# since it's an 'internals' module, kind of a black box. See playbook
# other modules are not allowed to have this kind of handling
if remote_module_path.endswith("/setup") and args.find("metadata=") == -1:
if self.remote_user == 'root':
args = "%s metadata=/etc/ansible/setup" % args
else:
args = "%s metadata=~/.ansible/setup" % args
template = jinja2.Template(args)
args = template.render(inject_vars)
cmd = "%s %s" % (remote_module_path, args)
result = self._exec_command(conn, cmd)
self._delete_remote_files(conn, [ tmp ])
return result
def _execute_normal_module(self, conn, host, tmp):
'''
transfer & execute a module that is not 'copy' or 'template'
because those require extra work.
'''
module = self._transfer_module(conn, tmp, self.module_name)
result = self._execute_module(conn, tmp, module, self.module_args)
# when running the setup module, which pushes vars to the host and ALSO
# returns them (+factoids), store the variables that were returned such that commands
# run AFTER setup use these variables for templating when executed
# from playbooks
if self.module_name == 'setup':
host = conn._host
try:
var_result = json.loads(result)
except:
var_result = {}
self._delete_remote_files(conn, tmp)
return self._return_from_module(conn, host, result)
def _execute_async_module(self, conn, host, tmp):
'''
transfer the given module name, plus the async module
and then run the async module wrapping the other module
'''
async = self._transfer_module(conn, tmp, 'async_wrapper')
module = self._transfer_module(conn, tmp, self.module_name)
new_args = []
new_args = [ self.generated_jid, self.background, module ]
new_args.extend(self.module_args)
result = self._execute_module(conn, tmp, async, new_args)
return self._return_from_module(conn, host, result)
def _parse_kv(self, args):
''' helper function to convert a string of key/value items to a dict '''
options = {}
for x in args:
if x.find("=") != -1:
k, v = x.split("=")
options[k]=v
return options
def _execute_copy(self, conn, host, tmp):
''' handler for file transfer operations '''
# load up options
options = self._parse_kv(self.module_args)
source = options['src']
dest = options['dest']
# transfer the file to a remote tmp location
tmp_path = tmp
tmp_src = tmp_path + source.split('/')[-1]
self._transfer_file(conn, path_dwim(self.basedir, source), tmp_src)
# install the copy module
self.module_name = 'copy'
module = self._transfer_module(conn, tmp, 'copy')
# run the copy module
args = [ "src=%s" % tmp_src, "dest=%s" % dest ]
result = self._execute_module(conn, tmp, module, args)
self._delete_remote_files(conn, tmp_path)
return self._return_from_module(conn, host, result)
def _execute_template(self, conn, host, tmp):
''' handler for template operations '''
# load up options
options = self._parse_kv(self.module_args)
source = options['src']
dest = options['dest']
metadata = options.get('metadata', None)
if metadata is None:
if self.remote_user == 'root':
metadata = '/etc/ansible/setup'
else:
metadata = '~/.ansible/setup'
# first copy the source template over
tpath = tmp
tempname = os.path.split(source)[-1]
temppath = tpath + tempname
self._transfer_file(conn, path_dwim(self.basedir, source), temppath)
# install the template module
template_module = self._transfer_module(conn, tmp, 'template')
# run the template module
args = [ "src=%s" % temppath, "dest=%s" % dest, "metadata=%s" % metadata ]
result = self._execute_module(conn, tmp, template_module, args)
self._delete_remote_files(conn, [ tpath ])
return self._return_from_module(conn, host, result)
def _executor(self, host):
'''
callback executed in parallel for each host.
returns (hostname, connected_ok, extra)
where extra is the result of a successful connect
or a traceback string
'''
# depending on whether it's a normal module,
# or a request to use the copy or template
# module, call the appropriate executor function
ok, conn = self._connect(host)
conn._host = host
if not ok:
return [ host, False, conn ]
tmp = self._get_tmp_path(conn)
result = None
if self.module_name not in [ 'copy', 'template' ]:
if self.background == 0:
result = self._execute_normal_module(conn, host, tmp)
else:
result = self._execute_async_module(conn, host, tmp)
elif self.module_name == 'copy':
result = self._execute_copy(conn, host, tmp)
elif self.module_name == 'template':
result = self._execute_template(conn, host, tmp)
else:
# this would be a coding error in THIS module
# shouldn't occur
raise Exception("???")
self._delete_remote_files(conn, tmp)
conn.close()
return result
def remote_log(self, conn, msg):
''' this is the function we use to log things '''
stdin, stdout, stderr = conn.exec_command('/usr/bin/logger -t ansible -p auth.info %r' % msg)
# TODO: maybe make that optional
def _exec_command(self, conn, cmd):
''' execute a command string over SSH, return the output '''
msg = '%s: %s' % (self.module_name, cmd)
self.remote_log(conn, msg)
stdin, stdout, stderr = conn.exec_command(cmd)
results = "\n".join(stdout.readlines())
return results
def _get_tmp_path(self, conn):
''' gets a temporary path on a remote box '''
result = self._exec_command(conn, "mktemp -d /tmp/ansible.XXXXXX")
return result.split("\n")[0] + '/'
def _copy_module(self, conn, tmp, module):
''' transfer a module over SFTP, does not run it '''
in_path = os.path.expanduser(
os.path.join(self.module_path, module)
)
out_path = tmp + module
sftp = conn.open_sftp()
sftp.put(in_path, out_path)
sftp.close()
return out_path
def match_hosts(self, pattern):
''' return all matched hosts fitting a pattern '''
return [ h for h in self.host_list if self._matches(h, pattern) ]
def run(self):
''' xfer & run module on all matched hosts '''
# find hosts that match the pattern
hosts = self.match_hosts(self.pattern)
if len(hosts) == 0:
return None
# attack pool of hosts in N forks
# _executor_hook does all of the work
hosts = [ (self,x) for x in hosts ]
if self.forks > 1:
job_queue = multiprocessing.Manager().Queue()
result_queue = multiprocessing.Manager().Queue()
for i in hosts:
job_queue.put(i)
workers = []
for i in range(self.forks):
tmp = multiprocessing.Process(target=_executor_hook,
args=(job_queue, result_queue))
tmp.start()
workers.append(tmp)
try:
for worker in workers:
worker.join()
except KeyboardInterrupt:
for worker in workers:
worker.terminate()
worker.join()
results = []
while not result_queue.empty():
results.append(result_queue.get(block=False))
else:
results = [ x._executor(h) for (x,h) in hosts ]
# sort hosts by ones we successfully contacted
# and ones we did not so that we can return a
# dictionary containing results of everything
results2 = {
"contacted" : {},
"dark" : {}
}
hosts_with_results = []
for x in results:
(host, is_ok, result) = x
hosts_with_results.append(host)
if not is_ok:
results2["dark"][host] = result
else:
results2["contacted"][host] = result
# hosts which were contacted but never got a chance
# to return a result before we exited/ctrl-c'd
# perhaps these shouldn't be 'dark' but I'm not sure if they fit
# anywhere else.
for host in self.match_hosts(self.pattern):
if host not in hosts_with_results:
results2["dark"][host] = {}
return results2

View File

@ -0,0 +1,797 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import multiprocessing
import signal
import os
import pwd
import Queue
import random
import traceback
import tempfile
import time
import base64
import getpass
import codecs
import collections
import re
import ansible.constants as C
import ansible.inventory
from ansible import utils
from ansible import errors
from ansible import module_common
import poller
import connection
from ansible import callbacks as ans_callbacks
HAS_ATFORK=True
try:
from Crypto.Random import atfork
except ImportError:
HAS_ATFORK=False
################################################
def _executor_hook(job_queue, result_queue):
''' callback used by multiprocessing pool '''
# attempt workaround of https://github.com/newsapps/beeswithmachineguns/issues/17
# this function also not present in CentOS 6
if HAS_ATFORK:
atfork()
signal.signal(signal.SIGINT, signal.SIG_IGN)
while not job_queue.empty():
try:
job = job_queue.get(block=False)
runner, host = job
result_queue.put(runner._executor(host))
except Queue.Empty:
pass
except:
traceback.print_exc()
################################################
class ReturnData(object):
''' internal return class for execute methods, not part of API signature '''
__slots__ = [ 'result', 'comm_ok', 'host' ]
def __init__(self, host=None, result=None, comm_ok=True):
self.host = host
self.result = result
self.comm_ok = comm_ok
if type(self.result) in [ str, unicode ]:
self.result = utils.parse_json(self.result)
if host is None:
raise Exception("host not set")
if type(self.result) != dict:
raise Exception("dictionary result expected")
def communicated_ok(self):
return self.comm_ok
def is_successful(self):
return self.comm_ok and ('failed' not in self.result) and (self.result.get('rc',0) == 0)
def daisychain(self, module_name):
''' request a module call follow this one '''
if self.is_successful():
self.result['daisychain'] = module_name
return self
class Runner(object):
''' core API interface to ansible '''
# see bin/ansible for how this is used...
def __init__(self,
host_list=C.DEFAULT_HOST_LIST, # ex: /etc/ansible/hosts, legacy usage
module_path=C.DEFAULT_MODULE_PATH, # ex: /usr/share/ansible
module_name=C.DEFAULT_MODULE_NAME, # ex: copy
module_args=C.DEFAULT_MODULE_ARGS, # ex: "src=/tmp/a dest=/tmp/b"
forks=C.DEFAULT_FORKS, # parallelism level
timeout=C.DEFAULT_TIMEOUT, # SSH timeout
pattern=C.DEFAULT_PATTERN, # which hosts? ex: 'all', 'acme.example.org'
remote_user=C.DEFAULT_REMOTE_USER, # ex: 'username'
remote_pass=C.DEFAULT_REMOTE_PASS, # ex: 'password123' or None if using key
remote_port=C.DEFAULT_REMOTE_PORT, # if SSH on different ports
private_key_file=C.DEFAULT_PRIVATE_KEY_FILE, # if not using keys/passwords
sudo_pass=C.DEFAULT_SUDO_PASS, # ex: 'password123' or None
background=0, # async poll every X seconds, else 0 for non-async
basedir=None, # directory of playbook, if applicable
setup_cache=None, # used to share fact data w/ other tasks
transport=C.DEFAULT_TRANSPORT, # 'ssh', 'paramiko', 'local'
conditional='True', # run only if this fact expression evals to true
callbacks=None, # used for output
verbose=False, # whether to show more or less
sudo=False, # whether to run sudo or not
sudo_user=C.DEFAULT_SUDO_USER, # ex: 'root'
module_vars=None, # a playbooks internals thing
is_playbook=False, # running from playbook or not?
inventory=None # reference to Inventory object
):
# storage & defaults
self.setup_cache = utils.default(setup_cache, lambda: collections.defaultdict(dict))
self.basedir = utils.default(basedir, lambda: os.getcwd())
self.callbacks = utils.default(callbacks, lambda: ans_callbacks.DefaultRunnerCallbacks())
self.generated_jid = str(random.randint(0, 999999999999))
self.transport = transport
self.inventory = utils.default(inventory, lambda: ansible.inventory.Inventory(host_list))
self.module_vars = utils.default(module_vars, lambda: {})
self.sudo_user = sudo_user
self.connector = connection.Connection(self)
self.conditional = conditional
self.module_path = module_path
self.module_name = module_name
self.forks = int(forks)
self.pattern = pattern
self.module_args = module_args
self.timeout = timeout
self.verbose = verbose
self.remote_user = remote_user
self.remote_pass = remote_pass
self.remote_port = remote_port
self.private_key_file = private_key_file
self.background = background
self.sudo = sudo
self.sudo_pass = sudo_pass
self.is_playbook = is_playbook
# misc housekeeping
if self.transport == 'ssh' and remote_pass:
raise errors.AnsibleError("SSH transport does not support passwords, only keys or agents")
if self.transport == 'local':
self.remote_user = pwd.getpwuid(os.geteuid())[0]
# ensure we are using unique tmp paths
random.seed()
# *****************************************************
def _delete_remote_files(self, conn, files):
''' deletes one or more remote files '''
if type(files) == str:
files = [ files ]
for filename in files:
if filename.find('/tmp/') == -1:
raise Exception("not going to happen")
self._low_level_exec_command(conn, "rm -rf %s" % filename, None)
# *****************************************************
def _transfer_str(self, conn, tmp, name, data):
''' transfer string to remote file '''
if type(data) == dict:
data = utils.jsonify(data)
afd, afile = tempfile.mkstemp()
afo = os.fdopen(afd, 'w')
afo.write(data.encode('utf8'))
afo.flush()
afo.close()
remote = os.path.join(tmp, name)
try:
conn.put_file(afile, remote)
finally:
os.unlink(afile)
return remote
# *****************************************************
def _execute_module(self, conn, tmp, module_name, args,
async_jid=None, async_module=None, async_limit=None, inject=None):
''' runs a module that has already been transferred '''
if type(args) == dict:
args = utils.jsonify(args,format=True)
(remote_module_path, is_new_style) = self._copy_module(conn, tmp, module_name, inject)
cmd = "chmod +x %s" % remote_module_path
if self.sudo and self.sudo_user != 'root':
# deal with possible umask issues once sudo'ed to other user
cmd = "chmod 555 %s" % remote_module_path
self._low_level_exec_command(conn, cmd, tmp)
cmd = ""
if not is_new_style:
args = utils.template(args, inject)
argsfile = self._transfer_str(conn, tmp, 'arguments', args)
if async_jid is None:
cmd = "%s %s" % (remote_module_path, argsfile)
else:
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]])
else:
if async_jid is None:
cmd = "%s" % (remote_module_path)
else:
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module]])
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
return ReturnData(host=conn.host, result=res)
# *****************************************************
def _execute_raw(self, conn, tmp, inject=None):
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
return ReturnData(host=conn.host, result=dict(
stdout=self._low_level_exec_command(conn, self.module_args, tmp, sudoable = True)
))
# ***************************************************
def _execute_normal_module(self, conn, tmp, module_name, inject=None):
''' transfer & execute a module that is not 'copy' or 'template' '''
# shell and command are the same module
if module_name == 'shell':
module_name = 'command'
self.module_args += " #USE_SHELL"
exec_rc = self._execute_module(conn, tmp, module_name, self.module_args, inject=inject)
return exec_rc
# *****************************************************
def _execute_async_module(self, conn, tmp, module_name, inject=None):
''' transfer the given module name, plus the async module, then run it '''
# shell and command module are the same
module_args = self.module_args
if module_name == 'shell':
module_name = 'command'
module_args += " #USE_SHELL"
(module_path, is_new_style) = self._copy_module(conn, tmp, module_name, inject)
self._low_level_exec_command(conn, "chmod +x %s" % module_path, tmp)
return self._execute_module(conn, tmp, 'async_wrapper', module_args,
async_module=module_path,
async_jid=self.generated_jid,
async_limit=self.background,
inject=inject
)
# *****************************************************
def _execute_copy(self, conn, tmp, inject=None):
''' handler for file transfer operations '''
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and not 'first_available_file' in inject) or dest is None:
result=dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in inject.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
results=dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(host=conn.host, results=results)
source = utils.template(source, inject)
source = utils.path_dwim(self.basedir, source)
local_md5 = utils.md5(source)
if local_md5 is None:
result=dict(failed=True, msg="could not find src=%s" % source)
return ReturnData(host=conn.host, result=result)
remote_md5 = self._remote_md5(conn, tmp, dest)
exec_rc = None
if local_md5 != remote_md5:
# transfer the file to a remote tmp location
tmp_src = tmp + source.split('/')[-1]
conn.put_file(source, tmp_src)
# run the copy module
self.module_args = "%s src=%s" % (self.module_args, tmp_src)
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
else:
# no need to transfer the file, already correct md5
result = dict(changed=False, md5sum=remote_md5, transferred=False)
return ReturnData(host=conn.host, result=result).daisychain('file')
# *****************************************************
def _execute_fetch(self, conn, tmp, inject=None):
''' handler for fetch operations '''
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if source is None or dest is None:
results = dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, result=results)
# apply templating to source argument
source = utils.template(source, inject)
# apply templating to dest argument
dest = utils.template(dest, inject)
# files are saved in dest dir, with a subdir for each host, then the filename
dest = "%s/%s/%s" % (utils.path_dwim(self.basedir, dest), conn.host, source)
dest = dest.replace("//","/")
# calculate md5 sum for the remote file
remote_md5 = self._remote_md5(conn, tmp, source)
# these don't fail because you may want to transfer a log file that possibly MAY exist
# but keep going to fetch other log files
if remote_md5 == '0':
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
if remote_md5 == '1':
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
if remote_md5 == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
# calculate md5 sum for the local file
local_md5 = utils.md5(dest)
if remote_md5 != local_md5:
# create the containing directories, if needed
if not os.path.isdir(os.path.dirname(dest)):
os.makedirs(os.path.dirname(dest))
# fetch the file and check for changes
conn.fetch_file(source, dest)
new_md5 = utils.md5(dest)
if new_md5 != remote_md5:
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
return ReturnData(host=conn.host, result=result)
result = dict(changed=True, md5sum=new_md5)
return ReturnData(host=conn.host, result=result)
else:
result = dict(changed=False, md5sum=local_md5, file=source)
return ReturnData(host=conn.host, result=result)
# *****************************************************
def _execute_template(self, conn, tmp, inject=None):
''' handler for template operations '''
if not self.is_playbook:
raise errors.AnsibleError("in current versions of ansible, templates are only usable in playbooks")
# load up options
options = utils.parse_kv(self.module_args)
source = options.get('src', None)
dest = options.get('dest', None)
if (source is None and 'first_available_file' not in inject) or dest is None:
result = dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, comm_ok=False, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
if 'first_available_file' in inject:
found = False
for fn in self.module_vars.get('first_available_file'):
fn = utils.template(fn, inject)
if os.path.exists(fn):
source = fn
found = True
break
if not found:
result = dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(host=conn.host, comm_ok=False, result=result)
source = utils.template(source, inject)
# template the source data locally & transfer
try:
resultant = utils.template_from_file(self.basedir, source, inject)
except Exception, e:
result = dict(failed=True, msg=str(e))
return ReturnData(host=conn.host, comm_ok=False, result=result)
xfered = self._transfer_str(conn, tmp, 'source', resultant)
# run the copy module, queue the file module
self.module_args = "%s src=%s dest=%s" % (self.module_args, xfered, dest)
return self._execute_module(conn, tmp, 'copy', self.module_args, inject=inject).daisychain('file')
# *****************************************************
def _execute_assemble(self, conn, tmp, inject=None):
''' handler for assemble operations '''
# FIXME: once assemble is ported over to the use the new common logic, this method
# will be unneccessary as it can decide to daisychain via it's own module returns.
# and this function can be deleted.
return self._execute_module(conn, tmp, 'assemble', self.module_args, inject=inject).daisychain('file')
# *****************************************************
def _executor(self, host):
''' handler for multiprocessing library '''
try:
exec_rc = self._executor_internal(host)
if type(exec_rc) != ReturnData:
raise Exception("unexpected return type: %s" % type(exec_rc))
# redundant, right?
if not exec_rc.comm_ok:
self.callbacks.on_unreachable(host, exec_rc.result)
return exec_rc
except errors.AnsibleError, ae:
msg = str(ae)
self.callbacks.on_unreachable(host, msg)
return ReturnData(host=host, comm_ok=False, result=dict(failed=True, msg=msg))
except Exception:
msg = traceback.format_exc()
self.callbacks.on_unreachable(host, msg)
return ReturnData(host=host, comm_ok=False, result=dict(failed=True, msg=msg))
# *****************************************************
def _executor_internal(self, host):
''' executes any module one or more times '''
host_variables = self.inventory.get_variables(host)
port = host_variables.get('ansible_ssh_port', self.remote_port)
inject = self.setup_cache[host].copy()
inject.update(host_variables)
inject.update(self.module_vars)
inject['hostvars'] = self.setup_cache
items = self.module_vars.get('items', [])
if isinstance(items, basestring) and items.startswith("$"):
items = items.replace("$","")
if items in inject:
items = inject[items]
else:
raise errors.AnsibleError("unbound variable in with_items: %s" % items)
if type(items) != list:
raise errors.AnsibleError("with_items only takes a list: %s" % items)
if self.module_name in [ 'apt', 'yum' ]:
# hack for apt and soon yum, with_items maps back into a single module call
inject['item'] = ",".join(items)
items = []
if len(items) == 0:
return self._executor_internal_inner(host, inject, port)
else:
# executing using with_items, so make multiple calls
# TODO: refactor
aggregrate = {}
all_comm_ok = True
all_changed = False
all_failed = False
results = []
# Save module name and args since daisy-chaining can overwrite them
module_name = self.module_name
module_args = self.module_args
for x in items:
self.module_name = module_name
self.module_args = module_args
inject['item'] = x
result = self._executor_internal_inner(host, inject, port)
results.append(result.result)
if result.comm_ok == False:
all_comm_ok = False
break
for x in results:
if x.get('changed') == True:
all_changed = True
if (x.get('failed') == True) or (('rc' in x) and (x['rc'] != 0)):
all_failed = True
break
msg = 'All items succeeded'
if all_failed:
msg = "One or more items failed."
rd_result = dict(failed=all_failed, changed=all_changed, results=results, msg=msg)
if not all_failed:
del rd_result['failed']
return ReturnData(host=host, comm_ok=all_comm_ok, result=rd_result)
# *****************************************************
def _executor_internal_inner(self, host, inject, port, is_chained=False):
''' decides how to invoke a module '''
# special non-user/non-fact variables:
# 'groups' variable is a list of host name in each group
# 'hostvars' variable contains variables for each host name
# ... and is set elsewhere
# 'inventory_hostname' is also set elsewhere
group_hosts = {}
for g in self.inventory.groups:
group_hosts[g.name] = [ h.name for h in g.hosts ]
inject['groups'] = group_hosts
# allow module args to work as a dictionary
# though it is usually a string
new_args = ""
if type(self.module_args) == dict:
for (k,v) in self.module_args.iteritems():
new_args = new_args + "%s='%s' " % (k,v)
self.module_args = new_args
self.module_args = utils.template(self.module_args, inject)
conditional = utils.template(self.conditional, inject)
if not eval(conditional):
result = utils.jsonify(dict(skipped=True))
self.callbacks.on_skipped(host, inject.get('item',None))
return ReturnData(host=host, result=result)
conn = None
try:
conn = self.connector.connect(host, port)
except errors.AnsibleConnectionFailed, e:
result = dict(failed=True, msg="FAILED: %s" % str(e))
return ReturnData(host=host, comm_ok=False, result=result)
module_name = utils.template(self.module_name, inject)
tmp = self._make_tmp_path(conn)
result = None
handler = getattr(self, "_execute_%s" % self.module_name, None)
if handler:
result = handler(conn, tmp, inject=inject)
else:
if self.background == 0:
result = self._execute_normal_module(conn, tmp, module_name, inject=inject)
else:
result = self._execute_async_module(conn, tmp, module_name, inject=inject)
result.result['module'] = self.module_name
if result.is_successful() and 'daisychain' in result.result:
self.module_name = result.result['daisychain']
if 'daisychain_args' in result.result:
self.module_args = result.result['daisychain_args']
result2 = self._executor_internal_inner(host, inject, port, is_chained=True)
result2.result['module'] = self.module_name
changed = False
if result.result.get('changed',False) or result2.result.get('changed',False):
changed = True
result.result.update(result2.result)
result.result['changed'] = changed
del result.result['daisychain']
self._delete_remote_files(conn, tmp)
conn.close()
if not result.comm_ok:
# connection or parsing errors...
self.callbacks.on_unreachable(host, result.result)
else:
data = result.result
if 'item' in inject:
result.result['item'] = inject['item']
if is_chained:
# no callbacks
return result
if 'skipped' in data:
self.callbacks.on_skipped(result.host)
elif not result.is_successful():
ignore_errors = self.module_vars.get('ignore_errors', False)
self.callbacks.on_failed(result.host, data, ignore_errors)
if ignore_errors:
if 'failed' in result.result:
result.result['failed'] = False
if 'rc' in result.result:
result.result['rc'] = 0
else:
self.callbacks.on_ok(result.host, data)
return result
# *****************************************************
def _low_level_exec_command(self, conn, cmd, tmp, sudoable=False):
''' execute a command string over SSH, return the output '''
sudo_user = self.sudo_user
stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudo_user, sudoable=sudoable)
if type(stdout) != str:
out = "\n".join(stdout.readlines())
else:
out = stdout
if type(stderr) != str:
err = "\n".join(stderr.readlines())
else:
err = stderr
return out + err
# *****************************************************
def _remote_md5(self, conn, tmp, path):
''' takes a remote md5sum without requiring python, and returns 0 if no file '''
test = "rc=0; [[ -r \"%s\" ]] || rc=2; [[ -f \"%s\" ]] || rc=1" % (path,path)
md5s = [
"(/usr/bin/md5sum %s 2>/dev/null)" % path,
"(/sbin/md5sum -q %s 2>/dev/null)" % path,
"(/usr/bin/digest -a md5 -v %s 2>/dev/null)" % path
]
cmd = " || ".join(md5s)
cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path)
return self._low_level_exec_command(conn, cmd, tmp, sudoable=False).split()[0]
# *****************************************************
def _make_tmp_path(self, conn):
''' make and return a temporary path on a remote box '''
basefile = 'ansible-%s-%s' % (time.time(), random.randint(0, 2**48))
basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile)
if self.remote_user == 'root':
basetmp = os.path.join('/var/tmp', basefile)
elif self.sudo and self.sudo_user != 'root':
basetmp = os.path.join('/tmp', basefile)
cmd = 'mkdir -p %s' % basetmp
if self.remote_user != 'root':
cmd += ' && chmod a+x %s' % basetmp
cmd += ' && echo %s' % basetmp
result = self._low_level_exec_command(conn, cmd, None, sudoable=False)
return result.split("\n")[0].strip() + '/'
# *****************************************************
def _copy_module(self, conn, tmp, module, inject):
''' transfer a module over SFTP, does not run it '''
if module.startswith("/"):
raise errors.AnsibleFileNotFound("%s is not a module" % module)
# Search module path(s) for named module.
for module_path in self.module_path.split(os.pathsep):
in_path = os.path.expanduser(os.path.join(module_path, module))
if os.path.exists(in_path):
break
else:
raise errors.AnsibleFileNotFound("module %s not found in %s" % (module, self.module_path))
out_path = os.path.join(tmp, module)
module_data = ""
is_new_style=False
with open(in_path) as f:
module_data = f.read()
if module_common.REPLACER in module_data:
is_new_style=True
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
encoded_args = "\"\"\"%s\"\"\"" % utils.template(self.module_args, inject).replace("\"","\\\"")
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
# use the correct python interpreter for the host
if 'ansible_python_interpreter' in inject:
interpreter = inject['ansible_python_interpreter']
module_lines = module_data.split('\n')
if '#!' and 'python' in module_lines[0]:
module_lines[0] = "#!%s" % interpreter
module_data = "\n".join(module_lines)
self._transfer_str(conn, tmp, module, module_data)
return (out_path, is_new_style)
# *****************************************************
def _parallel_exec(self, hosts):
''' handles mulitprocessing when more than 1 fork is required '''
job_queue = multiprocessing.Manager().Queue()
[job_queue.put(i) for i in hosts]
result_queue = multiprocessing.Manager().Queue()
workers = []
for i in range(self.forks):
prc = multiprocessing.Process(target=_executor_hook,
args=(job_queue, result_queue))
prc.start()
workers.append(prc)
try:
for worker in workers:
worker.join()
except KeyboardInterrupt:
for worker in workers:
worker.terminate()
worker.join()
results = []
while not result_queue.empty():
results.append(result_queue.get(block=False))
return results
# *****************************************************
def _partition_results(self, results):
''' seperate results by ones we contacted & ones we didn't '''
if results is None:
return None
results2 = dict(contacted={}, dark={})
for result in results:
host = result.host
if host is None:
raise Exception("internal error, host not set")
if result.communicated_ok():
results2["contacted"][host] = result.result
else:
results2["dark"][host] = result.result
# hosts which were contacted but never got a chance to return
for host in self.inventory.list_hosts(self.pattern):
if not (host in results2['dark'] or host in results2['contacted']):
results2["dark"][host] = {}
return results2
# *****************************************************
def run(self):
''' xfer & run module on all matched hosts '''
# find hosts that match the pattern
hosts = self.inventory.list_hosts(self.pattern)
if len(hosts) == 0:
self.callbacks.on_no_hosts()
return dict(contacted={}, dark={})
hosts = [ (self,x) for x in hosts ]
results = None
if self.forks > 1:
results = self._parallel_exec(hosts)
else:
results = [ self._executor(h[1]) for h in hosts ]
return self._partition_results(results)
# *****************************************************
def run_async(self, time_limit):
''' Run this module asynchronously and return a poller. '''
self.background = time_limit
results = self.run()
return results, poller.AsyncPoller(results, self)

View File

@ -0,0 +1,43 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
################################################
import local
import paramiko_ssh
import ssh
class Connection(object):
''' Handles abstract connections to remote hosts '''
def __init__(self, runner):
self.runner = runner
def connect(self, host, port=None):
conn = None
transport = self.runner.transport
if transport == 'local':
conn = local.LocalConnection(self.runner, host)
elif transport == 'paramiko':
conn = paramiko_ssh.ParamikoConnection(self.runner, host, port)
elif transport == 'ssh':
conn = ssh.SSHConnection(self.runner, host, port)
if conn is None:
raise Exception("unsupported connection type")
return conn.connect()

View File

@ -0,0 +1,72 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import traceback
import os
import shutil
import subprocess
from ansible import errors
class LocalConnection(object):
''' Local based connections '''
def __init__(self, runner, host):
self.runner = runner
self.host = host
def connect(self, port=None):
''' connect to the local host; nothing to do here '''
return self
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False):
''' run a command on the local host '''
if self.runner.sudo and sudoable:
if self.runner.sudo_pass:
# NOTE: if someone wants to add sudo w/ password to the local connection type, they are welcome
# to do so. The primary usage of the local connection is for crontab and kickstart usage however
# so this doesn't seem to be a huge priority
raise errors.AnsibleError("sudo with password is presently only supported on the paramiko (SSH) connection type")
cmd = "sudo -u {0} -s {1}".format(sudo_user, cmd)
p = subprocess.Popen(cmd, shell=True, stdin=None,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return ("", stdout, stderr)
def put_file(self, in_path, out_path):
''' transfer a file from local to local '''
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
try:
shutil.copyfile(in_path, out_path)
except shutil.Error:
traceback.print_exc()
raise errors.AnsibleError("failed to copy: %s and %s are the same" % (in_path, out_path))
except IOError:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
def fetch_file(self, in_path, out_path):
''' fetch a file from local to local -- for copatibility '''
self.put_file(in_path, out_path)
def close(self):
''' terminate the connection; nothing to do here '''
pass

View File

@ -0,0 +1,144 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import warnings
import traceback
import os
import re
import shutil
import subprocess
import pipes
import socket
import random
from ansible import errors
# prevent paramiko warning noise -- see http://stackoverflow.com/questions/3920502/
HAVE_PARAMIKO=False
with warnings.catch_warnings():
warnings.simplefilter("ignore")
try:
import paramiko
HAVE_PARAMIKO=True
except ImportError:
pass
class ParamikoConnection(object):
''' SSH based connections with Paramiko '''
def __init__(self, runner, host, port=None):
self.ssh = None
self.runner = runner
self.host = host
self.port = port
if port is None:
self.port = self.runner.remote_port
def connect(self):
''' activates the connection object '''
if not HAVE_PARAMIKO:
raise errors.AnsibleError("paramiko is not installed")
user = self.runner.remote_user
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(self.host, username=user, allow_agent=True, look_for_keys=True,
key_filename=self.runner.private_key_file, password=self.runner.remote_pass,
timeout=self.runner.timeout, port=self.port)
except Exception, e:
msg = str(e)
if "PID check failed" in msg:
raise errors.AnsibleError("paramiko version issue, please upgrade paramiko on the machine running ansible")
elif "Private key file is encrypted" in msg:
msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u <username>.' % (
user, self.host, self.port, msg)
raise errors.AnsibleConnectionFailed(msg)
else:
raise errors.AnsibleConnectionFailed(msg)
self.ssh = ssh
return self
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False):
''' run a command on the remote host '''
bufsize = 4096
chan = self.ssh.get_transport().open_session()
chan.get_pty()
if not self.runner.sudo or not sudoable:
quoted_command = '"$SHELL" -c ' + pipes.quote(cmd)
chan.exec_command(quoted_command)
else:
# Rather than detect if sudo wants a password this time, -k makes
# sudo always ask for a password if one is required. The "--"
# tells sudo that this is the end of sudo options and the command
# follows. Passing a quoted compound command to sudo (or sudo -s)
# directly doesn't work, so we shellquote it with pipes.quote()
# and pass the quoted string to the user's shell. We loop reading
# output until we see the randomly-generated sudo prompt set with
# the -p option.
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
prompt = '[sudo via ansible, key=%s] password: ' % randbits
sudocmd = 'sudo -k && sudo -p "%s" -u %s -- "$SHELL" -c %s' % (
prompt, sudo_user, pipes.quote(cmd))
sudo_output = ''
try:
chan.exec_command(sudocmd)
if self.runner.sudo_pass:
while not sudo_output.endswith(prompt):
chunk = chan.recv(bufsize)
if not chunk:
if 'unknown user' in sudo_output:
raise errors.AnsibleError(
'user %s does not exist' % sudo_user)
else:
raise errors.AnsibleError('ssh connection ' +
'closed waiting for password prompt')
sudo_output += chunk
chan.sendall(self.runner.sudo_pass + '\n')
except socket.timeout:
raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output)
return (chan.makefile('wb', bufsize), chan.makefile('rb', bufsize), '')
def put_file(self, in_path, out_path):
''' transfer a file from local to remote '''
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
sftp = self.ssh.open_sftp()
try:
sftp.put(in_path, out_path)
except IOError:
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
sftp.close()
def fetch_file(self, in_path, out_path):
''' save a remote file to the specified path '''
sftp = self.ssh.open_sftp()
try:
sftp.get(in_path, out_path)
except IOError:
raise errors.AnsibleError("failed to transfer file from %s" % in_path)
sftp.close()
def close(self):
''' terminate the connection '''
self.ssh.close()

View File

@ -0,0 +1,138 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import os
import subprocess
import shlex
import pipes
import random
import select
import fcntl
from ansible import errors
class SSHConnection(object):
''' ssh based connections '''
def __init__(self, runner, host, port):
self.runner = runner
self.host = host
self.port = port
def connect(self):
''' connect to the remote host '''
self.common_args = []
extra_args = os.getenv("ANSIBLE_SSH_ARGS", None)
if extra_args is not None:
self.common_args += shlex.split(extra_args)
else:
self.common_args += ["-o", "ControlMaster=auto",
"-o", "ControlPersist=60s",
"-o", "ControlPath=/tmp/ansible-ssh-%h-%p-%r"]
self.common_args += ["-o", "StrictHostKeyChecking=no"]
if self.port is not None:
self.common_args += ["-o", "Port=%d" % (self.port)]
if self.runner.private_key_file is not None:
self.common_args += ["-o", "IdentityFile="+self.runner.private_key_file]
self.common_args += ["-o", "User="+self.runner.remote_user]
return self
def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False):
''' run a command on the remote host '''
ssh_cmd = ["ssh", "-tt", "-q"] + self.common_args + [self.host]
if self.runner.sudo and sudoable:
# Rather than detect if sudo wants a password this time, -k makes
# sudo always ask for a password if one is required. The "--"
# tells sudo that this is the end of sudo options and the command
# follows. Passing a quoted compound command to sudo (or sudo -s)
# directly doesn't work, so we shellquote it with pipes.quote()
# and pass the quoted string to the user's shell. We loop reading
# output until we see the randomly-generated sudo prompt set with
# the -p option.
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
prompt = '[sudo via ansible, key=%s] password: ' % randbits
sudocmd = 'sudo -k && sudo -p "%s" -u %s -- "$SHELL" -c %s' % (
prompt, sudo_user, pipes.quote(cmd))
sudo_output = ''
ssh_cmd.append(sudocmd)
p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if self.runner.sudo_pass:
fcntl.fcntl(p.stdout, fcntl.F_SETFL,
fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
while not sudo_output.endswith(prompt):
rfd, wfd, efd = select.select([p.stdout], [],
[p.stdout], self.runner.timeout)
if p.stdout in rfd:
chunk = p.stdout.read()
if not chunk:
raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt')
sudo_output += chunk
else:
stdout = p.communicate()
raise errors.AnsibleError('ssh connection error waiting for sudo password prompt')
p.stdin.write(self.runner.sudo_pass + '\n')
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
else:
ssh_cmd.append(cmd)
p = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# We can't use p.communicate here because the ControlMaster may have stdout open as well
p.stdin.close()
stdout = ''
while p.poll() is None:
rfd, wfd, efd = select.select([p.stdout], [], [p.stdout], 1)
if p.stdout in rfd:
stdout += os.read(p.stdout.fileno(), 1024)
# older versions of ssh generate this error which we ignore
stdout=stdout.replace("tcgetattr: Invalid argument\n", "")
# suppress Ubuntu 10.04/12.04 error on -tt option
stdout=stdout.replace("tcgetattr: Inappropriate ioctl for device\n","")
if p.returncode != 0 and stdout.find('Bad configuration option: ControlPersist') != -1:
raise errors.AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" before running again')
return ('', stdout, '')
def put_file(self, in_path, out_path):
''' transfer a file from local to remote '''
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
sftp_cmd = ["sftp"] + self.common_args + [self.host]
p = subprocess.Popen(sftp_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate("put %s %s\n" % (in_path, out_path))
if p.returncode != 0:
raise errors.AnsibleError("failed to transfer file to %s:\n%s\n%s" % (out_path, stdout, stderr))
def fetch_file(self, in_path, out_path):
''' fetch a file from remote to local '''
sftp_cmd = ["sftp"] + self.common_args + [self.host]
p = subprocess.Popen(sftp_cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate("get %s %s\n" % (in_path, out_path))
if p.returncode != 0:
raise errors.AnsibleError("failed to transfer file from %s:\n%s\n%s" % (in_path, stdout, stderr))
def close(self):
''' not applicable since we're executing openssh binaries '''
pass

View File

@ -0,0 +1,101 @@
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import time
from ansible import errors
class AsyncPoller(object):
""" Manage asynchronous jobs. """
def __init__(self, results, runner):
self.runner = runner
self.results = { 'contacted': {}, 'dark': {}}
self.hosts_to_poll = []
self.completed = False
# Get job id and which hosts to poll again in the future
jid = None
for (host, res) in results['contacted'].iteritems():
if res.get('started', False):
self.hosts_to_poll.append(host)
jid = res.get('ansible_job_id', None)
else:
self.results['contacted'][host] = res
for (host, res) in results['dark'].iteritems():
self.results['dark'][host] = res
if jid is None:
raise errors.AnsibleError("unexpected error: unable to determine jid")
if len(self.hosts_to_poll)==0:
raise errors.AnsibleErrot("unexpected error: no hosts to poll")
self.jid = jid
def poll(self):
""" Poll the job status.
Returns the changes in this iteration."""
self.runner.module_name = 'async_status'
self.runner.module_args = "jid=%s" % self.jid
self.runner.pattern = "*"
self.runner.background = 0
self.runner.inventory.restrict_to(self.hosts_to_poll)
results = self.runner.run()
self.runner.inventory.lift_restriction()
hosts = []
poll_results = { 'contacted': {}, 'dark': {}, 'polled': {}}
for (host, res) in results['contacted'].iteritems():
if res.get('started',False):
hosts.append(host)
poll_results['polled'][host] = res
else:
self.results['contacted'][host] = res
poll_results['contacted'][host] = res
if 'failed' in res:
self.runner.callbacks.on_async_failed(host, res, self.jid)
else:
self.runner.callbacks.on_async_ok(host, res, self.jid)
for (host, res) in results['dark'].iteritems():
self.results['dark'][host] = res
poll_results['dark'][host] = res
self.runner.callbacks.on_async_failed(host, res, self.jid)
self.hosts_to_poll = hosts
if len(hosts)==0:
self.completed = True
return poll_results
def wait(self, seconds, poll_interval):
""" Wait a certain time for job completion, check status every poll_interval. """
clock = seconds - poll_interval
while (clock >= 0 and not self.completed):
time.sleep(poll_interval)
poll_results = self.poll()
for (host, res) in poll_results['polled'].iteritems():
if res.get('started'):
self.runner.callbacks.on_async_poll(host, res, self.jid, clock)
clock = clock - poll_interval
return self.results

364
lib/ansible/utils.py Executable file → Normal file
View File

@ -15,88 +15,57 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
###############################################################
import sys
import os
import shlex
import re
import codecs
import jinja2
import yaml
import optparse
import operator
from ansible import errors
from ansible import color
from ansible import __version__
import ansible.constants as C
try:
import json
except ImportError:
import simplejson as json
try:
from hashlib import md5 as _md5
except ImportError:
from md5 import md5 as _md5
###############################################################
# UTILITY FUNCTIONS FOR COMMAND LINE TOOLS
###############################################################
def err(msg):
''' print an error message to stderr '''
print >> sys.stderr, msg
def exit(msg, rc=1):
''' quit with an error to stdout and a failure code '''
err(msg)
sys.exit(rc)
def bigjson(result):
''' format JSON output (uncompressed) '''
return json.dumps(result, sort_keys=True, indent=4)
def jsonify(result, format=False):
''' format JSON output (uncompressed or uncompressed) '''
def smjson(result):
''' format JSON output (compressed) '''
return json.dumps(result, sort_keys=True)
def task_start_msg(name, conditional):
if conditional:
return "NOTIFIED: [%s] **********\n" % name
result2 = result.copy()
if format:
return json.dumps(result2, sort_keys=True, indent=4)
else:
return "TASK: [%s] *********\n" % name
def regular_generic_msg(hostname, result, oneline, caption):
''' output on the result of a module run that is not command '''
if not oneline:
return "%s | %s >>\n%s" % (hostname, caption, bigjson(result))
else:
return "%s | %s >> %s" % (hostname, caption, smjson(result))
def regular_success_msg(hostname, result, oneline):
''' output the result of a successful module run '''
return regular_generic_msg(hostname, result, oneline, 'success')
def regular_failure_msg(hostname, result, oneline):
''' output the result of a failed module run '''
return regular_generic_msg(hostname, result, oneline, 'FAILED')
def command_generic_msg(hostname, result, oneline, caption):
''' output the result of a command run '''
rc = result.get('rc', '0')
stdout = result.get('stdout','')
stderr = result.get('stderr', '')
msg = result.get('msg', '')
if not oneline:
buf = "%s | %s | rc=%s >>\n" % (hostname, caption, result.get('rc',0))
if stdout:
buf += stdout
if stderr:
buf += stderr
if msg:
buf += msg
return buf
else:
if stderr:
return "%s | %s | rc=%s | (stdout) %s (stderr) %s" % (hostname, caption, rc, stdout, stderr)
else:
return "%s | %s | rc=%s | (stdout) %s" % (hostname, caption, rc, stdout)
def command_success_msg(hostname, result, oneline):
''' output from a successful command run '''
return command_generic_msg(hostname, result, oneline, 'success')
def command_failure_msg(hostname, result, oneline):
''' output from a failed command run '''
return command_generic_msg(hostname, result, oneline, 'FAILED')
return json.dumps(result2, sort_keys=True)
def write_tree_file(tree, hostname, buf):
''' write something into treedir/hostname '''
# TODO: might be nice to append playbook runs per host in a similar way
# in which case, we'd want append mode.
path = os.path.join(tree, hostname)
@ -106,55 +75,12 @@ def write_tree_file(tree, hostname, buf):
def is_failed(result):
''' is a given JSON result a failed result? '''
failed = False
rc = 0
if type(result) == dict:
failed = result.get('failed', 0)
rc = result.get('rc', 0)
if rc != 0:
return True
return failed
def host_report_msg(hostname, module_name, result, oneline):
''' summarize the JSON results for a particular host '''
buf = ''
failed = is_failed(result)
if module_name == 'command':
if not failed:
buf = command_success_msg(hostname, result, oneline)
else:
buf = command_failure_msg(hostname, result, oneline)
else:
if not failed:
buf = regular_success_msg(hostname, result, oneline)
else:
buf = regular_failure_msg(hostname, result, oneline)
return buf
def dark_hosts_msg(results):
''' summarize the results of all uncontactable hosts '''
buf = ''
if len(results['dark'].keys()) > 0:
buf += "*** Hosts which could not be contacted or did not respond: ***"
for hostname in results['dark'].keys():
buf += "%s:\n%s\n" % (hostname, results['dark'][hostname])
buf += "\n"
return buf
def has_dark_hosts(results):
''' are there any uncontactable hosts? '''
return len(results['dark'].keys()) > 0
def contacted_hosts(results):
''' what are the contactable hosts? '''
return sorted(results['contacted'])
def contacted_host_result(results, hostname):
''' what are the results for a given host? '''
return results['contacted'][hostname]
return ((result.get('rc', 0) != 0) or (result.get('failed', False) in [ True, 'True', 'true']))
def prepare_writeable_dir(tree):
''' make sure a directory exists and is writeable '''
if tree != '/':
tree = os.path.realpath(os.path.expanduser(tree))
if not os.path.exists(tree):
@ -166,6 +92,8 @@ def prepare_writeable_dir(tree):
exit("Cannot write to path %s" % tree)
def path_dwim(basedir, given):
''' make relative paths work like folks expect '''
if given.startswith("/"):
return given
elif given.startswith("~/"):
@ -173,4 +101,236 @@ def path_dwim(basedir, given):
else:
return os.path.join(basedir, given)
def json_loads(data):
''' parse a JSON string and return a data structure '''
return json.loads(data)
def parse_json(data):
''' this version for module return data only '''
try:
return json.loads(data)
except:
# not JSON, but try "Baby JSON" which allows many of our modules to not
# require JSON and makes writing modules in bash much simpler
results = {}
try:
tokens = shlex.split(data)
except:
print "failed to parse json: "+ data
raise
for t in tokens:
if t.find("=") == -1:
raise errors.AnsibleError("failed to parse: %s" % data)
(key,value) = t.split("=", 1)
if key == 'changed' or 'failed':
if value.lower() in [ 'true', '1' ]:
value = True
elif value.lower() in [ 'false', '0' ]:
value = False
if key == 'rc':
value = int(value)
results[key] = value
if len(results.keys()) == 0:
return { "failed" : True, "parsed" : False, "msg" : data }
return results
_LISTRE = re.compile(r"(\w+)\[(\d+)\]")
def _varLookup(name, vars):
''' find the contents of a possibly complex variable in vars. '''
path = name.split('.')
space = vars
for part in path:
if part in space:
space = space[part]
elif "[" in part:
m = _LISTRE.search(part)
if not m:
return
try:
space = space[m.group(1)][int(m.group(2))]
except (KeyError, IndexError):
return
else:
return
return space
_KEYCRE = re.compile(r"\$(?P<complex>\{){0,1}((?(complex)[\w\.\[\]]+|\w+))(?(complex)\})")
def varLookup(varname, vars):
''' helper function used by varReplace '''
m = _KEYCRE.search(varname)
if not m:
return None
return _varLookup(m.group(2), vars)
def varReplace(raw, vars):
''' Perform variable replacement of $variables in string raw using vars dictionary '''
# this code originally from yum
done = [] # Completed chunks to return
while raw:
m = _KEYCRE.search(raw)
if not m:
done.append(raw)
break
# Determine replacement value (if unknown variable then preserve
# original)
varname = m.group(2)
replacement = unicode(_varLookup(varname, vars) or m.group())
start, end = m.span()
done.append(raw[:start]) # Keep stuff leading up to token
done.append(replacement) # Append replacement value
raw = raw[end:] # Continue with remainder of string
return ''.join(done)
def template(text, vars):
''' run a text buffer through the templating engine until it no longer changes '''
prev_text = ''
depth = 0
while prev_text != text:
depth = depth + 1
if (depth > 20):
raise errors.AnsibleError("template recursion depth exceeded")
prev_text = text
text = varReplace(unicode(text), vars)
return text
def template_from_file(basedir, path, vars):
''' run a file through the templating engine '''
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(basedir), trim_blocks=False)
data = codecs.open(path_dwim(basedir, path), encoding="utf8").read()
t = environment.from_string(data)
vars = vars.copy()
res = t.render(vars)
if data.endswith('\n') and not res.endswith('\n'):
res = res + '\n'
return template(res, vars)
def parse_yaml(data):
''' convert a yaml string to a data structure '''
return yaml.load(data)
def parse_yaml_from_file(path):
''' convert a yaml file to a data structure '''
try:
data = file(path).read()
except IOError:
raise errors.AnsibleError("file not found: %s" % path)
return parse_yaml(data)
def parse_kv(args):
''' convert a string of key/value items to a dict '''
options = {}
if args is not None:
# attempting to split a unicode here does bad things
vargs = shlex.split(str(args), posix=True)
for x in vargs:
if x.find("=") != -1:
k, v = x.split("=",1)
options[k]=v
return options
def md5(filename):
''' Return MD5 hex digest of local file, or None if file is not present. '''
if not os.path.exists(filename):
return None
digest = _md5()
blocksize = 64 * 1024
infile = open(filename, 'rb')
block = infile.read(blocksize)
while block:
digest.update(block)
block = infile.read(blocksize)
infile.close()
return digest.hexdigest()
def default(value, function):
''' syntactic sugar around lazy evaluation of defaults '''
if value is None:
return function()
return value
####################################################################
# option handling code for /usr/bin/ansible and ansible-playbook
# below this line
class SortedOptParser(optparse.OptionParser):
'''Optparser which sorts the options by opt before outputting --help'''
def format_help(self, formatter=None):
self.option_list.sort(key=operator.methodcaller('get_opt_string'))
return optparse.OptionParser.format_help(self, formatter=None)
def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, async_opts=False, connect_opts=False):
''' create an options parser for any ansible script '''
parser = SortedOptParser(usage, version="%prog " + __version__)
parser.add_option('-v','--verbose', default=False, action="store_true",
help='verbose mode')
parser.add_option('-f','--forks', dest='forks', default=constants.DEFAULT_FORKS, type='int',
help="specify number of parallel processes to use (default=%s)" % constants.DEFAULT_FORKS)
parser.add_option('-i', '--inventory-file', dest='inventory',
help="specify inventory host file (default=%s)" % constants.DEFAULT_HOST_LIST,
default=constants.DEFAULT_HOST_LIST)
parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true',
help='ask for SSH password')
parser.add_option('--private-key', default=None, dest='private_key_file',
help='use this file to authenticate the connection')
parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true',
help='ask for sudo password')
parser.add_option('-M', '--module-path', dest='module_path',
help="specify path(s) to module library (default=%s)" % constants.DEFAULT_MODULE_PATH,
default=constants.DEFAULT_MODULE_PATH)
parser.add_option('-T', '--timeout', default=constants.DEFAULT_TIMEOUT, type='int',
dest='timeout',
help="override the SSH timeout in seconds (default=%s)" % constants.DEFAULT_TIMEOUT)
if output_opts:
parser.add_option('-o', '--one-line', dest='one_line', action='store_true',
help='condense output')
parser.add_option('-t', '--tree', dest='tree', default=None,
help='log output to this directory')
if runas_opts:
parser.add_option("-s", "--sudo", default=False, action="store_true",
dest='sudo', help="run operations with sudo (nopasswd)")
parser.add_option('-U', '--sudo-user', dest='sudo_user', help='desired sudo user (default=root)',
default=None) # Can't default to root because we need to detect when this option was given
parser.add_option('-u', '--user', default=constants.DEFAULT_REMOTE_USER,
dest='remote_user',
help='connect as this user (default=%s)' % constants.DEFAULT_REMOTE_USER)
if connect_opts:
parser.add_option('-c', '--connection', dest='connection',
choices=C.DEFAULT_TRANSPORT_OPTS,
default=C.DEFAULT_TRANSPORT,
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
if async_opts:
parser.add_option('-P', '--poll', default=constants.DEFAULT_POLL_INTERVAL, type='int',
dest='poll_interval',
help="set the poll interval if using -B (default=%s)" % constants.DEFAULT_POLL_INTERVAL)
parser.add_option('-B', '--background', dest='seconds', type='int', default=0,
help='run asynchronously, failing after X seconds (default=N/A)')
return parser

184
library/apt Executable file
View File

@ -0,0 +1,184 @@
#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
# (c) 2012, Flowroute LLC
# Written by Matthew Williams <matthew@flowroute.com>
# Based on yum module written by Seth Vidal <skvidal at fedoraproject.org>
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
#
import traceback
# added to stave off future warnings about apt api
import warnings;
warnings.filterwarnings('ignore', "apt API not stable yet", FutureWarning)
# APT related constants
APT_PATH = "/usr/bin/apt-get"
APT = "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical %s" % APT_PATH
def run_apt(command):
try:
cmd = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
except (OSError, IOError), e:
rc = 1
err = str(e)
out = ''
except:
rc = 1
err = traceback.format_exc()
out = ''
else:
rc = cmd.returncode
return rc, out, err
def package_split(pkgspec):
parts = pkgspec.split('=')
if len(parts) > 1:
return parts[0], parts[1]
else:
return parts[0], None
def package_status(m, pkgname, version, cache):
try:
pkg = cache[pkgname]
except KeyError:
m.fail_json(msg="No package matching '%s' is available" % pkgname)
if version:
try :
return pkg.is_installed and pkg.installed.version == version, False
except AttributeError:
#assume older version of python-apt is installed
return pkg.isInstalled and pkg.installedVersion == version, False
else:
try :
return pkg.is_installed, pkg.is_upgradable
except AttributeError:
#assume older version of python-apt is installed
return pkg.isInstalled, pkg.isUpgradable
def install(m, pkgspec, cache, upgrade=False, default_release=None, install_recommends=True, force=False):
packages = ""
for package in pkgspec:
name, version = package_split(package)
installed, upgradable = package_status(m, name, version, cache)
if not installed or (upgrade and upgradable):
packages += "'%s' " % package
if len(packages) != 0:
if force:
force_yes = '--force-yes'
else:
force_yes = ''
cmd = "%s --option Dpkg::Options::=--force-confold -q -y %s install %s" % (APT, force_yes,packages)
if default_release:
cmd += " -t '%s'" % (default_release,)
if not install_recommends:
cmd += " --no-install-recommends"
rc, out, err = run_apt(cmd)
if rc:
m.fail_json(msg="'apt-get install %s' failed: %s" % (packages, err))
else:
m.exit_json(changed=True)
else:
m.exit_json(changed=False)
def remove(m, pkgspec, cache, purge=False):
packages = ""
for package in pkgspec:
name, version = package_split(package)
installed, upgradable = package_status(m, name, version, cache)
if installed:
packages += "'%s' " % package
if len(packages) == 0:
m.exit_json(changed=False)
else:
purge = '--purge' if purge else ''
cmd = "%s -q -y %s remove %s" % (APT, purge,packages)
rc, out, err = run_apt(cmd)
if rc:
m.fail_json(msg="'apt-get remove %s' failed: %s" % (packages, err))
m.exit_json(changed=True)
def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(default='installed', choices=['installed', 'latest', 'removed']),
update_cache = dict(default='no', choices=['yes', 'no'], aliases=['update-cache']),
purge = dict(default='no', choices=['yes', 'no']),
package = dict(default=None, aliases=['pkg', 'name']),
default_release = dict(default=None, aliases=['default-release']),
install_recommends = dict(default='yes', aliases=['install-recommends'], choices=['yes', 'no']),
force = dict(default='no', choices=['yes', 'no'])
)
)
try:
import apt, apt_pkg
except:
module.fail_json("Could not import python modules: apt, apt_pkg. Please install python-apt package.")
if not os.path.exists(APT_PATH):
module.fail_json(msg="Cannot find apt-get")
p = module.params
if p['package'] is None and p['update_cache'] != 'yes':
module.fail_json(msg='pkg=name and/or update_cache=yes is required')
install_recommends = module.boolean(p['install_recommends'])
cache = apt.Cache()
if p['default_release']:
apt_pkg.config['APT::Default-Release'] = p['default_release']
# reopen cache w/ modified config
cache.open(progress=None)
if module.boolean(p['update_cache']):
cache.update()
cache.open(progress=None)
if p['package'] == None:
module.exit_json(changed=False)
force_yes = module.boolean(p['force'])
packages = p['package'].split(',')
latest = p['state'] == 'latest'
for package in packages:
if package.count('=') > 1:
module.fail_json(msg="invalid package spec: %s" % package)
if latest and '=' in package:
module.fail_json(msg='version number inconsistent with state=latest: %s' % package)
if p['state'] == 'latest':
install(module, packages, cache, upgrade=True,
default_release=p['default_release'],
install_recommends=install_recommends,
force=force_yes)
elif p['state'] == 'installed':
install(module, packages, cache, default_release=p['default_release'],
install_recommends=install_recommends,force=force_yes)
elif p['state'] == 'removed':
remove(module, packages, cache, purge = module.boolean(p['purge']))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

87
library/assemble Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import os.path
import shutil
import tempfile
# ===========================================
# Support methods
def assemble_from_fragments(path):
''' assemble a file from a directory of fragments '''
assembled = []
for f in sorted(os.listdir(path)):
fragment = "%s/%s" % (path, f)
if os.path.isfile(fragment):
assembled.append(file(fragment).read())
return "".join(assembled)
def write_temp_file(data):
fd, path = tempfile.mkstemp()
os.write(fd, data)
os.close(fd)
return path
# ==============================================================
# main
def main():
module = AnsibleModule(
argument_spec = dict(
src = dict(required=True),
dest = dict(required=True),
)
)
changed=False
pathmd5 = None
destmd5 = None
src = os.path.expanduser(module.params['src'])
dest = os.path.expanduser(module.params['dest'])
if not os.path.exists(src):
module.fail_json(msg="Source (%s) does not exist" % src)
if not os.path.isdir(src):
module.fail_json(msg="Source (%s) is not a directory" % src)
path = write_temp_file(assemble_from_fragments(src))
pathmd5 = module.md5(path)
if os.path.exists(dest):
destmd5 = module.md5(dest)
if pathmd5 != destmd5:
shutil.copy(path, dest)
changed = True
# Mission complete
module.exit_json(src=src, dest=dest, md5sum=destmd5,
changed=changed, msg="OK",
daisychain="file", daisychain_args=module.params)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
#
@ -18,81 +19,50 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
try:
import json
except ImportError:
import simplejson as json
import shlex
import os
import subprocess
import sys
import datetime
import traceback
# ===========================================
# convert arguments of form a=b c=d
# to a dictionary
# FIXME: make more idiomatic
def main():
args = " ".join(sys.argv[1:])
items = shlex.split(args)
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
module = AnsibleModule(argument_spec=dict(
jid=dict(required=True),
mode=dict(default='status', choices=['status','cleanup']),
))
mode = params.get('mode', 'status')
jid = params.get('jid', None)
mode = module.params['mode']
jid = module.params['jid']
# ===========================================
# setup logging directory
logdir = os.path.expanduser("~/.ansible_async")
log_path = os.path.join(logdir, jid)
if jid is None:
print json.dumps({
"failed" : True,
"msg" : "jid=INTEGER is required"
})
sys.exit(1)
if not os.path.exists(log_path):
module.fail_json(msg="could not find job", ansible_job_id=jid)
if mode == 'cleanup':
os.unlink(log_path)
module.exit_json(ansible_job_id=jid, erased=log_path)
# setup logging directory
logdir = os.path.expanduser("~/.ansible_async")
log_path = os.path.join(logdir, jid)
# NOT in cleanup mode, assume regular status mode
# no remote kill mode currently exists, but probably should
# consider log_path + ".pid" file and also unlink that above
if not os.path.exists(log_path):
print json.dumps({
"failed" : 1,
"msg" : "could not find job",
"ansible_job_id" : jid
})
sys.exit(1)
if mode == 'cleanup':
os.unlink(log_path)
print json.dumps({
"ansible_job_id" : jid,
"erased" : log_path
})
sys.exit(0)
# NOT in cleanup mode, assume regular status mode
# no remote kill mode currently exists, but probably should
# consider log_path + ".pid" file and also unlink that above
data = file(log_path).read()
try:
data = json.loads(data)
except:
print json.dumps({
"failed" : True,
"msg" : "Could not parse job output"
})
sys.exit(1)
if not data.has_key("started"):
data['finished'] = 1
data['ansible_job_id'] = jid
print json.dumps(data)
sys.exit(0)
data = file(log_path).read()
try:
data = json.loads(data)
except Exception, e:
if data == '':
# file not written yet? That means it is running
module.exit_json(results_file=log_path, ansible_job_id=jid, started=1)
else:
module_fail_json(ansible_job_id=jid, results_file=log_path,
msg="Could not parse job output: %s" % data)
if not data.has_key("started"):
data['finished'] = 1
data['ansible_job_id'] = jid
module.exit_json(**data)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
#
@ -30,20 +31,55 @@ import datetime
import traceback
import signal
import time
import syslog
def daemonize_self():
# daemonizing code: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
# logger.info("cobblerd started")
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)
# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(022)
# do second fork
try:
pid = os.fork()
if pid > 0:
# print "Daemon PID %d" % pid
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)
dev_null = file('/dev/null','rw')
os.dup2(dev_null.fileno(), sys.stdin.fileno())
os.dup2(dev_null.fileno(), sys.stdout.fileno())
os.dup2(dev_null.fileno(), sys.stderr.fileno())
if len(sys.argv) < 3:
print json.dumps({
"failed" : True,
"msg" : "usage: async_wrapper <jid> <module_script> <time_limit> <args>. Humans, do not call directly!"
"msg" : "usage: async_wrapper <jid> <time_limit> <modulescript> <argsfile>. Humans, do not call directly!"
})
sys.exit(1)
jid = sys.argv[1]
time_limit = sys.argv[2]
wrapped_module = sys.argv[3]
args = sys.argv[4:]
argsfile = sys.argv[4]
cmd = "%s %s" % (wrapped_module, argsfile)
cmd = "%s %s" % (wrapped_module, " ".join(args))
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Invoked with %s' % " ".join(sys.argv[1:]))
# setup logging directory
logdir = os.path.expanduser("~/.ansible_async")
@ -60,65 +96,105 @@ if not os.path.exists(logdir):
def _run_command(wrapped_cmd, jid, log_path):
logfile = open(log_path, "w+")
logfile = open(log_path, "w")
logfile.write(json.dumps({ "started" : 1, "ansible_job_id" : jid }))
logfile.close()
logfile = open(log_path, "w")
result = {}
outdata = ''
try:
cmd = shlex.split(wrapped_cmd)
script = subprocess.Popen(cmd, shell=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = script.communicate()
result = json.loads(out)
stdin=None, stdout=logfile, stderr=logfile)
script.communicate()
outdata = file(log_path).read()
result = json.loads(outdata)
except (OSError, IOError), e:
result = {
"failed": 1,
"cmd" : wrapped_cmd,
"msg": str(e),
}
result['ansible_job_id'] = jid
logfile.write(json.dumps(result))
except:
result = {
"failed" : 1,
"cmd" : wrapped_cmd,
"data" : outdata, # temporary debug only
"msg" : traceback.format_exc()
}
result['ansible_job_id'] = jid
logfile = open(log_path, "w+")
logfile.write(json.dumps(result))
result['ansible_job_id'] = jid
logfile.write(json.dumps(result))
logfile.close()
# immediately exit this process, leaving an orphaned process
# running which immediately forks a supervisory timing process
pid = os.fork()
if pid == 0:
# "RETURNING SUCCESS IN UNO"
print json.dumps({ "started" : 1, "ansible_job_id" : jid })
sys.exit(0)
else:
# "DAEMONIZED DOS"
sub_pid = os.fork()
if sub_pid == 0:
# "RUNNING IN KID A"
_run_command(cmd, jid, log_path)
sys.exit(0)
else:
# "WATCHING IN KID B"
remaining = int(time_limit)
if os.path.exists("/proc/%s" % sub_pid):
# "STILL RUNNING"
time.sleep(1)
remaining = remaining - 1
else:
# "DONE IN KID B"
sys.exit(0)
if remaining == 0:
# "SLAYING IN KID B"
os.kill(sub_pid, signals.SIGKILL)
sys.exit(1)
#import logging
#import logging.handlers
sys.exit(0)
#logger = logging.getLogger("ansible_async")
#logger.setLevel(logging.WARNING)
#logger.addHandler( logging.handlers.SysLogHandler("/dev/log") )
def debug(msg):
#logger.warning(msg)
pass
try:
pid = os.fork()
if pid:
# Notify the overlord that the async process started
# we need to not return immmediately such that the launched command has an attempt
# to initialize PRIOR to ansible trying to clean up the launch directory (and argsfile)
# this probably could be done with some IPC later. Modules should always read
# the argsfile at the very first start of their execution anyway
time.sleep(1)
debug("Return async_wrapper task started.")
print json.dumps({ "started" : 1, "ansible_job_id" : jid, "results_file" : log_path })
sys.stdout.flush()
sys.exit(0)
else:
# The actual wrapper process
# Daemonize, so we keep on running
daemonize_self()
# we are now daemonized, create a supervisory process
debug("Starting module and watcher")
sub_pid = os.fork()
if sub_pid:
# the parent stops the process after the time limit
remaining = int(time_limit)
# set the child process group id to kill all children
os.setpgid(sub_pid, sub_pid)
debug("Start watching %s (%s)"%(sub_pid, remaining))
time.sleep(5)
while os.waitpid(sub_pid, os.WNOHANG) == (0, 0):
debug("%s still running (%s)"%(sub_pid, remaining))
time.sleep(5)
remaining = remaining - 5
if remaining == 0:
debug("Now killing %s"%(sub_pid))
os.killpg(sub_pid, signal.SIGKILL)
debug("Sent kill to group %s"%sub_pid)
time.sleep(1)
sys.exit(0)
debug("Done in kid B.")
os._exit(0)
else:
# the child process runs the actual module
debug("Start module (%s)"%os.getpid())
_run_command(cmd, jid, log_path)
debug("Module complete (%s)"%os.getpid())
sys.exit(0)
except Exception, err:
debug("error: %s"%(err))
raise err

136
library/authorized_key Executable file
View File

@ -0,0 +1,136 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Ansible module to add authorized_keys for ssh logins.
(c) 2012, Brad Olson <brado@movedbylight.com>
This file is part of Ansible
Ansible is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ansible is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
"""
# Makes sure the public key line is present or absent in the user's .ssh/authorized_keys.
#
# Arguments
# =========
# user = username
# key = line to add to authorized_keys for user
# state = absent|present (default: present)
#
# see example in examples/playbooks
import sys
import os
import pwd
import os.path
def keyfile(user, write=False):
"""
Calculate name of authorized keys file, optionally creating the
directories and file, properly setting permissions.
:param str user: name of user in passwd file
:param bool write: if True, write changes to authorized_keys file (creating directories if needed)
:return: full path string to authorized_keys for user
"""
user_entry = pwd.getpwnam(user)
homedir = user_entry.pw_dir
sshdir = os.path.join(homedir, ".ssh")
keysfile = os.path.join(sshdir, "authorized_keys")
if not write:
return keysfile
uid = user_entry.pw_uid
gid = user_entry.pw_gid
if not os.path.exists(sshdir):
os.mkdir(sshdir, 0700)
os.chown(sshdir, uid, gid)
os.chmod(sshdir, 0700)
if not os.path.exists( keysfile):
try:
f = open(keysfile, "w") #touches file so we can set ownership and perms
finally:
f.close()
os.chown(keysfile, uid, gid)
os.chmod(keysfile, 0600)
return keysfile
def readkeys(filename):
if not os.path.isfile(filename):
return []
f = open(filename)
keys = [line.rstrip() for line in f.readlines()]
f.close()
return keys
def writekeys( filename, keys):
f = open(filename,"w")
f.writelines( (key + "\n" for key in keys) )
f.close()
def enforce_state(module, params):
"""
Add or remove key.
"""
user = params["user"]
key = params["key"]
state = params.get("state", "present")
# check current state -- just get the filename, don't create file
params["keyfile"] = keyfile(user, write=False)
keys = readkeys(params["keyfile"])
present = key in keys
# handle idempotent state=present
if state=="present":
if present:
module.exit_json(changed=False)
keys.append(key)
writekeys(keyfile(user,write=True), keys)
elif state=="absent":
if not present:
module.exit_json(changed=False)
keys.remove(key)
writekeys(keyfile(user,write=True), keys)
params['changed'] = True
return params
def main():
module = AnsibleModule(
argument_spec = dict(
user = dict(required=True),
key = dict(required=True),
state = dict(default='present', choices=['absent','present'])
)
)
params = module.params
results = enforce_state(module, module.params)
module.exit_json(**results)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
#
@ -16,60 +17,109 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
try:
import json
except ImportError:
import simplejson as json
import subprocess
import sys
import datetime
import traceback
import shlex
import os
if len(sys.argv) == 1:
print json.dumps({
"failed" : True,
"msg" : "the command module requires arguments (-a)"
})
sys.exit(1)
def main():
args = sys.argv[1:]
startd = datetime.datetime.now()
# the command module is the one ansible module that does not take key=value args
# hence don't copy this one if you are looking to build others!
module = CommandModule(argument_spec=dict())
try:
cmd = subprocess.Popen(args, shell=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
except (OSError, IOError), e:
print json.dumps({
"failed": 1,
"msg": str(e),
})
sys.exit(1)
except:
print json.dumps({
"failed" : 1,
"msg" : traceback.format_exc()
})
sys.exit(1)
shell = module.params['shell']
chdir = module.params['chdir']
args = module.params['args']
endd = datetime.datetime.now()
delta = endd - startd
if chdir:
os.chdir(chdir)
if out is None:
out = ''
if err is None:
err = ''
if not shell:
args = shlex.split(args)
startd = datetime.datetime.now()
result = {
"stdout" : out.strip(),
"stderr" : err.strip(),
"rc" : cmd.returncode,
"start" : str(startd),
"end" : str(endd),
"delta" : str(delta),
}
try:
cmd = subprocess.Popen(args, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
except (OSError, IOError), e:
module.fail_json(cmd=args, msg=str(e))
except:
module.fail_json(msg=traceback.format_exc())
print json.dumps(result)
endd = datetime.datetime.now()
delta = endd - startd
if out is None:
out = ''
if err is None:
err = ''
module.exit_json(
cmd = args,
stdout = out.strip(),
stderr = err.strip(),
rc = cmd.returncode,
start = str(startd),
end = str(endd),
delta = str(delta),
changed = True
)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
# only the command module should ever need to do this
# everything else should be simple key=value
class CommandModule(AnsibleModule):
def _handle_aliases(self):
pass
def _check_invalid_arguments(self):
pass
def _load_params(self):
''' read the input and return a dictionary and the arguments string '''
args = MODULE_ARGS
items = shlex.split(args)
params = {}
params['chdir'] = None
params['shell'] = False
if args.find("#USE_SHELL") != -1:
args = args.replace("#USE_SHELL", "")
params['shell'] = True
check_args = shlex.split(args)
for x in check_args:
if x.startswith("creates="):
# do not run the command if the line contains creates=filename
# and the filename already exists. This allows idempotence
# of command executions.
(k,v) = x.split("=",1)
if os.path.exists(v):
self.exit_json(
cmd=args,
stdout="skipped, since %s exists" % v,
skipped=True,
changed=False,
stderr=False,
rc=0
)
args = args.replace(x,'')
elif x.startswith("chdir="):
(k,v) = x.split("=", 1)
if not (os.path.exists(v) and os.path.isdir(v)):
self.fail_json(msg="cannot change to directory '%s': path does not exist" % v)
elif v[0] != '/':
self.fail_json(msg="the path for 'chdir' argument must be fully qualified")
params['chdir'] = v
args = args.replace(x, '')
params['args'] = args
return (params, args)
main()

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
@ -16,57 +17,58 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import os
import shlex
import shutil
try:
import json
except ImportError:
import simplejson as json
def main():
# ===========================================
# convert arguments of form a=b c=d
# to a dictionary
# FIXME: make more idiomatic
module = AnsibleModule(
# not checking because of daisy chain to file module
check_invalid_arguments = False,
argument_spec = dict(
src=dict(required=True),
dest=dict(required=True)
)
)
args = " ".join(sys.argv[1:])
items = shlex.split(args)
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
src = os.path.expanduser(module.params['src'])
dest = os.path.expanduser(module.params['dest'])
src = params['src']
dest = params['dest']
if not os.path.exists(src):
module.fail_json(msg="Source %s failed to transfer" % (src))
if not os.access(src, os.R_OK):
module.fail_json(msg="Source %s not readable" % (src))
# raise an error if there is no src file
if not os.path.exists(src):
print json.dumps({
"failed" : 1,
"msg" : "Source %s failed to transfer" % src
})
sys.exit(1)
md5sum_src = module.md5(src)
md5sum_dest = None
md5sum = None
changed = False
if os.path.exists(dest):
md5sum = os.popen("md5sum %s" % dest).read().split()[0]
if os.path.exists(dest):
if not os.access(dest, os.W_OK):
module.fail_json(msg="Destination %s not writable" % (dest))
if not os.access(dest, os.R_OK):
module.fail_json(msg="Destination %s not readable" % (dest))
# Allow dest to be directory without compromising md5 check
if (os.path.isdir(dest)):
module.fail_json(msg="Destination %s cannot be a directory" % (dest))
md5sum_dest = module.md5(dest)
else:
if not os.access(os.path.dirname(dest), os.W_OK):
module.fail_json(msg="Destination %s not writable" % (os.path.dirname(dest)))
os.system("cp %s %s" % (src, dest))
md5sum2 = os.popen("md5sum %s" % dest).read().split()[0]
if md5sum != md5sum2:
changed = True
# mission accomplished
print json.dumps({
"md5sum" : md5sum2,
"changed" : changed
})
if md5sum_src != md5sum_dest:
try:
shutil.copyfile(src, dest)
except shutil.Error:
module.fail_json(msg="failed to copy: %s and %s are the same" % (src, dest))
except IOError:
module.fail_json(msg="failed to copy: %s to %s" % (src, dest))
changed = True
else:
changed = False
module.exit_json(dest=dest, src=src, md5sum=md5sum_src, changed=changed)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/bin/bash
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
@ -22,4 +23,28 @@
# facter
# ruby-json
/usr/bin/facter --json
import subprocess
def get_facter_data():
p = subprocess.Popen(["/usr/bin/env", "facter", "--json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
rc = p.returncode
return rc, out, err
def main():
module = AnsibleModule(
argument_spec = dict()
)
rc, out, err = get_facter_data()
if rc != 0:
module.fail_json(msg=err)
else:
module.exit_json(**json.loads(out))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

1
library/fetch Executable file
View File

@ -0,0 +1 @@
# this is a virtual module that is entirely implemented server side

354
library/file Executable file
View File

@ -0,0 +1,354 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import shutil
import stat
import grp
import pwd
try:
import selinux
HAVE_SELINUX=True
except ImportError:
HAVE_SELINUX=False
def add_path_info(kwargs):
path = kwargs['path']
if os.path.exists(path):
(user, group) = user_and_group(path)
kwargs['owner'] = user
kwargs['group'] = group
st = os.stat(path)
kwargs['mode'] = oct(stat.S_IMODE(st[stat.ST_MODE]))
# secontext not yet supported
if os.path.islink(path):
kwargs['state'] = 'link'
elif os.path.isfile(path):
kwargs['state'] = 'file'
else:
kwargs['state'] = 'directory'
if HAVE_SELINUX and selinux_enabled():
kwargs['secontext'] = ':'.join(selinux_context(path))
else:
kwargs['state'] = 'absent'
return kwargs
def module_exit_json(**kwargs):
add_path_info(kwargs)
module.exit_json(**kwargs)
def module_fail_json(**kwargs):
add_path_info(kwargs)
module.fail_json(**kwargs)
# Detect whether using selinux that is MLS-aware.
# While this means you can set the level/range with
# selinux.lsetfilecon(), it may or may not mean that you
# will get the selevel as part of the context returned
# by selinux.lgetfilecon().
def selinux_mls_enabled():
if not HAVE_SELINUX:
return False
if selinux.is_selinux_mls_enabled() == 1:
return True
else:
return False
def selinux_enabled():
if not HAVE_SELINUX:
return False
if selinux.is_selinux_enabled() == 1:
return True
else:
return False
# Determine whether we need a placeholder for selevel/mls
def selinux_initial_context():
context = [None, None, None]
if selinux_mls_enabled():
context.append(None)
return context
# If selinux fails to find a default, return an array of None
def selinux_default_context(path, mode=0):
context = selinux_initial_context()
if not HAVE_SELINUX or not selinux_enabled():
return context
try:
ret = selinux.matchpathcon(path, mode)
except OSError:
return context
if ret[0] == -1:
return context
context = ret[1].split(':')
return context
def selinux_context(path):
context = selinux_initial_context()
if not HAVE_SELINUX or not selinux_enabled():
return context
try:
ret = selinux.lgetfilecon(path)
except:
module_fail_json(path=path, msg='failed to retrieve selinux context')
if ret[0] == -1:
return context
context = ret[1].split(':')
return context
# ===========================================
# ===========================================
# support functions
def user_and_group(filename):
st = os.stat(filename)
uid = st.st_uid
gid = st.st_gid
try:
user = pwd.getpwuid(uid)[0]
except KeyError:
user = str(uid)
try:
group = grp.getgrgid(gid)[0]
except KeyError:
group = str(gid)
return (user, group)
def set_context_if_different(path, context, changed):
if not HAVE_SELINUX or not selinux_enabled():
return changed
cur_context = selinux_context(path)
new_context = list(cur_context)
# Iterate over the current context instead of the
# argument context, which may have selevel.
for i in range(len(cur_context)):
if context[i] is not None and context[i] != cur_context[i]:
new_context[i] = context[i]
if cur_context != new_context:
try:
rc = selinux.lsetfilecon(path, ':'.join(new_context))
except OSError:
module_fail_json(path=path, msg='invalid selinux context')
if rc != 0:
module_fail_json(path=path, msg='set selinux context failed')
changed = True
return changed
def set_owner_if_different(path, owner, changed):
if owner is None:
return changed
user, group = user_and_group(path)
if owner != user:
rc = os.system("/bin/chown -R %s %s 2>/dev/null" % (owner, path))
if rc != 0:
module_fail_json(path=path, msg='chown failed')
return True
return changed
def set_group_if_different(path, group, changed):
if group is None:
return changed
old_user, old_group = user_and_group(path)
if old_group != group:
rc = os.system("/bin/chgrp -R %s %s" % (group, path))
if rc != 0:
module_fail_json(path=path, msg='chgrp failed')
return True
return changed
def set_mode_if_different(path, mode, changed):
if mode is None:
return changed
try:
# FIXME: support English modes
mode = int(mode, 8)
except Exception, e:
module_fail_json(path=path, msg='mode needs to be something octalish', details=str(e))
st = os.stat(path)
prev_mode = stat.S_IMODE(st[stat.ST_MODE])
if prev_mode != mode:
# FIXME: comparison against string above will cause this to be executed
# every time
try:
os.chmod(path, mode)
except Exception, e:
module_fail_json(path=path, msg='chmod failed', details=str(e))
st = os.stat(path)
new_mode = stat.S_IMODE(st[stat.ST_MODE])
if new_mode != prev_mode:
return True
return changed
def rmtree_error(func, path, exc_info):
module_fail_json(path=path, msg='failed to remove directory')
def main():
global module
module = AnsibleModule(
check_invalid_arguments = False,
argument_spec = dict(
state = dict(choices=['file','directory','link','absent'], default='file'),
path = dict(aliases=['dest', 'name'], required=True),
src = dict(),
mode = dict(),
owner = dict(),
group = dict(),
seuser = dict(),
serole = dict(),
selevel = dict(),
setype = dict(),
)
)
params = module.params
state = params['state']
path = os.path.expanduser(params['path'])
src = params.get('src', None)
if src:
src = os.path.expanduser(src)
mode = params.get('mode', None)
owner = params.get('owner', None)
group = params.get('group', None)
# selinux related options
seuser = params.get('seuser', None)
serole = params.get('serole', None)
setype = params.get('setype', None)
selevel = params.get('serange', 's0')
secontext = [seuser, serole, setype]
if selinux_mls_enabled():
secontext.append(selevel)
default_secontext = selinux_default_context(path)
for i in range(len(default_secontext)):
if i is not None and secontext[i] == '_default':
secontext[i] = default_secontext[i]
if state == 'link' and (src is None or path is None):
module_fail_json(msg='src and dest are required for "link" state')
elif path is None:
module_fail_json(msg='path is required')
changed = False
prev_state = 'absent'
if os.path.lexists(path):
if os.path.islink(path):
prev_state = 'link'
elif os.path.isfile(path):
prev_state = 'file'
else:
prev_state = 'directory'
if prev_state != 'absent' and state == 'absent':
try:
if prev_state == 'directory':
if os.path.islink(path):
os.unlink(path)
else:
shutil.rmtree(path, ignore_errors=False, onerror=rmtree_error)
else:
os.unlink(path)
except Exception, e:
module_fail_json(path=path, msg=str(e))
module_exit_json(path=path, changed=True)
if prev_state != 'absent' and prev_state != state:
module_fail_json(path=path, msg='refusing to convert between %s and %s' % (prev_state, state))
if prev_state == 'absent' and state == 'absent':
module_exit_json(path=path, changed=False)
if state == 'file':
if prev_state == 'absent':
module_fail_json(path=path, msg='file does not exist, use copy or template module to create')
# set modes owners and context as needed
changed = set_context_if_different(path, secontext, changed)
changed = set_owner_if_different(path, owner, changed)
changed = set_group_if_different(path, group, changed)
changed = set_mode_if_different(path, mode, changed)
module_exit_json(path=path, changed=changed)
elif state == 'directory':
if prev_state == 'absent':
os.makedirs(path)
changed = True
# set modes owners and context as needed
changed = set_context_if_different(path, secontext, changed)
changed = set_owner_if_different(path, owner, changed)
changed = set_group_if_different(path, group, changed)
changed = set_mode_if_different(path, mode, changed)
module_exit_json(path=path, changed=changed)
elif state == 'link':
if os.path.isabs(src):
abs_src = src
else:
abs_src = os.path.join(os.path.dirname(dest), src)
if not os.path.exists(abs_src):
module_fail_json(path=path, src=src, msg='src file does not exist')
if prev_state == 'absent':
os.symlink(src, path)
changed = True
elif prev_state == 'link':
old_src = os.readlink(path)
if not os.path.isabs(old_src):
old_src = os.path.join(os.path.dirname(path), old_src)
if old_src != src:
os.unlink(path)
os.symlink(src, path)
else:
module_fail_json(dest=path, src=src, msg='unexpected position reached')
# set modes owners and context as needed
changed = set_context_if_different(path, secontext, changed)
changed = set_owner_if_different(path, owner, changed)
changed = set_group_if_different(path, group, changed)
changed = set_mode_if_different(path, mode, changed)
module.exit_json(dest=path, src=src, changed=changed)
module_fail_json(path=path, msg='unexpected position reached')
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

194
library/get_url Executable file
View File

@ -0,0 +1,194 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# see examples/playbooks/get_url.yml
import shutil
import datetime
import tempfile
HAS_URLLIB2=True
try:
import urllib2
except ImportError:
HAS_URLLIB2=False
HAS_URLPARSE=True
try:
import urlparse
import socket
except ImportError:
HAS_URLPARSE=False
# ==============================================================
# url handling
def url_filename(url):
fn = os.path.basename(urlparse.urlsplit(url)[2])
if fn == '':
return 'index.html'
return fn
def url_do_get(module, url, dest):
"""
Get url and return request and info
Credits: http://stackoverflow.com/questions/7006574/how-to-download-file-from-ftp
"""
USERAGENT = 'ansible-httpget'
info = dict(url=url)
r = None
actualdest = None
if os.path.isdir(dest):
urlfilename = url_filename(url)
actualdest = "%s/%s" % (dest, urlfilename)
module.params['path'] = actualdest
else:
actualdest = dest
info['daisychain_args'] = module.params
info['daisychain_args']['state'] = 'file'
info['daisychain_args']['dest'] = actualdest
info['actualdest'] = actualdest
request = urllib2.Request(url)
request.add_header('User-agent', USERAGENT)
if os.path.exists(actualdest):
t = datetime.datetime.utcfromtimestamp(os.path.getmtime(actualdest))
tstamp = t.strftime('%a, %d %b %Y %H:%M:%S +0000')
request.add_header('If-Modified-Since', tstamp)
try:
r = urllib2.urlopen(request)
info.update(r.info())
info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), status=200))
except urllib2.HTTPError, e:
# Must not fail_json() here so caller can handle HTTP 304 unmodified
info.update(dict(msg=str(e), status=e.code))
return r, info
except urllib2.URLError, e:
code = getattr(e, 'code', -1)
module.fail_json(msg="Request failed: %s" % str(e), status_code=code)
return r, info
def url_get(module, url, dest):
"""
Download url and store at dest.
If dest is a directory, determine filename from url.
Return (tempfile, info about the request)
"""
req, info = url_do_get(module, url, dest)
# TODO: should really handle 304, but how? src file could exist (and be newer) but empty
if info['status'] == 304:
module.exit_json(url=url, dest=info.get('actualdest', dest), changed=False, msg=info.get('msg', ''))
# create a temporary file and copy content to do md5-based replacement
if info['status'] != 200:
module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url)
actualdest = info['actualdest']
fd, tempname = tempfile.mkstemp()
f = os.fdopen(fd, 'wb')
try:
shutil.copyfileobj(req, f)
except Exception, err:
os.remove(tempname)
module.fail_json(msg="failed to create temporary content file: %s" % str(err))
f.close()
req.close()
return tempname, info
# ==============================================================
# main
def main():
# does this really happen on non-ancient python?
if not HAS_URLLIB2:
module.fail_json(msg="urllib2 is not installed")
if not HAS_URLPARSE:
module.fail_json(msg="urlparse is not installed")
module = AnsibleModule(
# not checking because of daisy chain to file module
check_invalid_arguments = False,
argument_spec = dict(
url = dict(required=True),
dest = dict(required=True),
)
)
url = module.params['url']
dest = os.path.expanduser(module.params['dest'])
# download to tmpsrc
tmpsrc, info = url_get(module, url, dest)
md5sum_src = None
md5sum_dest = None
dest = info['actualdest']
# raise an error if there is no tmpsrc file
if not os.path.exists(tmpsrc):
os.remove(tmpsrc)
module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'])
if not os.access(tmpsrc, os.R_OK):
os.remove(tmpsrc)
module.fail_json( msg="Source %s not readable" % (tmpsrc))
md5sum_src = module.md5(tmpsrc)
# check if there is no dest file
if os.path.exists(dest):
# raise an error if copy has no permission on dest
if not os.access(dest, os.W_OK):
os.remove(tmpsrc)
module.fail_json( msg="Destination %s not writable" % (dest))
if not os.access(dest, os.R_OK):
os.remove(tmpsrc)
module.fail_json( msg="Destination %s not readable" % (dest))
md5sum_dest = module.md5(dest)
else:
if not os.access(os.path.dirname(dest), os.W_OK):
os.remove(tmpsrc)
module.fail_json( msg="Destination %s not writable" % (os.path.dirname(dest)))
if md5sum_src != md5sum_dest:
try:
shutil.copyfile(tmpsrc, dest)
except Exception, err:
os.remove(tmpsrc)
module.fail_json(msg="failed to copy %s to %s: %s" % (tmpsrc, dest, str(err)))
changed = True
else:
changed = False
os.remove(tmpsrc)
# Mission complete
module.exit_json(url=url, dest=dest, src=tmpsrc, md5sum=md5sum_src,
changed=changed, msg=info.get('msg',''),
daisychain="file", daisychain_args=info.get('daisychain_args',''))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
@ -18,124 +19,179 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# I wanted to keep this simple at first, so for now this checks out
# from the MASTER branch of a repo at a particular SHA or
# from the given branch of a repo at a particular SHA or
# tag. Latest is not supported, you should not be doing
# that. Branch checkouts are not supported. Contribs
# welcome! -- MPD
# that. Contribs welcome! -- MPD
try:
import json
except ImportError:
import simplejson as json
import os
import sys
import shlex
import subprocess
# ===========================================
# convert arguments of form a=b c=d
# to a dictionary
# FIXME: make more idiomatic
args = " ".join(sys.argv[1:])
items = shlex.split(args)
params = {}
for x in items:
(k, v) = x.split("=")
params[k] = v
dest = params['dest']
repo = params['repo']
version = params['version']
# ===========================================
import re
def get_version(dest):
''' samples the version of the git repo '''
os.chdir(dest)
cmd = "git show --abbrev-commit"
sha = os.popen(cmd).read().split("\n")
sha = sha[0].split()[1]
return sha
''' samples the version of the git repo '''
os.chdir(dest)
cmd = "git show --abbrev-commit"
sha = os.popen(cmd).read().split("\n")
sha = sha[0].split()[1]
return sha
def clone(repo, dest):
''' makes a new git repo if it does not already exist '''
try:
os.makedirs(dest)
except:
pass
cmd = "git clone %s %s" % (repo, dest)
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return cmd.communicate()
''' makes a new git repo if it does not already exist '''
try:
os.makedirs(os.path.dirname(dest))
except:
pass
cmd = "git clone %s %s" % (repo, dest)
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)
def pull(repo, dest):
''' updates repo from remote sources '''
os.chdir(dest)
cmd = "git pull -u origin"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return cmd.communicate()
def reset(dest):
'''
Resets the index and working tree to HEAD.
Discards any changes to tracked files in working
tree since that commit.
'''
os.chdir(dest)
cmd = "git reset --hard HEAD"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)
def switchver(version, dest):
''' once pulled, switch to a particular SHA or tag '''
os.chdir(dest)
if version != 'HEAD':
cmd = "git checkout %s --force" % version
else:
# is there a better way to do this?
cmd = "git rebase origin"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
return (out, err)
def switchLocalBranch( branch ):
cmd = "git checkout %s" % branch
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return cmd.communicate()
gitconfig = os.path.join(dest, '.git', 'config')
def get_branches(module, dest):
os.chdir(dest)
branches = []
cmd = "git branch -a"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
if cmd.returncode != 0:
module.fail_json(msg="Could not determine branch data - received %s" % out)
for line in out.split('\n'):
branches.append(line.strip())
return branches
out, err, status = (None, None, None)
def is_remote_branch(module, dest, remote, branch):
branches = get_branches(module, dest)
rbranch = 'remotes/%s/%s' % (remote, branch)
if rbranch in branches:
return True
else:
return False
# if there is no git configuration, do a clone operation
# else pull and switch the version
def is_local_branch(module, dest, branch):
branches = get_branches(module, dest)
lbranch = '%s' % branch
if lbranch in branches:
return True
elif '* %s' % branch in branches:
return True
else:
return False
before = None
if not os.path.exists(gitconfig):
(out, err) = clone(repo, dest)
else:
# else do a pull
before = get_version(dest)
(out, err) = pull(repo, dest)
def is_current_branch(module, dest, branch):
branches = get_branches(module, dest)
for b in branches:
if b.startswith('* '):
cur_branch = b
if branch == cur_branch or '* %s' % branch == cur_branch:
return True
else:
return True
# handle errors from clone or pull
def pull(module, repo, dest, version):
''' updates repo from remote sources '''
os.chdir(dest)
branches = get_branches(module, dest)
cur_branch = ''
for b in branches:
if b.startswith('* '):
cur_branch = b
if is_local_branch(module, dest, version) and not is_current_branch(module, dest, version):
(out, err) = switch_version(module, dest, remote, version)
if out.find('error') != -1:
print json.dumps({
"failed" : True,
"out" : out,
"err" : err
})
sys.exit(1)
cmd = "git pull -u origin"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)
# switch to version specified regardless of whether
# we cloned or pulled
def switch_version(module, dest, remote, version):
''' once pulled, switch to a particular SHA, tag, or branch '''
os.chdir(dest)
cmd = ''
if version != 'HEAD':
if not is_local_branch(module, dest, version) and is_remote_branch(module, dest, remote, version):
cmd = "git checkout --track -b %s %s/%s" % (version, remote, version)
else:
cmd = "git checkout --force %s" % version
else:
# is there a better way to do this?
cmd = "git rebase origin"
cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = cmd.communicate()
rc = cmd.returncode
return (rc, out, err)
(out, err) = switchver(version, dest)
if err.find('error') != -1:
print json.dumps({
"failed" : True,
"out" : out,
"err" : err
})
sys.exit(1)
# ===========================================
# determine if we changed anything
def main():
module = AnsibleModule(
argument_spec = dict(
dest=dict(required=True),
repo=dict(required=True, aliases=['name']),
version=dict(default='HEAD'),
remote=dict(default='origin')
)
)
after = get_version(dest)
changed = False
dest = module.params['dest']
repo = module.params['repo']
version = module.params['version']
remote = module.params['remote']
if before != after:
changed = True
gitconfig = os.path.join(dest, '.git', 'config')
print json.dumps({
"changed" : changed,
"before" : before,
"after" : after
})
out, err, status = (None, None, None)
# if there is no git configuration, do a clone operation
# else pull and switch the version
before = None
if not os.path.exists(gitconfig):
(rc, out, err) = clone(repo, dest)
if rc != 0:
module.fail_json(msg=err)
else:
# else do a pull
before = get_version(dest)
(rc, out, err) = reset(dest)
if rc != 0:
module.fail_json(msg=err)
(rc, out, err) = pull(module, repo, dest, version)
# handle errors from clone or pull
if out.find('error') != -1 or err.find('ERROR') != -1:
module.fail_json(msg=err)
# switch to version specified regardless of whether
# we cloned or pulled
(rc, out, err) = switch_version(module, dest, remote, version)
if err.find('error') != -1:
module.fail_json(msg=err)
# determine if we changed anything
after = get_version(dest)
changed = False
if before != after:
changed = True
module.exit_json(changed=changed, before=before, after=after)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

138
library/group Executable file
View File

@ -0,0 +1,138 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import grp
def get_bin_path(module, arg):
if os.path.exists('/usr/sbin/%s' % arg):
return '/usr/sbin/%s' % arg
elif os.path.exists('/sbin/%s' % arg):
return '/sbin/%s' % arg
else:
module.fail_json(msg="Cannot find %s" % arg)
def group_del(module, group):
cmd = [get_bin_path(module, 'groupdel'), group]
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
rc = p.returncode
return (rc, out, err)
def group_add(module, group, **kwargs):
cmd = [get_bin_path(module, 'groupadd')]
for key in kwargs:
if key == 'gid' and kwargs[key] is not None:
cmd.append('-g')
cmd.append(kwargs[key])
elif key == 'system' and kwargs[key] == 'yes':
cmd.append('-r')
cmd.append(group)
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
rc = p.returncode
return (rc, out, err)
def group_mod(module, group, **kwargs):
cmd = [get_bin_path(module, 'groupmod')]
info = group_info(group)
for key in kwargs:
if key == 'gid':
if kwargs[key] is not None and info[2] != int(kwargs[key]):
cmd.append('-g')
cmd.append(kwargs[key])
if len(cmd) == 1:
return (None, '', '')
cmd.append(group)
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
rc = p.returncode
return (rc, out, err)
def group_exists(group):
try:
if grp.getgrnam(group):
return True
except KeyError:
return False
def group_info(group):
if not group_exists(group):
return False
try:
info = list(grp.getgrnam(group))
except KeyError:
return False
return info
# ===========================================
def main():
module = AnsibleModule(
argument_spec = dict(
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True),
gid=dict(default=None),
system=dict(default='no', choices=['yes', 'no']),
)
)
state = module.params['state']
name = module.params['name']
gid = module.params['gid']
system = module.params['system']
rc = None
out = ''
err = ''
result = {}
result['name'] = name
result['state'] = state
if state == 'absent':
if group_exists(name):
(rc, out, err) = group_del(module, name)
if rc != 0:
module.fail_json(name=name, msg=err)
elif state == 'present':
if not group_exists(name):
(rc, out, err) = group_add(module, name, gid=gid, system=system)
else:
(rc, out, err) = group_mod(module, name, gid=gid)
if rc is not None and rc != 0:
module.fail_json(name=name, msg=err)
if rc is None:
result['changed'] = False
else:
result['changed'] = True
if out:
result['stdout'] = out
if err:
result['stderr'] = err
if group_exists(name):
info = group_info(name)
result['system'] = system
result['gid'] = info[2]
module.exit_json(**result)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

265
library/mount Executable file
View File

@ -0,0 +1,265 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Red Hat, inc
# Written by Seth Vidal
# based on the mount modules from salt and puppet
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# mount module - mount fs and define in fstab
# usage:
#
# mount name=mountpoint, src=device_to_be_mounted fstype=fstype
# opts=mount_opts, dump=0 passno=0 state=[present|absent|mounted|unmounted]
#
# absent == remove from fstab and unmounted
# present == add to fstab, do not change mount state
# mounted == add to fstab if not there and make sure it is mounted
# unmounted == do not change fstab state, but unmount
def write_fstab(lines, dest):
fs_w = open(dest, 'w')
for l in lines:
fs_w.write(l)
fs_w.flush()
fs_w.close()
def set_mount(**kwargs):
""" set/change a mount point location in fstab """
# kwargs: name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
args = dict(
opts = 'defaults',
dump = '0',
passno = '0',
fstab = '/etc/fstab'
)
args.update(kwargs)
new_line = '%(src)s %(name)s %(fstype)s %(opts)s %(dump)s %(passno)s\n'
to_write = []
exists = False
changed = False
for line in open(args['fstab'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
if len(line.split()) != 6:
# not sure what this is or why it is here
# but it is not our fault so leave it be
to_write.append(line)
continue
ld = {}
ld['src'], ld['name'], ld['fstype'], ld['opts'], ld['dump'], ld['passno'] = line.split()
if ld['name'] != args['name']:
to_write.append(line)
continue
# it exists - now see if what we have is different
exists = True
for t in ('src', 'fstype','opts', 'dump', 'passno'):
if ld[t] != args[t]:
changed = True
ld[t] = args[t]
if changed:
to_write.append(new_line % ld)
else:
to_write.append(line)
if not exists:
to_write.append(new_line % args)
changed = True
if changed:
write_fstab(to_write, args['fstab'])
return (args['name'], changed)
def unset_mount(**kwargs):
""" remove a mount point from fstab """
# kwargs: name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
args = dict(
opts = 'default',
dump = '0',
passno = '0',
fstab = '/etc/fstab'
)
args.update(kwargs)
to_write = []
changed = False
for line in open(args['fstab'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
if len(line.split()) != 6:
# not sure what this is or why it is here
# but it is not our fault so leave it be
to_write.append(line)
continue
ld = {}
ld['src'], ld['name'], ld['fstype'], ld['opts'], ld['dump'], ld['passno'] = line.split()
if ld['name'] != args['name']:
to_write.append(line)
continue
# if we got here we found a match - continue and mark changed
changed = True
if changed:
write_fstab(to_write, args['fstab'])
return (args['name'], changed)
def mount(**kwargs):
""" mount up a path or remount if needed """
name = kwargs['name']
if os.path.ismount(name):
cmd = ['/bin/mount', '-o', 'remount', name]
else:
cmd = ['/bin/mount', name ]
call = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = call.communicate()
if call.returncode == 0:
return 0, ''
else:
return call.returncode, out+err
def umount(**kwargs):
""" unmount a path """
name = kwargs['name']
cmd = ['/bin/umount', name]
call = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = call.communicate()
if call.returncode == 0:
return 0, ''
else:
return call.returncode, out+err
def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(required=True, choices=['present', 'absent', 'mounted', 'unmounted']),
name = dict(required=True),
opts = dict(default=None),
passno = dict(default=None),
dump = dict(default=None),
src = dict(required=True),
fstype = dict(required=True),
fstab = dict(default=None)
)
)
changed = False
rc = 0
args = {
'name': module.params['name'],
'src': module.params['src'],
'fstype': module.params['fstype']
}
if module.params['passno'] is not None:
args['passno'] = module.params['passno']
if module.params['opts'] is not None:
args['opts'] = module.params['opts']
if module.params['dump'] is not None:
args['dump'] = module.params['dump']
if module.params['fstab'] is not None:
args['fstab'] = module.params['fstab']
# absent == remove from fstab and unmounted
# unmounted == do not change fstab state, but unmount
# present == add to fstab, do not change mount state
# mounted == add to fstab if not there and make sure it is mounted, if it has changed in fstab then remount it
state = module.params['state']
name = module.params['name']
if state == 'absent':
name, changed = unset_mount(**args)
if changed:
if os.path.ismount(name):
res,msg = umount(**args)
if res:
fail_json(msg="Error unmounting %s: %s" % (name, msg))
if os.path.exists(name):
try:
os.rmdir(name)
except (OSError, IOError), e:
fail_json(msg="Error rmdir %s: %s" % (name, str(e)))
module.exit_json(changed=changed, **args)
if state == 'unmounted':
if os.path.ismount(name):
res,msg = umount(**args)
if res:
fail_json(msg="Error unmounting %s: %s" % (name, msg))
changed = True
module.exit_json(changed=changed, **args)
if state in ['mounted', 'present']:
name, changed = set_mount(**args)
if state == 'mounted':
if not os.path.exists(name):
try:
os.makedirs(name)
except (OSError, IOError), e:
fail_json(msg="Error making dir %s: %s" % (name, str(e)))
res = 0
if os.path.ismount(name):
if changed:
res,msg = mount(**args)
else:
changed = True
res,msg = mount(**args)
if res:
fail_json(msg="Error mounting %s: %s" % (name, msg))
module.exit_json(changed=changed, **args)
module.fail_json(msg='Unexpected position reached')
sys.exit(0)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

123
library/mysql_db Executable file
View File

@ -0,0 +1,123 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
# Sponsored by Four Kitchens http://fourkitchens.com.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import ConfigParser
try:
import MySQLdb
except ImportError:
mysqldb_found = False
else:
mysqldb_found = True
# ===========================================
# MySQL module specific support methods.
#
def db_exists(cursor, db):
res = cursor.execute("SHOW DATABASES LIKE %s", (db,))
return bool(res)
def db_delete(cursor, db):
query = "DROP DATABASE %s" % db
cursor.execute(query)
return True
def db_create(cursor, db, encoding, collation):
if encoding:
encoding = " CHARACTER SET %s" % encoding
if collation:
collation = " COLLATE %s" % collation
query = "CREATE DATABASE %s%s%s" % (db, encoding, collation)
res = cursor.execute(query)
return True
def load_mycnf():
config = ConfigParser.RawConfigParser()
mycnf = os.path.expanduser('~/.my.cnf')
if not os.path.exists(mycnf):
return False
try:
config.readfp(open(mycnf))
creds = dict(user=config.get('client', 'user'),passwd=config.get('client', 'pass'))
except (ConfigParser.NoOptionError, IOError):
return False
return creds
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec = dict(
login_user=dict(default=None),
login_password=dict(default=None),
login_host=dict(default="localhost"),
db=dict(required=True, aliases=['name']),
encoding=dict(default=""),
collation=dict(default=""),
state=dict(default="present", choices=["absent", "present"]),
)
)
if not mysqldb_found:
module.fail_json(msg="the python mysqldb module is required")
db = module.params["db"]
encoding = module.params["encoding"]
collation = module.params["collation"]
state = module.params["state"]
# Either the caller passes both a username and password with which to connect to
# mysql, or they pass neither and allow this module to read the credentials from
# ~/.my.cnf.
login_password = module.params["login_password"]
login_user = module.params["login_user"]
if login_user is None and login_password is None:
mycnf_creds = load_mycnf()
if mycnf_creds is False:
login_user = "root"
login_password = ""
else:
login_user = mycnf_creds["user"]
login_password = mycnf_creds["passwd"]
elif login_password is None or login_user is None:
module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
try:
db_connection = MySQLdb.connect(host=module.params["login_host"], user=login_user, passwd=login_password, db="mysql")
cursor = db_connection.cursor()
except Exception as e:
module.fail_json(msg="unable to connect, check login_user and login_password are correct, or alternatively check ~/.my.cnf contains credentials")
changed = False
if db_exists(cursor, db):
if state == "absent":
changed = db_delete(cursor, db)
else:
if state == "present":
changed = db_create(cursor, db, encoding, collation)
module.exit_json(changed=changed, db=db)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

227
library/mysql_user Executable file
View File

@ -0,0 +1,227 @@
#!/usr/bin/python
# (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
# Sponsored by Four Kitchens http://fourkitchens.com.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import ConfigParser
try:
import MySQLdb
except ImportError:
mysqldb_found = False
else:
mysqldb_found = True
# ===========================================
# MySQL module specific support methods.
#
def user_exists(cursor, user, host):
cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
count = cursor.fetchone()
return count[0] > 0
def user_add(cursor, user, host, password, new_priv):
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
if new_priv is not None:
for db_table, priv in new_priv.iteritems():
privileges_grant(cursor, user,host,db_table,priv)
return True
def user_mod(cursor, user, host, password, new_priv):
changed = False
# Handle passwords.
if password is not None:
cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
current_pass_hash = cursor.fetchone()
cursor.execute("SELECT PASSWORD(%s)", (password,))
new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user,host,password))
changed = True
# Handle privileges.
if new_priv is not None:
curr_priv = privileges_get(cursor, user,host)
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in curr_priv.iteritems():
if db_table not in new_priv:
privileges_revoke(cursor, user,host,db_table)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in new_priv.iteritems():
if db_table not in curr_priv:
privileges_grant(cursor, user,host,db_table,priv)
changed = True
# If the db.table specification exists in both the user's current privileges
# and in the new privileges, then we need to see if there's a difference.
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
for db_table in db_table_intersect:
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
if (len(priv_diff) > 0):
privileges_revoke(cursor, user,host,db_table)
privileges_grant(cursor, user,host,db_table,new_priv[db_table])
changed = True
return changed
def user_delete(cursor, user, host):
cursor.execute("DROP USER %s@%s", (user,host))
return True
def privileges_get(cursor, user,host):
""" MySQL doesn't have a better method of getting privileges aside from the
SHOW GRANTS query syntax, which requires us to then parse the returned string.
Here's an example of the string that is returned from MySQL:
GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass';
This function makes the query and returns a dictionary containing the results.
The dictionary format is the same as that returned by privileges_unpack() below.
"""
output = {}
cursor.execute("SHOW GRANTS FOR %s@%s", (user,host))
grants = cursor.fetchall()
for grant in grants:
res = re.match("GRANT\ (.+)\ ON\ (.+)\ TO", grant[0])
if res is None:
module.fail_json(msg="unable to parse the MySQL grant string")
privileges = res.group(1).split(", ")
privileges = ['ALL' if x=='ALL PRIVILEGES' else x for x in privileges]
db = res.group(2).replace('`', '')
output[db] = privileges
return output
def privileges_unpack(priv):
""" Take a privileges string, typically passed as a parameter, and unserialize
it into a dictionary, the same format as privileges_get() above. We have this
custom format to avoid using YAML/JSON strings inside YAML playbooks. Example
of a privileges string:
mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL
The privilege USAGE stands for no privileges, so we add that in on *.* if it's
not specified in the string, as MySQL will always provide this by default.
"""
output = {}
for item in priv.split('/'):
pieces = item.split(':')
output[pieces[0]] = pieces[1].upper().split(',')
if '*.*' not in output:
output['*.*'] = ['USAGE']
return output
def privileges_revoke(cursor, user,host,db_table):
query = "REVOKE ALL PRIVILEGES ON %s FROM '%s'@'%s'" % (db_table,user,host)
cursor.execute(query)
def privileges_grant(cursor, user,host,db_table,priv):
priv_string = ",".join(priv)
query = "GRANT %s ON %s TO '%s'@'%s'" % (priv_string,db_table,user,host)
cursor.execute(query)
def load_mycnf():
config = ConfigParser.RawConfigParser()
mycnf = os.path.expanduser('~/.my.cnf')
if not os.path.exists(mycnf):
return False
try:
config.readfp(open(mycnf))
creds = dict(user=config.get('client', 'user'),password=config.get('client', 'pass'))
except (ConfigParser.NoOptionError, IOError):
return False
return creds
# ===========================================
# Module execution.
#
def main():
module = AnsibleModule(
argument_spec = dict(
login_user=dict(default=None),
login_password=dict(default=None),
login_host=dict(default="localhost"),
user=dict(required=True, aliases=['name']),
password=dict(default=None),
host=dict(default="localhost"),
state=dict(default="present", choices=["absent", "present"]),
priv=dict(default=None),
)
)
user = module.params["user"]
password = module.params["password"]
host = module.params["host"]
state = module.params["state"]
priv = module.params["priv"]
if not mysqldb_found:
module.fail_json(msg="the python mysqldb module is required")
if priv is not None:
try:
priv = privileges_unpack(priv)
except:
module.fail_json(msg="invalid privileges string")
# Either the caller passes both a username and password with which to connect to
# mysql, or they pass neither and allow this module to read the credentials from
# ~/.my.cnf.
login_password = module.params["login_password"]
login_user = module.params["login_user"]
if login_user is None and login_password is None:
mycnf_creds = load_mycnf()
if mycnf_creds is False:
login_user = "root"
login_password = ""
else:
login_user = mycnf_creds["user"]
login_password = mycnf_creds["password"]
elif login_password is None or login_user is None:
module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
try:
db_connection = MySQLdb.connect(host=module.params["login_host"], user=login_user, passwd=login_password, db="mysql")
cursor = db_connection.cursor()
except Exception as e:
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials")
if state == "present":
if user_exists(cursor, user, host):
changed = user_mod(cursor, user, host, password, priv)
else:
if password is None:
module.fail_json(msg="password parameter required when adding a user")
changed = user_add(cursor, user, host, password, priv)
elif state == "absent":
if user_exists(cursor, user, host):
changed = user_delete(cursor, user, host)
else:
changed = False
module.exit_json(changed=changed, user=user)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View File

@ -1,4 +1,5 @@
#!/bin/bash
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
@ -18,4 +19,25 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
/usr/bin/ohai
def get_ohai_data():
p = subprocess.Popen(["/usr/bin/env", "ohai"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
rc = p.returncode
return rc, out, err
def main():
module = AnsibleModule(
argument_spec = dict()
)
rc, out, err = get_ohai_data()
if rc != 0:
module.fail_json(msg=err)
else:
module.exit_json(**json.loads(out))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

Some files were not shown because too many files have changed in this diff Show More