Compare commits

...

570 Commits

Author SHA1 Message Date
49f7e6a12c update submodule pointer 2016-02-23 14:22:01 -05:00
d70fd17cf9 Merge pull request #14618 from ogenstad/devel
Fixes authentication error bug when user has rsa keys
2016-02-23 14:20:49 -05:00
5d5abbb898 bump next VERSION for -network 2016-02-18 21:36:16 +00:00
3f7b7a1bb9 update submodule pointer 2016-02-18 21:34:02 +00:00
858a0a2339 bugfix on shared module utils from missed cherry-pick
One of the commits was missed when the branch was created.  This fixes the
modules so they work properly
2016-02-18 21:13:57 +00:00
61989f72cc VERSION rename for the -network release 2016-02-17 17:11:44 -05:00
b3da42a669 Updating submodule pointer for the network branch 2016-02-17 16:30:42 -05:00
e15daf186a minor bugfix that will catch connection errors in eos
This commit fixes a situation where connection errors would be caught
but no useful information display.  The connection error is now caught
and emitted in a call to fail_json
2016-02-17 15:41:44 +00:00
9c4467303f minor bugfix that will catch connection errors in ios
This commit fixes a situation where connection errors would be caught
but no useful information display.  The connection error is now caught
and emitted in a call to fail_json
2016-02-17 15:41:37 +00:00
df3f9345e1 minor bugfix that will catch connection errors in iosxr
This commit fixes a situation where connection errors would be caught
but no useful information display.  The connection error is now caught
and emitted in a call to fail_json
2016-02-17 15:41:30 +00:00
5c7c13b9c1 minor bugfix that will catch connection errors in nxos
This commit fixes a situation where connection errors would be caught
but no useful information display.  The connection error is now caught
and emitted in a call to fail_json
2016-02-17 15:41:23 +00:00
0ef6a7ae98 minor bugfix that will catch connection errors in junos
This commit fixes a situation where connection errors would be caught
but no useful information display.  The connection error is now caught
and emitted in a call to fail_json
2016-02-17 15:41:13 +00:00
1fd5bcdc09 minor bug fixes and updates to shell 2016-02-17 15:40:49 +00:00
d178c4be2a Move Conditional class to netcfg.
Added error handling for connect and execute methods.

Fix comments
2016-02-17 15:40:30 +00:00
be846c7053 initial add of action plugin ops_template
Adds new local action ops_config for handling openswitch configurations using
either dc or cli based configurations.  Implements the common net_config
local action.

Note this refactors the ops_config plugin to ops_template
2016-02-17 15:38:36 +00:00
fa2834d863 initial add of new action plugin junos_template
This adds a new action plugin, junos_template that allows the
the junos_template module to perform the templating function.  It
implements net_template
2016-02-17 15:38:29 +00:00
320e099c12 initial add of action plugin nxos_template
Adds new local action for working with cisco nxos configurations.  Implemements
the net_config local action.

Note this action plugin was refactored from nxos_config to nxos_template
2016-02-17 15:38:23 +00:00
f0ee61925a adds new action plugin iosxr_template
This adds a new action plugin iosxr_template that allows the
iosxr_template module to pass network device configurations through the
template engine.  It also allows configurations to be backed up.
2016-02-17 15:38:17 +00:00
d024517d4b initial add of action plugin ios_template
Adds a new local action ios_config for working with cisco ios configuration
files.  Implements the common net_confing local action

Note this plugin was refactored from ios_config to ios_template
2016-02-17 15:37:32 +00:00
651842a4ef initial add of action plugin eos_template
Adds a new local action for eos_config module to handle templating configs
and backing up running configurations.   Implements the local action
net_config

Note this action was refactored from eos_config to eos_template
2016-02-17 15:37:23 +00:00
ee7de10285 Add safety check on _backup key. 2016-02-17 15:37:11 +00:00
bec698052f New release v2.0.1.0-0.2.rc2 2016-02-15 16:02:44 -05:00
729dbe7e67 removed follow from common file docs 2016-02-15 14:14:22 -05:00
032d917e68 Update changelog for dnf fix 2016-02-13 02:09:26 -08:00
9ecdec0141 Update submodule refs 2016-02-13 02:09:26 -08:00
028c710380 Fix bugs related to task_includes and dep chain inheritance
* Fix the way task_include fields were created and copied
* Have blocks get_dep_chain() look at task_include's blocks for proper
  dep chain inheritance
* Fix the way task_include fields are copied to prevent a recursive
  degradation

Fixes #14460
2016-02-13 01:09:16 -05:00
8ee8872d6b Fixing bugs related to nested blocks inside roles
* Make sure dep chains are checked recursively for nested blocks
* Fixing iterator is_failed() check to make sure we're not in a
  rescue block before returning True
* Use is_failed() to test whether a host should be added to the TQM
  failed_hosts list
* Use is_failed() when compiling the list of hosts left to iterate
  over in both the linear and free strategies

Fixes #14222
2016-02-12 16:41:38 -05:00
caf1537510 Display the string which is causing an exception to be raised.
In the ansible template module, when there is an error while
expanding a templated string, displaying the string causing the
exception is very useful.
2016-02-11 22:51:26 -05:00
6f54b1ae2b removed signal trapping 2016-02-11 22:03:00 -05:00
f77690e652 update extras submodule ref
for ec2_vpc_net_facts and elasticsearch module updates
2016-02-11 13:37:41 -08:00
5336a51a9e Update submodule for fix to mysql_user password 2016-02-11 13:09:59 -08:00
9c049b757f Revert "Module params should default to str in most cases."
This reverts commit 9fae8501e2.

Reverting for 2.0.1 -- this has been broken in the 2.0 tree for long
enough that some modules are relying on the broken behaviour.  This fix
will definitely return for 2.1.0 and may return in 2.0.2 (impact needs
to be evaluated).
2016-02-11 12:58:41 -08:00
0ccbbe0c09 allow skipping tasks due to undefined var
mimic 1.x behaviour but give out big deprecation message, not only
for missing attribute but any undefined error.
2016-02-11 14:32:49 -05:00
43bbd4b6dc termination handling
- moved to base cli class to handle centrally and duplicate less code
- now avoids duplication and reiteration of signal handler by reassigning it
- left note on how to do non-graceful in case we add in future
  as I won't remember everything i did here and don't want to 'relearn' it.
2016-02-11 12:42:12 -05:00
517eec8c49 fix cartesian lookup
it seems that this was not working in 1.9 but we swallowed up the error
fixes #14437
2016-02-11 10:36:04 -05:00
b85d9acc09 update core submodule ref 2016-02-10 22:32:32 -08:00
ba5a3f2f67 Update submodule ref 2016-02-10 17:38:24 -08:00
eafc46512d fix typo 2016-02-10 17:37:18 -08:00
39a53c5623 add ismount function from python Lib/posixpath.py
needed for https://github.com/ansible/ansible-modules-core/pull/2737
2016-02-10 17:37:07 -08:00
9fae8501e2 Module params should default to str in most cases. 2016-02-10 15:33:30 -08:00
724761d2a1 read full file when doing diff
but avoid reading file at all or full file when file is too big for diffing
2016-02-10 15:47:44 -05:00
3e2b2b359c Clarify error message when module replacer encounters a bad import line for module_utils code 2016-02-10 08:47:45 -08:00
5ebd5c548c Catch exceptions during module execution so they don't fail the worker
Fixes #14120
2016-02-10 09:24:44 -05:00
18ffa8a4da avoid termination message when term is internal 2016-02-09 18:11:11 -05:00
fa88d0770c Proposed fix for ansible/ansible-modules-extras#1348 due to datetime.datetime type not being matched 2016-02-09 17:12:36 -05:00
24cae22e0b Submodule pointer update 2016-02-09 13:29:45 -05:00
4f2a2ec884 strip BECOME-SUCCESS at lower level
Fixes #14395
2016-02-09 13:12:46 -05:00
7d44063c1e Filter become success string from the stdout of script results
Fixes #14390
2016-02-09 12:38:31 -05:00
dcc0a2d596 Also hide the before state of files with --diff and no_log 2016-02-08 22:06:38 -08:00
c97afd0c48 fix winrm erorr formatting 2016-02-08 23:00:49 -05:00
7c726d197b correctly handle term signals
- adhoc now terminates gracefully
- avoid race condition on terminations by ignoring errors if
  worker might have been reaped between checking if active and termination call
- ansible-playbook now properly exits on sigint/term
- adhoc and playbook now give exceptions that we should not normally capture
  and rely on top level finally to reap children
- handle systemexit breaks in workers
- added debug to see at which frame we exit
partial fix for #14346
2016-02-08 17:23:13 -05:00
13c379dcd0 Added more info to the no action detected error
Error fix
2016-02-08 16:57:54 -05:00
9196a72c3f Add support ssh configs from /etc/openssh.
In Altlinux system config dir for openssh is /etc/openssh.
2016-02-08 10:32:46 -05:00
0b4a259444 Also chmod the args file path when using become for old-style modules
Fixes #14348
2016-02-08 10:02:34 -05:00
5353b8c3b6 Allowing multiple tags to be specified in include's tags argument. 2016-02-08 08:52:55 -05:00
6869ced587 Update core submodule ref to fix apt locale bug 2016-02-07 14:27:48 -08:00
87414e4fd1 Add locale fixes to changelgo 2016-02-07 13:11:32 -08:00
ef9c27e280 Update submodule refs 2016-02-07 12:56:57 -08:00
76ab1d2afc Allow setting run_command environment overrides for the life of an AnsibleModule 2016-02-07 12:55:58 -08:00
a316891968 tag the setup task with always
fixes #14228
2016-02-05 10:35:31 -05:00
3303f25a0c Fix --diff to respect no_log task parameter. 2016-02-04 12:47:44 -08:00
bee699a33a Update submodule refs 2016-02-04 11:12:10 -08:00
4133cf924a Also add 'null' as a possible JSON value 2016-02-04 11:39:20 -05:00
53559433ef Defined JSON booleans in global context for python eval()
We define 'false' and 'true' as variables so that python eval() recognizes them as False and True.

This fixes #14291.
2016-02-04 11:39:20 -05:00
8ca8459af5 Merge pull request #14214 from dvarrazzo/conflicting-actions-message
Better "conflicting action statements" message
2016-02-03 22:55:35 -05:00
6c559e904a Handle when the lookup-plugin is used in jinja-style
This fixes #14190.
2016-02-03 11:18:51 -05:00
901beeec55 avoid logging color codes 2016-02-03 11:00:08 -05:00
4604deca92 Change $() into backticks. Backticks should work for both POSIX and non-POSIX compatible shells.
Fixes #14176
2016-02-02 12:39:55 -08:00
d001c73699 Establish sh as the default shell plugin.
This is a fix for one of the problems pointed out in #14176
2016-02-02 12:19:43 -08:00
53cfde5096 better fix for ansible-pull inventory handling
now it mirrors what it did in previous versions
and properly uses inventory and limit options
2016-02-02 14:29:47 -05:00
6c9d2cc2b4 Note the dnf fix 2016-02-02 10:51:41 -08:00
4de1e26d5c Update submodule refs 2016-02-02 10:49:20 -08:00
8ac8f4c2ab make executable setting connection dependant
winrm shoudl not use executable, rest should?
fixes #14233
2016-02-02 10:22:09 -08:00
5035e53e80 Revert "load now does not modify the incomming data"
This reverts commit 2126e9fee1.
we judge this to be too big a change for stable, keeping in devel
we may still add part or all back to stable in the future.
2016-02-02 11:22:41 -05:00
2126e9fee1 load now does not modify the incomming data
also removed json loader as yaml loader can do both
2016-02-02 11:22:26 -05:00
93cab88663 Don't mark host as having gathered facts when peeking at next task
Fixes #14243
2016-02-02 11:12:28 -05:00
c5f857ec39 Make sure setup tasks inherit properly from their parent play
Fixes #13602
2016-02-02 11:12:24 -05:00
07dd83bc6e deprecate all bare variable conversions (not debug)
now deprecation message appears with variable name in all spots where this occurs
debug's var= option is excluded as this is only place where bare variables shold actually
be accepted.
2016-02-02 00:01:47 -05:00
acb556b3d4 fix incorrect environment processing
it was assumed it could only be a dict or string (it starts out as a list)
also a 2nd assumption that bare vars only would appear in one of the dict keys.

removed deprecation warnings from here as they should be signaled in the bare conversion itself.
2016-02-02 00:01:36 -05:00
48b20684f2 normalize error message on src != dir when local
now action plugin returns same error as module
2016-02-01 13:44:01 -05:00
d7a6ccc4c2 Use isinstance type checks in bool filter so we can handle unsafe proxy objects. Fixes #14240 2016-02-01 12:34:13 -06:00
7f2bc082fa harcoding inventory/hosts when pulling from repo
return to pre 2.0 behaviour which did not factor inventory into the repo pull
2016-01-30 11:39:51 -05:00
d975f32243 added back --profile to keep backwards compat
this was broken by #12548
2016-01-30 11:36:37 -05:00
e8e388a742 Some attributes of callbacks aren't in v2. Port plugins to the v2 way to do that
Update porting guide with info on callback porting
2016-01-30 08:18:19 -08:00
a6c25f143a Explicitly set validate_certs=False as the boto default is True
Fixes #14089
2016-01-29 16:38:18 -08:00
e957ab5a7f fixed code for v1 callback runtime compatiblity
old exception code obsoleted by exception avoidance
errors on callback produce warnings and don't stop play
2016-01-29 17:01:30 -05:00
46002111bb fix unicode input for template lookup
fixes #14207
2016-01-29 16:46:33 -05:00
05bb1b3cb0 Better "conflicting action statements" message 2016-01-29 18:20:09 +00:00
8d7056220d Fix for SLES 11.4, which has now also an /etc/os-release file. 2016-01-29 09:14:13 -08:00
76c941305b When setting up the local connection for the rsync we need to set the shell as well.
Fixes #13490
2016-01-28 20:32:56 -08:00
48da1b8e85 New release v2.0.1.0-0.1.rc1 2016-01-28 16:39:53 -05:00
1e56daf66c More CHANGELOG updates for 2.0.1 2016-01-28 16:32:19 -05:00
506b0a8b61 New release v2.0.0.2-1 2016-01-28 16:24:01 -05:00
ea1339de18 New release v2.0.0.1-1 2016-01-28 16:24:01 -05:00
d60764bc81 Add more entries to changelog 2016-01-28 13:19:31 -08:00
951fa36fca Return an error when synchronize is used with something other than an ssh connection
Suggested in #2832
2016-01-28 16:05:12 -05:00
43cf527fe7 Don't try to generate retry files if the tqm hasn't been initialized
Fixes #14144
2016-01-28 16:02:54 -05:00
a074ebedbc Make retry file generation not use StringIO 2016-01-28 16:02:23 -05:00
d29dfdc84b Re-implementing the retry file feature for 2.0
Fixes #13944
2016-01-28 16:02:19 -05:00
aac8ea0262 Fix lookup of parent attribute when parent doesn't have the attr
Fixes #14100
2016-01-28 16:02:09 -05:00
3db2073909 Only use os.path.basename if get_file_content returned a value, and ensure that service_mgr has line endings stripped. Fixes #14026 2016-01-28 16:01:43 -05:00
0fddd90f14 Handle the key 'prepared' in the 'diff' result from modules
So far, when a 'diff' dict is returned with module results, it is
checked for 'before' and 'after' texts, which are processed in
_get_diff() by python difflib.  This generates the changes to display
when CLI users specify --diff.

However, some modules will generate changes that cannot easily be
expressed in a conventional diff. One example is the output of the
synchronize module, which presents changed files in a common log format
as in `rsync --itemize-changes`.

Add a check for a diff['prepared'] key, which can contain prepared diff text
from modules.
2016-01-28 16:01:15 -05:00
5fe16bef6f Make sure blocks use their parents dependency chains
Fixes a bug inroduced in fb797a9 where included tasks in a role
were not being executed because the child blocks had no dep chain
set.
2016-01-28 15:59:43 -05:00
86b179d3d7 Allow certain base attributes to be used as role params
Role definitions typically require params to be different from those
which are specified as FieldAttributes on the playbook classes used
for roles, however a certain subset should be allowed (typically those
used for connection stuff).

Fixes #14095
2016-01-28 15:59:43 -05:00
7a8520ac1a Fixing role dependency chain creation
The dep chain for roles created during the compile step had bugs, in
which the dep chain was overwriten and the original tasks in the role
were not assigned a dep chain. This lead to problems in determining
whether roles had already run when in a "diamond" structure, and in
some cases roles were not correctly getting variables from parents.

Fixes #14046
2016-01-28 15:59:43 -05:00
8f7868aead Note bugfix for unicode characters and the pager 2016-01-28 11:48:58 -08:00
9ddb4a985c Changes to convert to unicode at the borders
The module docs and vault changes solve issues where tracebacks can
happen.  The galaxy changes are mostly refactoring to be more pythonic
with a small chance that a unicode traceback could have occurred there
without the changes.  The change in __init__.py when we actually call
the pager makes things more robust but could hide places where we had
bytes coming in already so I didn't want to change that without auditing
where the text was coming from.

Fixes #14178
2016-01-28 11:47:21 -08:00
55db4d9e23 Add squashing fix to the changelog 2016-01-28 11:00:23 -08:00
849def8d6e Squashing was occuring even though pkgs didn't have a template that would be affected by squash
This broke other uses of looping (looping for delegate_to in the
reported bug)

Fixes #13980
2016-01-28 10:58:18 -08:00
c0d6a0c11a Update submodule refs 2016-01-28 09:44:19 -08:00
97cd0bc803 fixed unit test to match new output of become/su 2016-01-27 16:50:06 -05:00
7c7e849a27 fixed issue with vars prompt warning causing error
sometimes display object is not magically available, use it explicitly,
no need to be classmethod anymore
fixes #14147
2016-01-27 16:14:03 -05:00
9eaa5fecc1 fix for so su works in more cases
should not fail anymore on csh, fish nor the BSDs
fixes #14116
2016-01-27 14:13:27 -05:00
a0b7620b8e Add fix for 2831 to the changelog 2016-01-26 15:06:37 -08:00
3906614538 Fix for inventory hosts on localhost with alternate ports.
Fixes https://github.com/ansible/ansible-modules-core/issues/2831
2016-01-26 15:05:40 -08:00
03a89e17b9 fixed permissions check for ansible.log
fixes #13990
2016-01-26 11:23:04 -05:00
c52a35ff34 [hotfix] add missed playbook file 2016-01-26 10:45:58 -05:00
16ee2090bd Adding support for scm-based role source urls (incl. integration test to cover it) 2016-01-26 10:45:58 -05:00
7e1a3da97c Add last few major fixes to the 2.0.1 changelog 2016-01-25 20:07:19 -08:00
625e8f2030 Update submodule refs 2016-01-25 19:58:01 -08:00
826f42a153 For synchronize, fix sudo to execute on the remote end of the connection
* In 2.0.0.x become was reversed for synchronize. It was happening on
  the local machine instead of the remote machine. This restores the
  ansible-1.9.x behaviour of doing become on the remote machine.
  However, there's aspects of this that are hacky (no hackier than
  ansible-1.9 but not using 2.0 features).  The big problem is that it
  does not understand any become method except sudo.  I'm willing to use
  a partial fix now because we don't want people to get used to the
  reversed semantics in their playbooks.
* synchronize copying to the wrong host when inventory_hostname is
  localhost
* Fix problem with unicode arguments (first seen as a bug on synchronize)

Fixes #14041
Fixes #13825
2016-01-25 19:55:56 -08:00
1fdf8bca2a python3 doesn't have raw explicit-unicode literals. Workaround it with a raw native string that we make unicode in py2. 2016-01-25 19:48:17 -08:00
6eaf9b9fa2 Transform tracebacks into unicode before printing
Fixes #14042
2016-01-25 19:22:43 -08:00
9a3515357b raise AnsibleError as an 'expected' exception
fixes #14065
2016-01-25 22:21:31 -05:00
cd0812a187 adhoc now respects limit when listing hosts
also removed cruft about localhost as if used it is specified inline
fixes #13848
2016-01-25 17:39:39 -05:00
864c67cec5 ensure stdout callback alwasy is loaded first
it is now called for every event prior to any other callbacks
fixes #14114
2016-01-25 17:13:37 -05:00
7fe91e1848 better error when host inventory script isnt dict 2016-01-25 16:32:57 -05:00
084bacc4a4 add webdocs as alias 2016-01-25 16:32:38 -05:00
e6c9d9c81b Update CHANGELOG.md 2016-01-25 14:42:31 -05:00
0c250f2b07 Use templated handler name during callback
Fixes #14082
2016-01-23 09:26:25 -05:00
a516f883ac convert winrm put_file script template to Unicode string literal
Fixes traceback on homedirs with non-ascii chars
2016-01-22 13:11:02 -08:00
95f7aa2b53 corrected submodule ref 2016-01-22 08:16:53 -05:00
da4a9d6da0 Fix bug with any_errors_fatal where task was not available 2016-01-21 16:41:05 -05:00
d18cc79873 add detect Altlinux distributive 2016-01-21 16:10:53 -05:00
2b3cd2577a Disallow setting state on template tasks
Fixes #14056
2016-01-21 15:37:31 -05:00
50bb0d08f1 Template the run_once value in the linear strategy as we use it there
This is pre-post_validation, so we have to template it on the fly
as we use it to determine if we bypass the host loop.

Fixes #11876
2016-01-21 15:37:25 -05:00
391420d88f Re-add cache clearing call to Inventory init
This prevents a bug where the existing cache outside of the class
is not cleared when creating a new Inventory object. This only really
affects people using the API directly right now, but wanted to fix it
to prevent weird errors from popping up.
2016-01-21 15:37:19 -05:00
4627b3efbb Standardize removal of BECOME-SUCCESS method and use it for async too
Fixes #13965
Fixes #13971
2016-01-21 15:37:10 -05:00
7d8794daa6 plugins/strategy: added significant details to parser error message.
See discussion at
https://github.com/ansible/ansible/issues/13753
2016-01-21 15:37:02 -05:00
2021362386 Fix incorrect handling of any_errors_fatal in the linear strategy
Instead of bombing out of the strategy, we now properly mark hosts failed
so that the play iterator can handle block rescue/always properly.

Fixes #14024
2016-01-21 15:36:27 -05:00
bb564075b5 Forward conditionals onto included plays when conditional eval errors
When using a playbook-level include, we now catch any errors raised during
the conditional evaluation step and set a flag to indicate we need to pass
those conditionals on to the included play (most likely because they contain
inventory variables for evaluation).

Fixes #14003
2016-01-21 15:36:06 -05:00
09b26b6373 Don't assign both parent blocks and task includes to blocks
This causes problems when fetching parent attributes, as the include
was being skipped because the parent block would fetch the attribute
from the parent play first.

Fixes #13872
2016-01-21 15:35:57 -05:00
8647fdc7f2 go back to defaulting wrapping commands in shell
this was taken out in an effort to default to the user's shell but creates issues as this is not known ahead of time
and its painful to set executable and shell_type for all servers, it should only be needed for those that restrict the user
to specific shells and when /bin/sh is not available. raw and command may still bypass this by explicitly passing None.
fixes #13882

still conditional
2016-01-21 15:35:49 -05:00
7fe0bfd4f9 Revert "Properly look for parent become attribute"
This reverts commit 1b46a422aa.
2016-01-21 15:35:35 -05:00
2b5caa23fb Properly look for parent become attribute
Corrects inheritence of the boolean value, which needs some special
consideration from other (string/int) values.

Fixes #13872
2016-01-21 15:35:16 -05:00
0d2a9d3f53 Relocate use of ERROR to display class, to avoid doubling up 2016-01-21 15:35:09 -05:00
34e3a8e6cb Catch INI section parsing misses and raise an appropriate error
Fixes #13917
2016-01-21 15:35:02 -05:00
1fc35117d3 Fix role hashing failure/traceback when params contain lists
Fixes #13857
2016-01-21 15:34:52 -05:00
fb81447581 Don't clear start at task flag until all hosts are advanced
Clearing the flag after the first host was advanced caused all other
hosts to not advance at all.

Fixes #13864
2016-01-21 15:34:23 -05:00
2dfcb218dc Fix with loop + delegate issues
* Don't re-use the existing connection if the remote_addr field of
  the play context has changed
* When overriding variables in PlayContext (from task/variables),
  don't set the same attribute based on a different variable name
  if we had already previously set it from another variable name

Fixes #13880
2016-01-21 15:33:44 -05:00
44ab6db535 Fix any_errors_fatal incorrect implementation in 2.0
Also adds that flag to blocks.

Fixes #13744
2016-01-21 15:32:33 -05:00
d9f03cf014 Update submodule refs to pull in doc fixes 2016-01-21 11:11:32 -08:00
c5e46efbd9 Avoid recursively checking JSON inventory for Unicode
by moving to en-bloc unicode conversion to act on scripts stdout

Both python-json and simplejson always return unicode strings when using
their loads() method on unicode strings. This is true at least since
2009. This makes checking each substring unnecessary, because we do not
need to recursively check the strings contained in the inventory dict
later one-by-one

This commit makes parsing of large dynamic inventory at least 2 seconds
faster.

cf: https://github.com/towolf/ansible-large-inventory-testcase
2016-01-21 08:43:52 -08:00
92579eb48f avoid shredding empty files, also x/0
also cleaned up unused import and exception var
2016-01-21 10:56:24 -05:00
d56e2fb80e corrected host/group match in inventory_hostnames
now the lookup works when using ! and & operators
fixes #13997
2016-01-20 18:33:00 -05:00
1f930f098f Update core submodule to bring in yum fix 2016-01-20 12:18:03 -08:00
63404821da rework run_command's env setting to not change os.environ for the rest of the module.
New param to run_command to modify the environment for just this invocation.
Documentation and comment adjustments.
2016-01-20 12:17:14 -08:00
cf75521bc8 updated submodule refs 2016-01-20 15:02:04 -05:00
50d34f90e7 clarified with_file 2016-01-19 21:20:54 -05:00
089f17bfc8 Don't tracback trying to retore settings
Can occur if we exit the timeout before the settings were changed
2016-01-19 14:11:16 -08:00
27bf701260 Set decoding of path to unicode to raise an exception if non-utf8
Letting it pass would just cause an error later on (no such file found)
so it's better to catch it here and know that we have users dealing with
non-utf8 pathnames than to have to track it down from later on.
2016-01-19 05:50:46 -08:00
299e42bc28 Make all parts of messages and pathnames into unicode so that we don't get UnicodeError tracebacks.
Note that the fix for display normalizing to unicode is correct but the
fix for pathnames is probably not.  Changing pathnames to unicode type
means that we will handle utf8 pathnames fine but pathnames can be any
sequence of bytes that do not contain null.  We do not handle sequences
of bytes that are not valid utf8 here.  To do that we need to revamp the
handling of basedir and paths to transform to bytes instead of unicode.
Didn't want to do that in 2.0.x as it will potentially introduce other
bugs as we find all the places that we combine basedir with other path
elements.  Since no one has raised that as an issue thus far so it's not
something we need to handle yet.  But it's something to keep in mind for
the future.

To test utf8 handling, create a utf8 directory and run a playbook from
within there.

To test non-utf8 handling (currently doesn't work as stated above), create
a directory with non-utf8 chars an run a playbook from there.  In bash,
create that directory like this: mkdir $'\377'

Fixes #13937
2016-01-19 05:45:56 -08:00
2f40903397 clarify role include paths 2016-01-19 08:48:55 -05:00
2ec31c2c79 fixed exception handling to be 2.4 compatible
previous 'fix' broke on 2.4
2016-01-19 08:33:37 -05:00
7afd8e9640 added missed ec2_vpc_net_facts 2016-01-18 22:06:05 -05:00
93781e0b05 be consistent about conversion to_str 2016-01-18 21:56:09 -05:00
edff4a9219 updated submodule refs 2016-01-18 21:15:30 -05:00
69b8a48ed1 Turn results that come from traceback messages into unicode, not str.
Fixes #13964
Fixes #13967
2016-01-18 14:44:57 -08:00
8f45660398 Update submodule refs 2016-01-18 13:59:46 -08:00
c8ffe94bf4 Fix proposed by @Yannig to fix become success detection when the output is multiline
See the Bug report for a specific error case with local connection,
sudo, and the raw module

Fixes #13728
2016-01-18 13:52:23 -08:00
c06b2e4622 correctly deals with non serializable type
combine_vars shoudl really be data types, but some just get in
in test, add dict to mock and avoid combine_vars using object
2016-01-18 15:18:18 -05:00
1394cdf6b9 Fix set_fact + run_once to assign variables to all hosts in the list
Fixes #13921
2016-01-18 11:55:39 -08:00
38a272b004 Allow module args as k=v pairs when using the module: option with local_action
This task format is valid in 1.x, but was broken in 2.x:
  - local_action:
     module: shell echo "hello world"
2016-01-18 11:55:30 -08:00
97a5804a2c Minor cleanup when reassigning play context to reused connections
* Relocate the assignment of the host address to the remote_addr field
  in the play context, which was only done when the connection was created
  (it's now done after the post_validate() is called on the play context)
* Make the assignment of the play context to the connection an else, since
  it's not required if the connection is not reused
2016-01-18 11:55:21 -08:00
8436a88b5d Fixes #13763 Update connections _play_context on every iteration
If this isnt updated, the _connection is reused, and thus has an outdated _play_context
This results in outdated `success_key` and `prompt` causing issues if sudo is run in a loop
Refer to the issue #13763 for more debugging and details
2016-01-18 11:55:12 -08:00
0bf20fab84 Fix erroneous fetch fail when fail_on_missing is set to False
Fixes #13832
2016-01-15 16:29:04 -08:00
22272fe5b8 Fix typos. 2016-01-15 15:10:44 -05:00
7d8afbd83d fix issue with add_hosts overwriting existing vars
fixes github.com/ansible/ansible-modules-core/issues/2799 and is alternative to #13841
2016-01-15 14:27:15 -05:00
f11e54bcff update core submodule ref 2016-01-15 09:36:08 -08:00
46a3a6a03e Non-newstyle modules can't use pipelining
This is because we pass arguments to non-newstyle modules via an
external file.  If we pipeline, then the interpreter thinks it has to
run the arguments as the script instead of what is piped in via stdin.
2016-01-14 17:24:25 -08:00
e29ae99b50 overwrite/merge not remove prev inventory data 2016-01-14 18:42:10 -05:00
363b74e498 Allow Ansible to return error with unicode within it.
Fix for https://github.com/ansible/ansible/issues/13899
2016-01-14 15:07:19 -08:00
17b60f4fbf Fix typo in 0b86aa6 2016-01-14 16:49:46 -05:00
b89f211ed4 Hack to work around callback API change for v2_playbook_on_start 2016-01-14 16:49:46 -05:00
cde5d8444d When setting fail state skip RESCUE/ALWAYS if cur_block doesn't have them
Fixes #13749
2016-01-14 15:59:40 -05:00
6cf0386a52 Tweak how strategies evaluate failed hosts via the iterator and bug fixes
* Added additional methods to the iterator code to assess host failures
  while also taking into account the block rescue/always states
* Fixed bugs in the free strategy, where results were not always being
  processed after being collected
* Added some prettier printing to the state output from iterator

Fixes #13699
2016-01-14 15:59:40 -05:00
b09f756195 now combine vars errors dump vars
very hard to debug w/o knowing what vars were being merged at the time of the error
2016-01-14 11:00:54 -05:00
27f9b9dc43 md5 now uses smaller salt
fixes #13891
2016-01-14 11:00:34 -05:00
31a951c4f7 only send event if tqm exists
fixes #13843
2016-01-14 11:00:18 -05:00
fe55e87ab4 Prevent traceback.
https://github.com/ansible/ansible/issues/13743#issuecomment-171520585

In some circumstance, the file fails to open.  When that occurs, we
can't try to close it in the finally clause.  Using a context manager is
the cleanest way to change the code to account for that case.
2016-01-13 20:52:22 -08:00
2ed31fc818 Add python-setuptools to the requirements for running ansible as
python-setuptools contains the egginfo needed to make pkg_resources
work.
2016-01-13 17:22:43 -08:00
be087575cc Fix circular import 2016-01-13 14:05:24 -08:00
79931041e2 do_encrypt import needed to move as well
Fixes #13861
2016-01-13 13:09:19 -08:00
832a739331 use integer division instead of floating point division.
Fixes #13855
2016-01-13 12:35:44 -08:00
b83ef89f09 Add windows/unix newline change to the changelog 2016-01-13 08:49:48 -08:00
419bd389d8 move core submodule ref for win_msi fix 2016-01-12 09:38:34 -08:00
e497e2b5c0 made loading of galaxy data files lazy 2016-01-12 12:28:47 -05:00
16ae952888 re-adding the dummy debian changlog entry for packaging 2016-01-12 12:20:23 -05:00
954eba8605 added galaxy data
not tested, but something like this seems to be missing in the Manifest.in
2016-01-12 11:31:54 -05:00
e4b701c00a added module name to missing interpreter error
fixes #13816
2016-01-12 11:26:18 -05:00
21c22a2416 Allow InventoryScript JSON with childgroups only
and without hosts and vars

Without this patch, the simplified syntax is triggered when a group
is defined like this:

    "platforms": {
        "children": [
            "cloudstack"
        ]
    }

Which results in a group 'platforms' with 1 host 'platforms'.

more details in https://github.com/ansible/ansible/issues/13655
2016-01-12 11:26:11 -05:00
4e98cc351c Update submodule refs so that new integration tests will pass 2016-01-12 08:02:47 -08:00
f53cb5f63e Integration tests for https://github.com/ansible/ansible-modules-core/pull/558 2016-01-12 08:02:05 -08:00
1cae019c79 cloudstack: fix case insensitivity
cloudstack: fix has_change reports changed for case insensitivity values
2016-01-12 08:02:05 -08:00
f3470c1062 cleaner implementation and random chunk length. 2016-01-12 08:02:05 -08:00
21a4c1380c use unix shred if possible, otherwise fast custom impl; do not shred encrypted file 2016-01-12 08:02:05 -08:00
4811504a6e add docs, remove unnecessary int() cast 2016-01-12 08:02:05 -08:00
08fee9bfc5 add os.fsync() so that the shredding data (hopefully) hits the drive 2016-01-12 08:02:05 -08:00
2cb2994371 shred ansible-vault tmp_file. Also when editor is interruped. 2016-01-12 08:02:05 -08:00
2eb59bedcf New release v2.0.0.0-1 2016-01-12 08:43:33 -05:00
14bf6b016c Split up comma-separated tags properly
Fixes #13795
2016-01-12 08:18:37 -05:00
28fecc9ce1 bugfix in nxos shared module for including defaults 2016-01-11 22:49:01 -05:00
2c647f18e5 bugfix in ios shared module for including defaults 2016-01-11 22:48:54 -05:00
4e087bb14f bugfix in eos shared module for including defaults 2016-01-11 22:48:43 -05:00
1f7fbf29f9 updated new windows module list in changelog 2016-01-11 16:24:30 -08:00
0681c2fd8f deletes nxapi from shared modules
The nxapi module has been superseded by the nxos shared module and is not longer needed. This commit removes (deletes) nxapi from module_utils.  All custom modules that have used nxapi should be using nxos instead.
2016-01-10 15:13:18 -05:00
4a6235e320 adds network config file parser to shared modules
This commit adds a new shared module that parses network device configuration
files.  It is used to build modules that work with the various supported
network device operating systems
2016-01-10 15:12:45 -05:00
94f13c7271 adds shared module shell for creating cli based transports
This commit add a new shared module shell that is used to build connections
to network devices that operate in a CLI environment.  This commit supercedes
the issh.py and cli.py commits and removes them from module_utils.
2016-01-10 15:12:29 -05:00
93eb60161c initial add of openswitch shared module
This commit adds a new shared module openswitch for building modules that
work with OpenSwitch.  This shared module supports connectivity to
OpenSwitch devices over SSH, CLI or REST.  It also adds an openswitch
documentation fragment for use in modules
2016-01-10 15:12:15 -05:00
2f1fc85002 adds shared module nxos for building cisco nxos modules
This commit refactors the nxapi into a new shared module nxos that supports
connectivity over both ssh (cli) and nxapi.  It supercedes the nxapi shared
module and removes it from module_utils.  This commit also adds a
documentation fragement supporting the nxos shared module
2016-01-10 15:12:05 -05:00
91f35363a2 adds new iosxr shared module for developing modules that work with IOS XR devices
This commit adds a new shared module for working with Cisco IOS XR devices over
CLI (SSH).  It also provides a documentation fragement for the commmon arguments
provided by the iosxr module.
2016-01-10 15:11:55 -05:00
f76633b347 updates the ios shared module with new shell
This update refactor the ios shared module to use the new shell shared
library instead of issh and cli.  It also adds the ios documentation
fragment to be used when building ios based modules.
2016-01-10 15:11:37 -05:00
9c9bfb962e initial add of eos shared module
This adds a shared module for communicating with Arista EOS devices over
SSH (cli) or JSON-RPC (eapi).  This modules replaces the eapi.py module
previously added to module_utils.  This commit includes a documentation
fragment that describes the eos common arguments
2016-01-10 15:11:08 -05:00
0e27fa4540 restructure vars_prompt and fix regression
pushed it to use the existing propmpt from display and moved the vars prompt code there also for uniformity
changed vars_prompt to check extra vars vs the empty play.vars to restore 1.9 behaviour
sipmlified the code as it didn't need to check for syntax again (tqm is made none prior based on that)
fixes #13770
2016-01-08 17:56:13 -05:00
3a972c9170 Restore ability for a module to specify WANT_JSON 2016-01-08 12:49:08 -05:00
91c7691a92 noted that regex_escape was added in 2.0
fixes #13759
2016-01-07 19:02:08 -05:00
502ad88506 New release v2.0.0-0.9.rc4 2016-01-07 13:41:47 -05:00
80f0380312 Fix typo 2016-01-07 06:01:44 -08:00
a8f160d2fd More fixes for unicode handling in the connection plugins.
Tested that ssh, docker, local, lxc-libvirt, chroot all work with the
updated unicode integration test.
2016-01-07 06:01:34 -08:00
6be48dd14c Revert "Show version without supplying a dummy action"
This reverts commit 21775d7866.
Parsing before action will fail if one of the action specific options is used
As per issue #13743
2016-01-07 08:29:43 -05:00
fcd074d40f Merge branch 'ktosiek-fix-playbook-hanging' into stable-2.0 2016-01-06 14:01:35 -05:00
21775d7866 Show version without supplying a dummy action
fixes #12004
parsing x2 does not seem to break anything
2016-01-06 11:54:19 -05:00
ab536c8aa8 Strip string terms before templating
The earlier code did call terms.strip(), but ignored the return value
instead of passing that in to templar.template(). Clearly an oversight.
2016-01-06 11:25:55 -05:00
93f37f5969 Don't drop noops from task counting code in linear strategy 2016-01-06 09:58:20 -05:00
ec3b7b7de8 linear strategy: don't look at tasks from the next block 2016-01-05 21:49:08 +01:00
7a4914aa9b Fix exception catching to be importable on python3 2016-01-05 12:04:22 -08:00
9ca5da82ff move hostvars.vars to vars
this fixes duplication under hostvars and exposes all vars in the vars dict
which makes dynamic reference possible on 'non hostvars'
2016-01-05 14:36:21 -05:00
3d608ef9fa simplified diff handling in callback
no need for the copy or other complexity
2016-01-05 14:27:33 -05:00
c14e099dd7 now handles 'non file diffs'
this allows modules to pass back a 'diff' dict and it will still show using the file interface
2016-01-05 14:27:33 -05:00
7e318e8398 Update extras submodule ref 2016-01-05 07:52:44 -08:00
add2e9cbd1 Fix problems with non-ascii values passed as part of the command to connection plugins
@drybjed discovered this with non-ascii environment variables and
command line arguments to script and raw module.
2016-01-05 07:46:24 -08:00
9f93c9c84b Clean up debug logging around _low_level_execute_command
We were logging the command to be executed many times, which made debug
logs very hard to read. Now we do it only once.

Also makes the logged ssh command line cut-and-paste-able (the lack of
which has confused a number of people by now; the problem being that we
pass the command as a single argument to execve(), so it doesn't need an
extra level of quoting as it does when you try to run it by hand).
2016-01-05 07:46:13 -08:00
b4e0b5503c move core submodule pointer 2016-01-04 16:10:29 -08:00
5536ddd118 Do not set 'changed' to True when using group_by
Since group_by is not changing in any way to the remote
system, there is no change. This also make things more consistent
with the set_fact plugin.
2016-01-04 16:23:25 -05:00
021ed1aa8b Replace to_string by to_unicode.
Fix https://github.com/ansible/ansible/issues/13707
2016-01-01 08:18:18 -08:00
a703f3a6d2 added newer vars to 'reset_vars'
these vars pass back info to the task about the connection
moved to their own block at start at file for readability and
added the newer standard vars
2015-12-28 12:50:04 -05:00
f7ea8b32a9 minor fix to become docs 2015-12-28 10:25:35 -05:00
ba7d2db8ad clarify idempotence explanation
Small typo; wasn't sure what to replace it with.
2015-12-28 10:24:55 -05:00
b50ed10a84 updated release cycle to 4 months instead of 2 2015-12-27 14:17:58 -05:00
b4fae25a96 CHANGELOG entry for bigip* validate_certs change 2015-12-25 12:24:19 -08:00
6142736946 Oops, core needs to stay on stable-2.0 2015-12-25 12:16:18 -08:00
25fc4217df Update submodule refs 2015-12-25 12:15:49 -08:00
d71e8fb870 bigip changes as requested by bcoca and abadger:
* Fix to error if validate_cert is True and python doesn't support it.
* Only globally disable certificate checking if really needed.  Use
  bigip verify parameter if available instead.
* Remove public disable certificate function to make it less likely
  people will attempt to reuse that
2015-12-25 12:15:24 -08:00
369ed9feed cloudstack: test_cs_instance: more integration tests
cloudstack: extend test_cs_instance addressing recovering

cloudstack: test_cs_instance: add tests for using display_name as indentifier.
2015-12-23 17:48:49 -08:00
ebd3b35d02 Update submodule refs 2015-12-23 14:29:36 -08:00
82df9041e7 Going to do this in the connection plugin
Revert "Transform the command we pass to subprocess into a byte string in _low_level-exec_command"

This reverts commit 6d76cb40c5.
2015-12-23 13:28:42 -08:00
dd59fc176e Going to do this in the connection plugin
Revert "Fix make tests-py3 on devel. Fix for https://github.com/ansible/ansible/issues/13638."

This reverts commit 725e40c5e6.
2015-12-23 13:28:42 -08:00
9af054addf Going to do this in the connection plugin
Revert "Convert to bytes later so that make_become_command can jsut operate on text type."

This reverts commit bfc082fb07.
2015-12-23 13:28:42 -08:00
7f29cb9dc6 corrected role path search order
the unfraking was matching roles in current dir as it always returns a full path,
pushed to the bottom as match of last resort
fixes #13645
2015-12-23 15:15:58 -05:00
2786908bac fixed tests to follow new invocation structure
also added maxdiff setting to see issues clearly when they happen
2015-12-23 11:45:50 -05:00
43331d8c31 better module error handling
* now module errors clearly state msg=MODULE FAILURE
* module's stdout and stderr go into module_stdout and module_stderr keys
which only appear during parsing failure
* invocation module_args are deleted from results provided by action
plugin as errors can keep us from overwriting and then disclosing info that
was meant to be kept hidden due to no_log
* fixed invocation module_args set by basic.py as it was creating different
keys as the invocation in action plugin base.
* results now merge
2015-12-23 10:59:01 -05:00
579a2ff739 fix no_log disclosure when using aliases 2015-12-22 17:17:06 -05:00
bfc082fb07 Convert to bytes later so that make_become_command can jsut operate on text type.
Conflicts:
	lib/ansible/plugins/action/__init__.py
2015-12-22 08:33:36 -08:00
725e40c5e6 Fix make tests-py3 on devel. Fix for https://github.com/ansible/ansible/issues/13638. 2015-12-22 08:33:36 -08:00
8216a659fa Also convert ints to bool for type=bool 2015-12-22 10:25:25 -05:00
a2120a3d63 Version bump for 2.0.0-0.8.rc3 2015-12-21 17:59:27 -05:00
6d76cb40c5 Transform the command we pass to subprocess into a byte string in _low_level-exec_command 2015-12-21 13:55:06 -08:00
b0e7ea78af Actually disable parallel makes for integration runner 2015-12-21 16:12:27 -05:00
6a0d2116b8 Delete new_inventory from stable-2.0 as it isn't used there.
leaving in devel for now as some people are planning on using it as
a starting place to update inventory in 2.1
2015-12-21 13:11:16 -08:00
37908735d4 Dropping instance size back down since we're not doing parallel builds 2015-12-21 15:55:16 -05:00
c0248873da Integration test runner tweaks 2015-12-21 15:53:19 -05:00
75695f5c70 Kick up the integration runner test image size 2015-12-21 15:53:19 -05:00
0a6bc57fa5 Parallelize make command for integration test runner
Also adds a new var, used by the prepare_tests role, to prevent it from
deleting the temp test directory at the start of each play to avoid any
potential race conditions
2015-12-21 14:09:02 -05:00
1d18964daa Merge role params into variables separately from other variables
Fixes #13617
2015-12-21 13:55:17 -05:00
8eb3963cd9 Adding documentation for the 'dig' lookup (#13126). 2015-12-21 13:49:49 -05:00
c605cd37f6 allow for non standard hostnames
* Changed parse_addresses to throw exceptions instead of passing None
* Switched callers to trap and pass through the original values.
* Added very verbose notice
* Look at deprecating this and possibly validate at plugin instead
fixes #13608
2015-12-21 13:42:47 -05:00
e1698fb4bf role search path clarified 2015-12-21 13:16:48 -05:00
81f09f3fbd Disable docker test for Fedora, due to broken packaging 2015-12-21 10:58:49 -05:00
b77d834239 Uncomment docker test for stable-2.0 2015-12-21 10:58:35 -05:00
7607e55a5a Save output of integration test results to files we can archive 2015-12-21 10:58:02 -05:00
d512717cd2 Fix logic in PlayIterator when inserting tasks during rescue/always
Because the fail_state is potentially non-zero in these block sections,
the prior logic led to included tasks not being inserted at all.

Related issue: #13605
2015-12-21 10:57:24 -05:00
ce17557b84 Fixed import typo for memcache module in tests.
The typo caused the test for the memcached cache plugin to be skipped
even when the necessary memcache python module was installed.
2015-12-21 10:27:36 -05:00
80e109e1ad And change the task a little more since different shlex versions are handling the quotes differently 2015-12-20 11:52:50 -08:00
fdc562e3c3 Fix test playbook syntax 2015-12-20 11:47:17 -08:00
1dcfd7ba02 Since the velox test server seems to be dropping using iptables to drop requests from aws, test via a different website instead 2015-12-20 11:38:54 -08:00
9cfa2d7e28 Fixing bugs in conditional testing with until and some integration runner tweaks 2015-12-20 12:45:33 -05:00
5bd01c09d6 Troubleshooting has reduced us to this 2015-12-20 12:45:28 -05:00
61bd0e1310 Fix the fedora host detection 2015-12-20 12:45:09 -05:00
bb1047c483 What is going on here 2015-12-20 12:45:03 -05:00
c2a29a01a2 Removing update all for test deps, it didn't fix the problem 2015-12-20 12:44:20 -05:00
6a1ebaa1bc Fix typo in integration test runner role 2015-12-20 12:44:10 -05:00
1d1a04008e Update mysql setup to handle installing mysql with dnf too. 2015-12-20 12:43:47 -05:00
1cde02058f Make integration tests run in parallel with async 2015-12-20 12:41:53 -05:00
48675550bf Try updating the centos7 image to a newer version (trying to resolve issue being unable to connect to some webservers) 2015-12-20 09:13:03 -08:00
3955ea5a8a Fixes for tests that assumed yum as package manager for systems that
have dnf
2015-12-20 08:11:05 -08:00
454c8ff5b8 Switch from yum to package when installing sudo so that dnf is handled as well 2015-12-20 08:10:58 -08:00
ba0b4afb6b Revert "removed invocation info as it is not no_log aware"
This reverts commit 6127a8585e.
2015-12-19 15:51:29 -08:00
76507bd567 Make return invocation information so that our sanitized copy will take precedence over what the executor knows. 2015-12-19 15:51:22 -08:00
d2bf615780 Fix unittests for return of invocation from fail_json and exit_json 2015-12-19 15:50:43 -08:00
3840fb267c corrected service detection in docker versions
now if 1 == bash it falls back into tool detection
2015-12-19 16:16:28 -05:00
fbccfc8b61 Add package module to squash list 2015-12-19 13:01:48 -08:00
2d179b376b Remove args from get_name() as we can't tell if any of the args are no_log 2015-12-19 11:51:51 -08:00
a0add9dda5 Comment to explain why we strip _ansible_notify specially 2015-12-19 11:32:31 -08:00
de9517dcc8 Also need redhat-rpm-config to compile pycrypto 2015-12-19 11:27:55 -08:00
bef2b70eae Make sure that yum is present on redhat family systems (makes things also work on fedora systems where dnf is the default) 2015-12-19 11:24:27 -08:00
df08ba37fe removed invocation info as it is not no_log aware
This was added in 1.9 and 2.0 tried to copy, but since it cannot
obey no_log restrictions I commented it out. I did not remove as
it is still very useful for module invocation debugging.
2015-12-19 11:45:59 -05:00
6dc42e376f update submodule refs 2015-12-18 22:18:25 -08:00
9689f00bb1 Add a Fedora latest host into the mix 2015-12-18 22:15:29 -08:00
2f0e8d9654 Make integration runner ec2 add_hosts use valid host names 2015-12-18 16:04:54 -08:00
6d9235e36d Fix the fedora host detection 2015-12-18 15:43:39 -08:00
eb606cc18b What is going on here 2015-12-18 15:43:32 -08:00
85ef768d51 Bugfix the fedora 23 install task 2015-12-18 14:39:27 -08:00
457e32128f Ubuntu images with hvm ssd 2015-12-18 14:39:20 -08:00
e8ce341f9c Fedora 23 needs to have python2 packages installed 2015-12-18 14:08:43 -08:00
87214005d2 Make tests that use kennethreitz retry. 2015-12-18 11:51:16 -08:00
dc0a4e74d6 added note about add_hosts 2015-12-18 13:57:58 -05:00
43409fa16c added missing features to changelog 2015-12-18 12:15:08 -05:00
b912923ef2 Do a full yum update to make sure packages are latest version
For the deps setup of integration tests, as we sometimes see odd
errors we can't reproduce, which may be related to slightly out of
date package dependencies.
2015-12-18 08:51:57 -08:00
46bc8253e1 Add awk to integration test deps list 2015-12-18 08:51:42 -08:00
1f70fc6424 Add state=latest to pip install of pycrypto 2015-12-18 08:51:34 -08:00
ed4ad5f6fb Add ca-certificates update to the integration deps playbook 2015-12-18 08:51:25 -08:00
69eb22c652 Install an updated version of pycrypto on Ubuntu12 from pip 2015-12-18 08:51:14 -08:00
dca880d04a kennetreitz.org times out but www.kennethreitz.org is fine 2015-12-18 08:34:07 -08:00
0c6364c771 debug line needs var not msg 2015-12-18 08:33:55 -08:00
c9cf07e3b5 Fixing bugs with {changed,failed}_when and until with registered vars
* Saving of the registered variable was occuring after the tests for
  changed/failed_when.
* Each of the above fields and until were being post_validated too early,
  so variables which were not defined at that time were causing task
  failures.

Fixes #13591
2015-12-18 11:08:06 -05:00
bcd66059ae Use --source instead of -e for awk in integration Makefile 2015-12-17 20:53:43 -05:00
72e6dbcd12 Fix get_url tests in light of distros backporting SNI support 2015-12-17 17:53:38 -08:00
184c2985fc Consolidating package lines for virtualenv install in test deps integration 2015-12-17 17:46:12 -05:00
4abbe8a989 Moving apt cache update to top to ensure cache is updated before deps installed 2015-12-17 17:46:12 -05:00
ce159ce2eb Switch virtualenv dep installation from pip to package manager 2015-12-17 17:46:12 -05:00
fdb94b9a7a Adding pip install of virtualenv to test deps integration role 2015-12-17 17:45:59 -05:00
be0e826091 remove .gitignore 2015-12-17 14:18:12 -08:00
f12ed5eba8 symbolic link role for testing 2015-12-17 14:18:06 -08:00
b1d5e9ff3c removed ansible_python_interpreter
* added missed renames of ansible_deps to ansible_test_deps
* removed acidential inventory.dynamic file
* modified README for ansible_test_deps role
2015-12-17 14:17:58 -08:00
dcb732b416 rename role ansible_deps to ansible_test_deps 2015-12-17 14:17:49 -08:00
ad6ec9610e playbook that Ansible jenkins runs moved into core
The playbook is already running in jenkins and works. This moves the
assets into core for ease of maintenance going forward.
2015-12-17 14:17:41 -08:00
fcceac5ff2 trigger jenkins integration tests 2015-12-17 14:17:35 -08:00
0a319a28f2 Update submodule for mysql_user fix 2015-12-17 13:47:07 -08:00
17fe156016 Fixing a mistake from tweaking list stuff too much
Use the action only if the task name is not set
2015-12-17 16:34:03 -05:00
5824a6d9bb Further tweaks to the output format of list tasks/tags 2015-12-17 16:31:02 -05:00
59cac03d3b Make --list-tasks respect tags
Also makes the output closer to the appearance of v1

Fixes #13260
2015-12-17 16:11:00 -05:00
cc2e0c1e3d Update submodule to pull in mysqldump fix 2015-12-17 13:02:11 -08:00
624556eb16 Add recent bugfixes to changelog 2015-12-17 10:35:24 -08:00
64737c80f9 Update submodule refs 2015-12-17 10:21:50 -08:00
5e9d182229 changed test to use filter for accurate reporting 2015-12-17 12:28:17 -05:00
1f7ed5d8ee Fix handling of environment inheritence, and template each inherited env
Environments were not being templated individually, so a variable environment
value was causing the exception regarding dicts to be hit. Also, environments
as inherited were coming through with the tasks listed first, followed by the
parents, so they were being merged backwards. Reversing the list of environments
fixed this.
2015-12-17 09:46:41 -05:00
f41dd578b3 Make sure we're using the original host when processing include results
Also fixes a bug where we were passing an incorrect number of parameters to
_do_handler_run() when processing an include file in a handler task/block.

Fixes #13560
2015-12-16 19:13:03 -05:00
407d76b8d5 Fixing template integration test for python 2.6 versions
No longer immediately fallback to to_json if simplejson is not installed
2015-12-16 18:24:12 -05:00
d2c43c421f Update core module ref for mysql_user fix 2015-12-16 14:10:00 -08:00
6c509b5364 Updating the porting guide to note the complex args/bare vars change
Related to #13518
2015-12-16 16:37:55 -05:00
4bce9947e5 fixed formating issues with rst 2015-12-16 16:37:55 -05:00
4ad7135690 Add first draft of porting guide for 2.0 2015-12-16 16:37:55 -05:00
bf9f6ce09c All variables in complex args again
Also updates the CHANGELOG to note the slight change, where bare variables
in args are no longer allowed to be bare variables

Fixes #13518
2015-12-16 16:37:55 -05:00
84a6b543a9 ensure additional args are a dict
They should already be templated before hitting this point?
otherwise the class needs the task variables passed into it.
relates to #13518 and  https://groups.google.com/forum/#!msg/ansible-project/esoBcphHwHs/Ghgi_M-MCQAJ
2015-12-16 15:32:26 -05:00
6cf7b792a3 Fix docs. The search command works with both galaxy.ansible.com and galaxy-qa.ansible.com. 2015-12-16 15:11:45 -05:00
abec9331d8 Fixed documentation typos and bits that needed clarification. Fixed missing spaces in VALID_ACTIONS. 2015-12-16 15:11:41 -05:00
ba16f2ae95 Add a section to intro_configuration for Galaxy. 2015-12-16 15:11:34 -05:00
1e605d727c Define and handle ignore_certs correctly. Preserve search term order. Tweak to Galaxy docsite. 2015-12-16 15:11:30 -05:00
6c48d52b4a Make sure it is clear that new commands require using the Galaxy 2.0 Beta site. 2015-12-16 15:11:26 -05:00
8bb598daa2 Updated ansible-galaxy man page. Removed -b option for import. 2015-12-16 15:11:22 -05:00
21edd6a5ab Fix overloaded options. Show an error when no action given. Don't show a helpful list of commands and descriptions. 2015-12-16 15:11:05 -05:00
e10e03f4d9 added releases doc 2015-12-16 15:10:48 -05:00
d5bdea0bab fix plugin loading for Windows modules
force plugin loader to only consider .py files, since that's the only place docs can live ATM...
2015-12-16 11:47:52 -08:00
cd1bdaa61d Update submodule refs for mysql refactor 2015-12-16 11:14:39 -08:00
9f69b6b585 Add shared connection code for mysql modules 2015-12-16 11:06:21 -08:00
9e52a7c769 Attempt at fixing strategy unit test failures on py2.6 and py3 2015-12-16 13:57:13 -05:00
8fd438000a Update url to site that has an invalid certificate 2015-12-16 09:47:56 -08:00
54ce8327cb Disabling docker test for stable-2.0 due to versioning issues 2015-12-16 11:24:29 -05:00
82519ab2a6 Preserve the cumulative path for checking includes which have parents
Otherwise, each relative include path is checked on its own, rather
than in relation to the (possibly relative) path of its parent, meaning
includes multiple level deep may fail to find the correct (or any) file.

Fixes #13472
2015-12-16 11:23:33 -05:00
5761c333a6 Update submodule ref to fix docs build 2015-12-16 08:13:54 -08:00
72b852a814 Update submodule refs 2015-12-16 08:03:34 -08:00
897569c1ca Conditionally create the CustomHTTPSConnection class only if we have the required baseclasses.
Fixes #11918
2015-12-16 08:02:11 -08:00
8e2cb2abb2 Fixes for proxy on RHEL5 2015-12-16 08:02:03 -08:00
b8d9b106de First attempt to fix https certificate errors through a proxy with python-2.7.9+
Fixes #12549
2015-12-16 08:01:56 -08:00
f68c0043de Fix typo 2015-12-16 10:53:10 -05:00
48fb0cdecb debug now validates its params
simplified var handling
made default message the same as in pre 2.0
fixes #13532
2015-12-16 10:42:30 -05:00
28602cddb0 Use the original host rather than the serialized one when processing results
Fixes #13526
Fixes #13564
Fixes #13566
2015-12-16 01:50:14 -05:00
5011d63f7e Fix a part of python 3 tests (make tests-py3, see https://github.com/ansible/ansible/issues/13553 for more details). 2015-12-15 07:57:05 -08:00
5ab25e2dfe clean debug output to match prev versions 2015-12-15 09:28:39 -05:00
6e1bb6c87d renamed ssh.py shared module file to clarify 2015-12-15 08:44:43 -05:00
ac7dc6bd81 the ssh shared module will try to use keys if the password is not supplied
The current ssh shared module forces only password based authentication.  This
change will allow the ssh module to use keys if a password is not provided.
2015-12-15 08:39:17 -05:00
b6dac26224 Make module_utils.known_hosts.get_fqdn work on ipv6 2015-12-14 11:10:02 -08:00
a7a3a34987 Add tests for ansible.module_utils.known_hosts 2015-12-14 11:09:49 -08:00
6df187da84 Fix for template module not creating a file that was not present when force=false 2015-12-14 10:57:34 -08:00
cf95619229 Optionally only use UUIDs for openstack hosts on duplicates
The OpenStack inventory lists hostnames as the UUIDs because hostsnames
are not guarnateed to be unique on OpenStack. However, for the common
case, this is just confusing.

The new behavior is a visible change, so make it an opt-in via config.

Only turn the hostnames to UUIDs if there are duplicate hostnames.
2015-12-14 13:27:33 -05:00
17a37844d4 Fix the refresh flag in openstack inventory
Refresh will update the dogpile cache from shade, but doesn't cause
the ansible side json cache to be invalidated. It's a simple oversight.
2015-12-14 13:27:33 -05:00
gp
0697c6e59d Fix typo in galaxy.rst
Fix typo
2015-12-14 09:15:15 -08:00
66be9d06c4 Minor: Correct type pyhton => python 2015-12-14 08:51:33 -08:00
62b2c9eb4e Update submodule refs 2015-12-14 08:34:49 -08:00
887319f95c add tests for encrypted hash mysql_user 2015-12-14 08:02:11 -08:00
fc233bf3f4 Use an octal representation that works from 2.4->3+ for known_hosts 2015-12-14 10:44:22 -05:00
8dcba63022 Fixing up some non-py3 things for unit tests 2015-12-14 10:44:22 -05:00
60f5fe76a1 use default settings from ansible.cfg 2015-12-14 09:19:15 -05:00
0b66ec0ddd Cleanup strategy tests broken by new forking strategy 2015-12-14 03:07:51 -05:00
e7bf204db4 A few tweaks to improve new forking code 2015-12-14 03:07:48 -05:00
c8e6461dee Changing the way workers are forked 2015-12-14 01:16:55 -05:00
3214ef8832 added that ansible-pull is now shallow to changelog 2015-12-13 12:18:28 -05:00
6a5444c2fe add --full flag to ansible-pull man page
add --full flag that was added in #13502
2015-12-13 12:17:36 -05:00
d4e06695fd Fix Doc mistake
Fix Doc mistake in ansible/docsite/rst/playbooks_variables.rst
2015-12-13 09:50:45 -05:00
d369e3ec72 fix whitespace 2015-12-13 09:37:48 -05:00
bff8db3c8f make shallow clone the default for ansibel-pull 2015-12-13 09:37:48 -05:00
8c6010cc86 add depth option to ansible-pull
Allows shallow checkouts in ansible-pull by adding `--depth 1` (or higher number)
2015-12-13 09:37:48 -05:00
13ffd3ee2d include all packaging in tarball
not juse rpm spec file
2015-12-13 00:34:56 -05:00
f9bff8e488 simplified skippy
thanks agaffney!
2015-12-12 17:51:25 -05:00
6d83bf8ca9 changed shell delimiters for csh
fixes #13459
2015-12-12 16:13:20 -05:00
17f06dd310 removed unused imports in galaxy/cli 2015-12-12 13:43:46 -05:00
82f893d65a Explain how 'run_once' interacts with 'serial' 2015-12-12 00:33:21 -05:00
40b55eda4c avoid set to unique hosts to preserver order
swiched to using a list comp and set to still unique but keep expected order
fixes #13522
2015-12-11 15:40:48 -05:00
7678ab05d8 removed merge conflict 2015-12-11 15:10:48 -05:00
5b801afa38 Don't mark hosts failed if they've moved to a rescue portion of a block
Fixes #13521
2015-12-11 14:58:38 -05:00
71cc791677 narrow down exception catching in block builds
this was obscuring other errors and should have always been narrow scope
2015-12-11 13:13:19 -05:00
3d1b3e2bb8 removed 'bare' example in environment
now shows how to use explicit templating
2015-12-11 09:35:19 -05:00
b0e5556d9a fix make complaint when git is not installed 2015-12-10 21:50:11 -05:00
75e6fb30d5 Fixing up docker integration tests a bit 2015-12-10 13:10:40 -05:00
9b5ec8f359 Update submodule ref 2015-12-10 08:10:13 -08:00
2dbcd8fc9e become_pass needs to be bytes when it is passed to ssh.
Fixes #13240
2015-12-10 07:30:20 -08:00
82789df231 allow custom callbacks with adhoc cli for scripting
missing import of CallbackBase
2015-12-10 07:05:41 -08:00
3fafc6d655 Missed one place we were appending the incorrectly escaped item to raw params 2015-12-09 17:59:06 -05:00
9b864811f8 Note that handlers inside of includes are not possible at the moment 2015-12-09 13:53:04 -08:00
ba62a8d559 Windows doc updates 2015-12-09 16:36:23 -05:00
b793fbc60a Remove the funcd connection plugin 2015-12-09 13:30:37 -08:00
7e6d546632 added winrm CP notes to changelog 2015-12-09 15:50:37 -05:00
45d2f59858 changed deprecation to removal warning 2015-12-09 12:39:11 -08:00
dd6e10ce43 Update submodule refs 2015-12-09 12:11:30 -08:00
6694e97a31 Make on_file_diff callback item-aware 2015-12-09 14:52:06 -05:00
699bb847a0 reenabled --tree for ansible adhoc command
previous fix to avoid callbacks now conflicted with tree optoin
which is implemented as a callback in 2.0
2015-12-09 10:14:43 -08:00
c02ca22894 adhoc avoids callbacks by default as it did before
Previous emptying of whitelist only affected callbacks that were
constructed for need whitelist. This now works for all callbacks.
2015-12-09 10:03:39 -08:00
1f9d66ef60 Clarify language of delegate_facts documentation 2015-12-09 08:46:49 -08:00
8f6f2fc920 Code smell test for specifying both required and default in FieldAttributes 2015-12-09 08:46:49 -08:00
4d7710806e attribute defaults that are containers are a copy
This is simpler way to prevent persistent containers across instances
of classes that use field attributes
2015-12-09 08:42:14 -08:00
c2665da3e8 removed unused 'pattern' from ansible.cfg
also moved the config param to a 'deprecated' list in constants.py
added TODO for producing a deprecation warning for such vars
2015-12-09 08:42:14 -08:00
186259eb80 removed default from hosts to make it requried
prevents writing a play w/o a hosts entry which would default to
all/empty
2015-12-09 08:42:14 -08:00
e571bf94d4 Revert "avoid persistent containers in attribute defaults"
This reverts commit 87969868d4.
found better way to do it
2015-12-09 08:42:14 -08:00
565b5097da clarified warning from tree callback 2015-12-09 08:40:56 -08:00
0a6f8b2bf3 Galaxy 2.0 2015-12-09 11:30:05 -05:00
f649dc71e7 avoid persistent containers in attribute defaults
moved from the field attribute declaration and created a placeholder
which then is resolved in the field attribute class.

this is to avoid unwanted persistent of the defaults across objects which introduces
stealth bugs when multiple objects of the same kind are used in succession while
not overriding the default values.
2015-12-09 11:11:49 -05:00
d93e1f4ccf added delegate_facts docs 2015-12-08 14:18:11 -08:00
9eb9f55a31 keep string type filters as strings
now we don't try to convert types if using a filter that outputs a specifically formated string
made list of filters configurable
2015-12-08 12:52:44 -08:00
dbcfce03d2 Add ssh_host support for MacOSX El Capitan.
OS X El Capitan moved the /etc/ssh_* files into /etc/ssh/. This fix
adds a distribution version check for Darwin to set the keydir
appropriately on El Capitan and later.
2015-12-08 15:36:49 -05:00
b3cfb630dc Make "make webdocs" compatible with Python 3
The `webdocs` make target fails under Python 3. It fails due to a variety of
syntax errors, such as the use of `except Foo, e` and `print 'foo'`. Fix #13463
by making code compatible with both Python 2 and 3.
2015-12-08 15:31:35 -05:00
058e02137a Preserve original token when appending to _raw_params in parse_kv
Fixes #13311
2015-12-08 15:06:01 -05:00
666cb07614 fixed typo in tree callback, added default dir
this would allow it to work with playbooks also
2015-12-08 11:59:51 -08:00
df04955572 updated with delegate_facts directive 2015-12-08 11:53:36 -08:00
422092b8bc Fix typo from 5ae850c 2015-12-08 14:34:37 -05:00
5ae850c3b2 Make fact delegating configurable, defaulting to 1.x behavior 2015-12-08 14:00:17 -05:00
7c8e1b41bb Revert "Fix always_run support in the action plugin for template when copying"
This reverts commit 45670eff81.
2015-12-08 09:26:24 -08:00
43bfd16666 have always_run override check mode for a task
Fixes #13418
2015-12-08 09:26:13 -08:00
45670eff81 Fix always_run support in the action plugin for template when copying
Fixes #13418
2015-12-08 11:55:35 -05:00
50e5b0f8e9 Merge pull request #13467 from bcoca/adhoc_callbk_fix
adhoc does not load plugins by default
2015-12-08 11:28:20 -05:00
e69064d0fc Merge pull request #13451 from bcoca/doas_fix
fixed doas from getting stuck when needing passwords
2015-12-08 11:13:01 -05:00
1aa775196b adds new device argument to nxapi command arguments
The device argument allows a dict of nxapi parameters to be passed to
the module to simplify passing the nxapi parameters
2015-12-08 10:36:47 -05:00
b07451eef8 adhoc does not load plugins by default
reimplemented feature from 1.x which kept additional callbacks from
poluting adhoc unless specifically asked for through configuration.
2015-12-08 06:37:15 -08:00
cc98528ecb Version bump for 2.0.0-0.7.rc2 2015-12-07 13:13:37 -05:00
f241c70740 corrected usage of ec2.py's profile option
this was never introduced into ansible-playbook though the docs
stated otherwise. We still explain how to use the env var to get the
same result.
2015-12-07 09:56:05 -08:00
2ed2c12f60 Fix issue when var name is the same as content.
See https://github.com/ansible/ansible/issues/13453 for more details.
2015-12-07 10:08:13 -05:00
d85b8adba6 More meaningful string representation for meta tasks (like 'noop' and 'flush_handlers') 2015-12-07 10:08:13 -05:00
8e16b481d0 added extract filter to changelog 2015-12-07 10:08:13 -05:00
d89dbf19fb bugfix for ios.py shared module argument creation
This patch fixes a bug in module_utils/ios.py where the the wrong shared
module arguments are being generated.  This bug prevented the shared module
from operating correctly.  This patch should be generally applied.
2015-12-07 06:59:43 -08:00
40c01f3739 Use self.args when we parse arguments that way the arguments can be constructed manually 2015-12-06 22:19:11 -08:00
71ffa5abdc Add representers so we can output yaml for all the types we read in from yaml 2015-12-06 22:19:02 -08:00
0533e0bc96 fixed doas from getting stuck when needing passwords
Also adjusted test to match new doas become output
fixes #13449
2015-12-06 00:35:28 -05:00
3974b13a5a Make no_target_syslog consistent.
no_target_syslog = False --> do log on target
2015-12-05 16:14:36 -05:00
d04d5bf0d5 only set become defaults at last possible moment
tasks were overriding commandline with their defaults, not with the
explicit setting, removed the setting of defaults from task init and
pushed down to play context at last possible moment.
fixes #13362
2015-12-05 15:59:51 -05:00
8a733d990f simplified get_hosts code to have 1 retrun point 2015-12-05 10:13:09 -05:00
895fc48700 Remove duplicates from host list *before* caching it
Ansible previously added hosts to the host list multiple times for commands
like `ansible -i 'localhost,' -c local -m ping 'localhost,localhost'
--list-hosts`.
8d5f36a fixed the obvious error, but still added the un-deduplicated list to a
cache, so all future invocations of get_hosts() would retrieve a
non-deduplicated list.
This caused problems down the line: For some reason, Ansible only ever
schedules "flush_handlers" tasks (instead of scheduling any actual tasks from
the playbook) for hosts that are contained in the host lists multiple times.
This probably happens because the host states are stored in a dictionary
indexed by the hostnames, so duplicate hostname would cause the state to be
overwritten by subsequent invocations of … something.
2015-12-05 10:13:09 -05:00
2068ff8926 updated pull location in changelog
it was in between of backslash description and example
2015-12-05 01:48:41 -05:00
da6670cca4 Add fullstop to make sentence make sense. Touch parargraph while at it. 2015-12-04 21:35:39 -05:00
d0337a8928 comment examples in default hosts file 2015-12-04 16:02:26 -08:00
4c21d58f4c Transform exceptions into ansible messages via to_unicode instead of str to avoid tracebacks.
Fixes #13385
2015-12-04 11:52:07 -08:00
86ca0bf3b1 Correct connection type returned by libvirt_lxc inventory script
The correct connection type for LXC containers managed via libvirt is
libvirt_lxc, not lxc.
2015-12-04 11:00:37 -08:00
627576a955 Adding a uuid field so we can track host equality across serialization too 2015-12-04 13:34:10 -05:00
3aa4db5083 Update submodule refs 2015-12-04 09:59:30 -08:00
cd76552724 Changing up how host (in)equality is checked
Fixes #13397
2015-12-04 12:58:16 -05:00
f630e140d2 fixed ansible-pull broken options
* sudo was not working, now it supports full become
* now default checkout dir works, not only when specifying
* paths for checkout dir get expanded
* fixed limit options for playbook
* added verbose and debug info
2015-12-03 20:51:51 -08:00
c03b8ef0c2 return unique list of hosts 2015-12-03 19:44:31 -08:00
46718ac3f4 reverted to previous pull checkout dir behaviour
This fixes bugs with not finding plays when not specifying checkout dir
Also makes it backwards compatible
2015-12-03 19:44:31 -08:00
3e5c7c540b corrected playbook path, reformated options help
the last just to make the help consistent and readable
2015-12-03 18:24:10 -08:00
7950f09d19 Now and/or shell expressions depend on shell plugin
This should fix issues with fish shell users as && and || are
not valid syntax, fish uses actual 'and' and 'or' programs.
Also updated to allow for fish backticks pushed quotes to subshell,
fish seems to handle spaces w/o them.
Lastly, removed encompassing subshell () for fish compatibility.
fixes #13199
2015-12-03 16:43:02 -08:00
f8911adbbc For now, skip tests of module_utils/basic functions that are failing on
py3 (these are only run on the target hosts, not on the controller).
2015-12-03 14:26:25 -08:00
6aa1b6d9b1 Properly compare object references for Hosts when adding new ones
Fixes #13397
2015-12-03 15:27:10 -05:00
013ace9ab2 fix sorting of groups for host vars
Fixes #13371
2015-12-03 14:23:14 -05:00
0d0ed35ba4 Properly default remote_user for delegated-to hosts
Fixes #13323
2015-12-03 11:33:11 -05:00
b9fbfaf64e Also some unicode tests for return_values() 2015-12-02 21:12:33 -08:00
2c5c7b54f6 Add some test data that has unicode values 2015-12-02 21:12:26 -08:00
c1aeda59bd Don't compare or merge str with unicode
Fixes #13387
2015-12-02 21:12:18 -08:00
0f813fd76a updated docs for 2.0 api 2015-12-02 12:11:58 -08:00
38c11e2239 Default msg param to AnsibleError to avoid serialization problems 2015-12-02 14:18:13 -05:00
ed4a06d8ef Don't use play vars in HostVars
Fixes #13398
2015-12-02 14:18:13 -05:00
65f4cbf487 Fix template test results on python2.6 2015-12-02 10:34:14 -08:00
48a3922d56 Add example for regex_replace using named groups 2015-12-02 09:44:26 -08:00
ca838d75e3 Get v2_playbook_on_start working
* Move self._tqm.load_callbacks() earlier to ensure that v2_on_playbook_start can fire
* Pass the playbook instance to v2_on_playbook_start
* Add a _file_name instance attribute to the playbook
2015-12-02 12:41:18 -05:00
f339184e29 Use CLI.expand_tilde also for the vault --output file 2015-12-02 09:24:36 -08:00
91f71b0ace added remote environment var setting to changelog 2015-12-02 09:09:35 -08:00
f2f310472f Make module_lang default to whatever LANG is set to on the control node 2015-12-02 09:07:26 -08:00
de7dc5d07f Catch additional assertion errors for load_list_of_blocks 2015-12-02 09:04:59 -08:00
ae5cfb2898 better error on invalid task lists 2015-12-02 08:14:37 -08:00
381409140e Minor tweak and comment addition to 974a0ce3 2015-12-02 09:10:20 -05:00
be92f909ee Update debug messages and comments
The comment was taken literally from lib/plugins/strategy/linear.py and
makes no sense in free.py where we have no noop tasks.

Also update the debug messages.
2015-12-02 09:00:27 -05:00
974a0ce3fb Fix issue #13370
all_blocks is referenced after the loop over included_files, so it needs
to be initialized before this loop, not inside.
2015-12-02 09:00:26 -05:00
1f1febaa0d Let PlayIterator.add_tasks accept empty task lists
PlayIterator.add_tasks raised an error when trying to add an empty task
list.  This was the root cause of ansible issue #13370.
2015-12-02 09:00:26 -05:00
3c25ae2e10 updated new module list
added missing modules and fixed alphabetical ordering
2015-12-01 23:53:43 -08:00
d9218ce33f reformated test, changed big assert to with_items
much easier to see the individual condition that causes the failure
when using with_items and evaluating each part of the assert individually
2015-12-01 21:26:36 -08:00
346a9fe87d unconditionally set vars on init to avoid issues with var precedence 2015-12-01 21:25:43 -08:00
5b5c6c4f47 fixes a syntax issue with module_utils/eapi.py
This patch fixes an issue with the common args dict in the eapi shared
module.  This patch is required for the eapi shared module to be properly
imported and is therefore should be applied to all instances.
2015-12-01 20:46:11 -08:00
02d059271c initial add of ssh shared module.
This ssh shared module is used for building modules that require an
interactive shell environment such as those required for connecting
to network devices
2015-12-01 19:15:41 -08:00
a6771b2255 adds module create function for eapi.py shared module
This commit changes the way modules create an instance of AnsibleModule to
now use a common function, eapi_module.  This function will now automatically
append the common argument spec to the module argument_spec.  Module
arguments can override common module arguments
2015-12-01 19:14:38 -08:00
a9e8b54246 initial add of the ios shared module
This adds shared module support for building modules that connect to Cisco
IOS devices.  It builds on the module_utils/ssh.py shared module.
2015-12-01 19:05:14 -08:00
d2108e9ff3 fixed signature for init on callbacks
also removed passing display to base class which already handles this
2015-12-01 14:07:47 -08:00
59dadc4f6b allow shell plugin to affect remote module filename
Fix for 13368, added get_remote_filename to shell plugins, powershell version appends .ps1 if necessary, base shell plugin no-ops
2015-12-01 14:02:01 -08:00
927d28e5d5 added pull's code sig verification to changelog 2015-12-01 09:55:06 -08:00
a61718cfc5 Revert "Note that su now works with local connection"
This reverts commit 93ef35e6a9.

bcoca already added this
2015-12-01 09:49:08 -08:00
93ef35e6a9 Note that su now works with local connection 2015-12-01 09:14:10 -08:00
b0e22d7701 _connect no longer takes a port argument 2015-12-01 09:12:55 -08:00
1b7db6316e updated changelog to show su now works with local 2015-12-01 09:11:30 -08:00
ca8c6e8e1c ignore password flags in become conflict check
since all the --ask pass options end up triggering the same code
and are functionally equivalent, ignore them when it comes to checking
privilege escalation conflicts. This allows using -K when --become-method=su
and so on.
2015-12-01 08:57:33 -08:00
204e27ca66 avoid inheritance issues with default=dict declaration at class level
this should avoid the issue of subsequent plays not prompting for a var
prompted for in a previous play.
fixes #13363
2015-11-30 15:12:53 -08:00
f96730003b Also make sure remote_user is defaulted correctly for delegated hosts
Fixes #13323
2015-11-30 16:16:11 -05:00
4f3f79d37b Call the function :-)
Fixes #13330
2015-11-30 12:34:55 -08:00
89f0207007 Ensure port is (re)set for delegated-to hosts
Fixes #13265
2015-11-30 14:41:05 -05:00
f8ed1c003a fixed typo 2015-11-30 09:21:26 -08:00
c5cd908c33 allow for bad stdout return from make temp dir command
fixes #13359
2015-11-30 09:20:24 -08:00
958da26d18 corrected become_methods class variable in winrm
This should now correctly react when using become with winrm
fixes #13331
2015-11-30 08:35:33 -08:00
a5d6be6dd2 Make sure run_once tasks properly set variables for all active hosts
Fixes #13267
2015-11-30 11:28:28 -05:00
7af506e7cf Use text_type instead of unicode 2015-11-30 11:28:25 -05:00
3a0f2475b2 Make sure the uuid in vars is string 2015-11-30 10:27:21 -05:00
2db3f12027 Re-implement lookup wantlist
Fixes #13285
2015-11-29 23:45:14 -05:00
bb52b45ea0 Do not copy variable_manager each time. Instead, keep host and local variable_manager sync.
Fix https://github.com/ansible/ansible/issues/13221
2015-11-29 23:15:01 -05:00
4114a3097f Tweak location of stats callback execution and properly relocate stats output code 2015-11-28 14:02:50 -05:00
73a269f9a5 Put in trap for args being None
_normalize_old_style_args can return None. If it does, the loop
"for args in args" blows up.
2015-11-28 13:44:44 -05:00
737e467b8a Trigger on_stats just once, not once for each play
Fixes #13271
2015-11-28 13:37:02 -05:00
cac0eea291 Explicitly accept become_success in awaiting_prompt state
If we request escalation with a password, we start in expecting_prompt
state. If the escalation then succeeds without the password, i.e., the
become_success response arrives, we must explicitly move into the next
state (awaiting_escalation, which immediately goes into ready_to_send),
so that we no longer try to apply the timeout.

Otherwise, we would leak the success notification and eventually
timeout. But if the module response did arrive before the timeout
expired, the "process has already exited" test would do the right
thing by accident (which is why it didn't fail more often).

Fixes #13289
2015-11-28 10:22:35 -05:00
54843d88ee Re-adding role_name/role_uuid variables 2015-11-28 10:00:42 -05:00
47651e6c22 More restrictive test against variable name to allow setting variable starting with _. 2015-11-28 10:00:38 -05:00
9e6ec4c6b0 Switch parameters validation after parsing in order to be more consistent between old and new style. 2015-11-28 10:00:34 -05:00
6457f88aab Fix missing word in developing_modules.rst 2015-11-28 10:00:25 -05:00
e210da3659 Fix typo in modules_core.rst 2015-11-28 10:00:22 -05:00
e0ecaac90d changelog: minor formating fix 2015-11-28 10:00:18 -05:00
dbedcd3538 avoids prompting for vars during syntax check
fixes #13319
2015-11-27 11:46:32 -08:00
4e6442fd19 removes editorial
I feel that Ansible is above the "my hosted Git community is better than yours" discussion and thus removed the editorial around Bitbucket
2015-11-27 11:15:57 -08:00
126249d69a Add assertions for ansible_date_time in setup result. 2015-11-27 00:49:02 -05:00
056372690f Do not double transform to unicode 2015-11-25 07:56:06 -08:00
9cee982a62 fixing errors with utf-8 values
removing utf-8 stanza

changing cast to binary_type instead

using to_unicode
2015-11-25 07:55:52 -08:00
b69942a6d2 added missing : 2015-11-25 10:57:55 -08:00
d9858ee73a added missing events to base class 2015-11-25 10:57:55 -08:00
64bcab9253 fixes to fetch action module
* now only runs remote checksum when needed (fixes #12290)
 * unified return points to simplify program flow
2015-11-25 10:57:04 -08:00
fc4326dc0c Fix ssh state issues by simply assuming it's never connected 2015-11-24 12:01:42 -05:00
92ea5c9f7b Properly check for prompting state when re-using ssh connection
Fixes #13278
2015-11-24 09:10:51 -05:00
90021104d5 Use to_unicode instead of str() 2015-11-23 16:49:00 -05:00
8bd5abaf1e Allow debug var parameter to accept a list or dict. Fix https://github.com/ansible/ansible/issues/13252 2015-11-23 16:49:00 -05:00
1ab60564ae Add integration tests for zypper
Modeled after the yum tests but also tests local package installations
as fixed with PR#1256.

This depends on PRs #1256, #1261 and #1262 in ansible-modules-extra.
2015-11-23 15:22:29 -05:00
af2e94e3c7 test_hg fix: remove reference to "head"
ERROR! error while evaluating conditional: head.stat.isreg

This is remnant from earlier change 50e5d81777
which removed stat on head file..
2015-11-23 14:34:21 -05:00
5378a6003a test_svn fix: remove hardcoded "~/ansible_testing/svn" path 2015-11-23 14:33:48 -05:00
2859933a79 We should give pipes.quote() a string every time 2015-11-23 14:27:56 -05:00
42bffeec7c Template (and include vars) PlaybookInclude paths
Fixes #13249
2015-11-23 14:04:14 -05:00
9a8e95bff3 Modify task executor to reuse connection inside a loop. Fix WinRM connection to set _connected properly and display when remote shell is opened/closed. Add integration test using raw + with_items. 2015-11-23 14:04:00 -05:00
54ec2a0b84 docsite: cloudstack: fix missing quotes in example 2015-11-23 14:03:55 -05:00
1e9e6339d1 marked spot that should send per item reulsts 2015-11-23 14:03:51 -05:00
5bc3efe34b Update submodule refs 2015-11-23 09:02:12 -08:00
7d19ad82eb Recommend using pywinrm >= 0.1.1 from PyPI instead of GitHub version. 2015-11-20 15:40:34 -08:00
c1bb3aea06 Documentation: Update the Vagrant Guide
This is an attempt to solve #7665.

Revert the change applied by f56a6e0951
(#12310), as the inventory generated by Vagrant still rely on the legacy
`_ssh` setting names for backwards compatibility reasons.
See also https://github.com/mitchellh/vagrant/issues/6570
2015-11-21 14:03:24 -08:00
011df4ad24 Update docker_login so docs work 2015-11-20 13:59:02 -08:00
c71ef9e3d7 Fix non-module plugins picking up files that did not end in .py.
This was caused by accessing the cache using the passed in mod_type
rather than the suffix that we calculate with knowledge of whether this
is a module or non-module plugin.
2015-11-20 13:51:29 -08:00
e9ed190f7b Update submodule refs 2015-11-20 12:45:27 -08:00
fe090b0fb9 Add docker_login module to the changelog 2015-11-20 12:44:48 -08:00
50a924bb04 Docker cp sets file ownership to root:root so we can't use it.
Fixes #13219
2015-11-20 07:54:07 -08:00
1e968d34cb Simplify code a little 2015-11-19 09:56:58 -08:00
c99fffa936 Use ansible_host in synchronize module
Fixes #13073
2015-11-19 09:56:49 -08:00
f2225395f9 winrm error handling tweaks 2015-11-19 09:58:33 -05:00
c33f60435b fast winrm put_file without size restrictions 2015-11-19 09:58:28 -05:00
ab8bb57f5e Version bump for 2.0.0-0.6.rc1 2015-11-19 09:15:45 -05:00
261 changed files with 8008 additions and 2652 deletions

View File

@ -24,6 +24,7 @@ script:
- ./test/code-smell/replace-urlopen.sh .
- ./test/code-smell/use-compat-six.sh lib
- ./test/code-smell/boilerplate.sh
- ./test/code-smell/required-and-default-attributes.sh
- if test x"$TOXENV" != x'py24' ; then tox ; fi
- if test x"$TOXENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce).py' lib/ansible/module_utils ; fi
#- make -C docsite all

View File

@ -1,7 +1,53 @@
Ansible Changes By Release
==========================
## 2.0 "Over the Hills and Far Away" - ACTIVE DEVELOPMENT
## 2.1 TBD - ACTIVE DEVELOPMENT
####New Modules:
* cloudstack: cs_volume
####New Filters:
* extract
## 2.0.1 "Over the Hills and Far Away"
* Fixes a major compatibility break in the synchronize module shipped with
2.0.0.x. That version of synchronize ran sudo on the controller prior to
running rsync. In 1.9.x and previous, sudo was run on the host that rsync
connected to. 2.0.1 restores the 1.9.x behaviour.
* Additionally, several other problems with where synchronize chose to run when
combined with delegate_to were fixed. In particular, if a playbook targetted
localhost and then delegated_to a remote host the prior behavior (in 1.9.x
and 2.0.0.x) was to copy files between the src and destination directories on
the delegated host. This has now been fixed to copy between localhost and
the delegated host.
* Fix a regression where synchronize was unable to deal with unicode paths.
* Fix a regression where synchronize deals with inventory hosts that use
localhost but with an alternate port.
* Fixes a regression where the retry files feature was not implemented.
* Fixes a regression where the any_errors_fatal option was implemented in 2.0
incorrectly, and also adds a feature where any_errors_fatal can be set at
the block level.
* Fix tracebacks when playbooks or ansible itself were located in directories
with unicode characters.
* Fix bug when sending unicode characters to an external pager for display.
* Fix a bug with squashing loops for special modules (mostly package managers).
The optimization was squashing when the loop did not apply to the selection
of packages. This has now been fixed.
* Temp files created when using vault are now "shredded" using the unix shred
program which overwrites the file with random data.
* Some fixes to cloudstack modules for case sensitivity
* Fix non-newstyle modules (non-python modules and old-style modules) to
disabled pipelining.
* Fix fetch module failing even if fail_on_missing is set to False
* Fix for cornercase when local connections, sudo, and raw were used together.
* Fix dnf module to remove dependent packages when state=absent is specified.
This was a feature of the 1.9.x version that was left out by mistake when the
module was rewritten for 2.0.
* Fix bugs with non-english locales in yum, git, and apt modules
* Fix a bug with the dnf module where state=latest could only upgrade, not install.
## 2.0 "Over the Hills and Far Away" - Jan 12, 2016
###Major Changes:
@ -24,10 +70,13 @@ Ansible Changes By Release
by setting the `ANSIBLE_NULL_REPRESENTATION` environment variable.
* Added `meta: refresh_inventory` to force rereading the inventory in a play.
This re-executes inventory scripts, but does not force them to ignore any cache they might use.
* Now when you delegate an action that returns ansible_facts, these facts will be applied to the delegated host, unlike before when they were applied to the current host.
* New delegate_facts directive, a boolean that allows you to apply facts to the delegated host (true/yes) instead of the inventory_hostname (no/false) which is the default and previous behaviour.
* local connections now work with 'su' as a privilege escalation method
* Ansible 2.0 has deprecated the “ssh” from ansible_ssh_user, ansible_ssh_host, and ansible_ssh_port to become ansible_user, ansible_host, and ansible_port.
* New ssh configuration variables(`ansible_ssh_common_args`, `ansible_ssh_extra_args`) can be used to configure a
per-group or per-host ssh ProxyCommand or set any other ssh options.
`ansible_ssh_extra_args` is used to set options that are accepted only by ssh (not sftp or scp, which have their own analogous settings).
* ansible-pull can now verify the code it runs when using git as a source repository, using git's code signing and verification features.
* Backslashes used when specifying parameters in jinja2 expressions in YAML dicts sometimes needed to be escaped twice.
This has been fixed so that escaping once works. Here's an example of how playbooks need to be modified:
@ -71,9 +120,43 @@ newline being stripped you can change your playbook like this:
"msg": "Testing some things"
```
* In 1.9.x, newlines in templates were converted to Unix EOL conventions. If
someone wanted a templated file to end up with Windows or Mac EOL
conventions, this could cause problems for them. In 2.x newlines now remain
as specified in the template file.
* When specifying complex args as a variable, the variable must use the full jinja2
variable syntax ('{{var_name}}') - bare variable names there are no longer accepted.
In fact, even specifying args with variables has been deprecated, and will not be
allowed in future versions:
```
---
- hosts: localhost
connection: local
gather_facts: false
vars:
my_dirs:
- { path: /tmp/3a, state: directory, mode: 0755 }
- { path: /tmp/3b, state: directory, mode: 0700 }
tasks:
- file:
args: "{{item}}"
with_items: my_dirs
```
* The bigip\* networking modules have a new parameter, validate_certs. When
True (the default) the module will validate any hosts it connects to against
the TLS certificates it presents when run on new enough python versions. If
the python version is too old to validate certificates or you used certificates
that cannot be validated against available CAs you will need to add
validate_certs=no to your playbook for those tasks.
###Plugins
* Rewritten dnf module that should be faster and less prone to encountering bugs in cornercases
* WinRM connection plugin passes all vars named `ansible_winrm_*` to the underlying pywinrm client. This allows, for instance, `ansible_winrm_server_cert_validation=ignore` to be used with newer versions of pywinrm to disable certificate validation on Python 2.7.9+.
* WinRM connection plugin put_file is significantly faster and no longer has file size limitations.
####Deprecated Modules (new ones in parens):
@ -94,23 +177,31 @@ newline being stripped you can change your playbook like this:
* amazon: ec2_eni
* amazon: ec2_eni_facts
* amazon: ec2_remote_facts
* amazon: ec2_vpc_igw
* amazon: ec2_vpc_net
* amazon: ec2_vpc_net_facts
* amazon: ec2_vpc_route_table
* amazon: ec2_vpc_route_table_facts
* amazon: ec2_vpc_subnet
* amazon: ec2_vpc_subnet_facts
* amazon: ec2_win_password
* amazon: ecs_cluster
* amazon: ecs_task
* amazon: ecs_taskdefinition
* amazon: elasticache_subnet_group
* amazon: elasticache_subnet_group_facts
* amazon: iam
* amazon: iam_cert
* amazon: iam_policy
* amazon: route53_zone
* amazon: route53_facts
* amazon: route53_health_check
* amazon: route53_zone
* amazon: sts_assume_role
* amazon: s3_bucket
* amazon: s3_lifecycle
* amazon: s3_logging
* amazon: sqs_queue
* amazon: sns_topic
* amazon: sts_assume_role
* apk
* bigip_gtm_wide_ip
* bundler
@ -151,24 +242,29 @@ newline being stripped you can change your playbook like this:
* cloudstack: cs_template
* cloudstack: cs_user
* cloudstack: cs_vmsnapshot
* cronvar
* datadog_monitor
* deploy_helper
* docker: docker_login
* dpkg_selections
* elasticsearch_plugin
* expect
* find
* google: gce_tag
* hall
* ipify_facts
* iptables
* libvirt: virt_net
* libvirt: virt_pool
* maven_artifact
* openstack: os_ironic
* openstack: os_ironic_node
* openstack: os_auth
* openstack: os_client_config
* openstack: os_floating_ip
* openstack: os_image
* openstack: os_image_facts
* openstack: os_floating_ip
* openstack: os_ironic
* openstack: os_ironic_node
* openstack: os_keypair
* openstack: os_network
* openstack: os_network_facts
* openstack: os_nova_flavor
@ -184,6 +280,7 @@ newline being stripped you can change your playbook like this:
* openstack: os_server_volume
* openstack: os_subnet
* openstack: os_subnet_facts
* openstack: os_user
* openstack: os_user_group
* openstack: os_volume
* openvswitch_db.
@ -194,14 +291,15 @@ newline being stripped you can change your playbook like this:
* profitbricks: profitbricks
* profitbricks: profitbricks_datacenter
* profitbricks: profitbricks_nic
* profitbricks: profitbricks_snapshot
* profitbricks: profitbricks_volume
* profitbricks: profitbricks_volume_attachments
* proxmox
* proxmox_template
* profitbricks: profitbricks_snapshot
* proxmox: proxmox
* proxmox: proxmox_template
* puppet
* pushover
* pushbullet
* rax: rax_clb_ssl
* rax: rax_mon_alarm
* rax: rax_mon_check
* rax: rax_mon_entity
@ -211,6 +309,7 @@ newline being stripped you can change your playbook like this:
* rabbitmq_exchange
* rabbitmq_queue
* selinux_permissive
* sendgrid
* sensu_check
* sensu_subscription
* seport
@ -222,21 +321,24 @@ newline being stripped you can change your playbook like this:
* vertica_role
* vertica_schema
* vertica_user
* vmware: vmware_datacenter
* vmware: vca_fw
* vmware: vca_nat
* vmware: vmware_cluster
* vmware: vmware_datacenter
* vmware: vmware_dns_config
* vmware: vmware_dvs_host
* vmware: vmware_dvs_portgroup
* vmware: vmware_dvswitch
* vmware: vmware_host
* vmware: vmware_vmkernel_ip_config
* vmware: vmware_migrate_vmk
* vmware: vmware_portgroup
* vmware: vmware_target_canonical_facts
* vmware: vmware_vm_facts
* vmware: vmware_vm_vss_dvs_migrate
* vmware: vmware_vmkernel
* vmware: vmware_vmkernel_ip_config
* vmware: vmware_vsan_cluster
* vmware: vmware_vswitch
* vmware: vca_fw
* vmware: vca_nat
* vmware: vsphere_copy
* webfaction_app
* webfaction_db
@ -244,17 +346,22 @@ newline being stripped you can change your playbook like this:
* webfaction_mailbox
* webfaction_site
* win_acl
* win_dotnet_ngen
* win_environment
* win_firewall_rule
* win_package
* win_scheduled_task
* win_iis_virtualdirectory
* win_iis_webapplication
* win_iis_webapppool
* win_iis_webbinding
* win_iis_website
* win_lineinfile
* win_nssm
* win_package
* win_regedit
* win_scheduled_task
* win_unzip
* win_updates
* win_webpicmd
* xenserver_facts
* zabbix_host
* zabbix_hostmacro
@ -318,9 +425,21 @@ newline being stripped you can change your playbook like this:
* Lookup, vars and action plugin pathing has been normalized, all now follow the same sequence to find relative files.
* We do not ignore the explicitly set login user for ssh when it matches the 'current user' anymore, this allows overriding .ssh/config when it is set
explicitly. Leaving it unset will still use the same user and respect .ssh/config. This also means ansible_ssh_user can now return a None value.
* Handling of undefined variables has changed. In most places they will now raise an error instead of silently injecting an empty string. Use the default filter if you want to approximate the old behaviour::
* environment variables passed to remote shells now default to 'controller' settings, with fallback to en_us.UTF8 which was the previous default.
* add_hosts is much stricter about host name and will prevent invalid names from being added.
* ansible-pull now defaults to doing shallow checkouts with git, use `--full` to return to previous behaviour.
* random cows are more random
* when: now gets the registered var after the first iteration, making it possible to break out of item loops
* Handling of undefined variables has changed. In most places they will now raise an error instead of silently injecting an empty string. Use the default filter if you want to approximate the old behaviour:
```
- debug: msg="The error message was: {{error_code |default('') }}"
```
* The yum module's detection of installed packages has been made more robust by
using /usr/bin/rpm in cases where it woud have used repoquery before.
* The pip module now properly reports changes when packages are coming from a VCS.
* Fixes for retrieving files over https when a CONNECT-only proxy is in the middle.
## 1.9.4 "Dancing In the Street" - Oct 9, 2015

View File

@ -4,12 +4,14 @@ prune ticket_stubs
prune packaging
prune test
prune hacking
include README.md packaging/rpm/ansible.spec COPYING
include README.md COPYING
include examples/hosts
include examples/ansible.cfg
include lib/ansible/module_utils/powershell.ps1
recursive-include lib/ansible/modules *
recursive-include lib/ansible/galaxy/data *
recursive-include docs *
recursive-include packaging *
include Makefile
include VERSION
include MANIFEST.in

View File

@ -44,7 +44,7 @@ GIT_HASH := $(shell git log -n 1 --format="%h")
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed 's/[-_.\/]//g')
GITINFO = .$(GIT_HASH).$(GIT_BRANCH)
else
GITINFO = ''
GITINFO = ""
endif
ifeq ($(shell echo $(OS) | egrep -c 'Darwin|FreeBSD|OpenBSD'),1)
@ -167,6 +167,9 @@ install:
sdist: clean docs
$(PYTHON) setup.py sdist
sdist_upload: clean docs
$(PYTHON) setup.py sdist upload 2>&1 |tee upload.log
rpmcommon: $(MANPAGES) sdist
@mkdir -p rpm-build
@cp dist/*.gz rpm-build/

View File

@ -55,3 +55,4 @@ Ansible was created by [Michael DeHaan](https://github.com/mpdehaan) (michael.de
Ansible is sponsored by [Ansible, Inc](http://ansible.com)

View File

@ -4,11 +4,13 @@ Ansible Releases at a Glance
Active Development
++++++++++++++++++
2.0 "Over the Hills and Far Away" - in progress
2.1 "TBD" - in progress
Released
++++++++
2.0.1 "Over the Hills and Far Away" TBD
2.0.0 "Over the Hills and Far Away" 01-12-2015
1.9.4 "Dancing In the Streets" 10-09-2015
1.9.3 "Dancing In the Streets" 09-03-2015
1.9.2 "Dancing In the Streets" 06-24-2015

View File

@ -1 +1 @@
2.0.0 0.5.beta3
2.0.1.0 0.2.network

View File

@ -40,6 +40,7 @@ from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
from ansible.utils.display import Display
from ansible.utils.unicode import to_unicode
########################################
### OUTPUT OF LAST RESORT ###
class LastResort(object):
@ -60,6 +61,7 @@ if __name__ == '__main__':
try:
display = Display()
display.debug("starting run")
sub = None
try:
@ -107,7 +109,7 @@ if __name__ == '__main__':
have_cli_options = cli is not None and cli.options is not None
display.error("Unexpected Exception: %s" % to_unicode(e), wrap_text=False)
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
display.display("the full traceback was:\n\n%s" % traceback.format_exc())
display.display(u"the full traceback was:\n\n%s" % to_unicode(traceback.format_exc()))
else:
display.display("to see the full traceback, use -vvv")
sys.exit(250)

View File

@ -407,7 +407,7 @@ class Ec2Inventory(object):
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)')
parser.add_argument('--boto-profile', action='store',
parser.add_argument('--profile', '--boto-profile', action='store', dest='boto_profile',
help='Use boto profile for connections to EC2')
self.args = parser.parse_args()

View File

@ -27,11 +27,11 @@ result['all'] = {}
pipe = Popen(['virsh', '-q', '-c', 'lxc:///', 'list', '--name', '--all'], stdout=PIPE, universal_newlines=True)
result['all']['hosts'] = [x[:-1] for x in pipe.stdout.readlines()]
result['all']['vars'] = {}
result['all']['vars']['ansible_connection'] = 'lxc'
result['all']['vars']['ansible_connection'] = 'libvirt_lxc'
if len(sys.argv) == 2 and sys.argv[1] == '--list':
print(json.dumps(result))
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
print(json.dumps({'ansible_connection': 'lxc'}))
print(json.dumps({'ansible_connection': 'libvirt_lxc'}))
else:
print("Need an argument, either --list or --host <host>")

View File

@ -32,6 +32,13 @@
# all of them and present them as one contiguous inventory.
#
# See the adjacent openstack.yml file for an example config file
# There are two ansible inventory specific options that can be set in
# the inventory section.
# expand_hostvars controls whether or not the inventory will make extra API
# calls to fill out additional information about each server
# use_hostnames changes the behavior from registering every host with its UUID
# and making a group of its hostname to only doing this if the
# hostname in question has more than one server
import argparse
import collections
@ -51,7 +58,7 @@ import shade.inventory
CONFIG_FILES = ['/etc/ansible/openstack.yaml']
def get_groups_from_server(server_vars):
def get_groups_from_server(server_vars, namegroup=True):
groups = []
region = server_vars['region']
@ -76,7 +83,8 @@ def get_groups_from_server(server_vars):
groups.append(extra_group)
groups.append('instance-%s' % server_vars['id'])
groups.append(server_vars['name'])
if namegroup:
groups.append(server_vars['name'])
for key in ('flavor', 'image'):
if 'name' in server_vars[key]:
@ -94,9 +102,9 @@ def get_groups_from_server(server_vars):
return groups
def get_host_groups(inventory):
def get_host_groups(inventory, refresh=False):
(cache_file, cache_expiration_time) = get_cache_settings()
if is_cache_stale(cache_file, cache_expiration_time):
if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh):
groups = to_json(get_host_groups_from_cloud(inventory))
open(cache_file, 'w').write(groups)
else:
@ -106,23 +114,44 @@ def get_host_groups(inventory):
def get_host_groups_from_cloud(inventory):
groups = collections.defaultdict(list)
firstpass = collections.defaultdict(list)
hostvars = {}
for server in inventory.list_hosts():
list_args = {}
if hasattr(inventory, 'extra_config'):
use_hostnames = inventory.extra_config['use_hostnames']
list_args['expand'] = inventory.extra_config['expand_hostvars']
else:
use_hostnames = False
for server in inventory.list_hosts(**list_args):
if 'interface_ip' not in server:
continue
for group in get_groups_from_server(server):
groups[group].append(server['id'])
hostvars[server['id']] = dict(
ansible_ssh_host=server['interface_ip'],
openstack=server,
)
firstpass[server['name']].append(server)
for name, servers in firstpass.items():
if len(servers) == 1 and use_hostnames:
server = servers[0]
hostvars[name] = dict(
ansible_ssh_host=server['interface_ip'],
openstack=server)
for group in get_groups_from_server(server, namegroup=False):
groups[group].append(server['name'])
else:
for server in servers:
server_id = server['id']
hostvars[server_id] = dict(
ansible_ssh_host=server['interface_ip'],
openstack=server)
for group in get_groups_from_server(server, namegroup=True):
groups[group].append(server_id)
groups['_meta'] = {'hostvars': hostvars}
return groups
def is_cache_stale(cache_file, cache_expiration_time):
def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
''' Determines if cache file has expired, or if it is still valid '''
if refresh:
return True
if os.path.isfile(cache_file):
mod_time = os.path.getmtime(cache_file)
current_time = time.time()
@ -169,14 +198,24 @@ def main():
try:
config_files = os_client_config.config.CONFIG_FILES + CONFIG_FILES
shade.simple_logging(debug=args.debug)
inventory = shade.inventory.OpenStackInventory(
inventory_args = dict(
refresh=args.refresh,
config_files=config_files,
private=args.private,
)
if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
inventory_args.update(dict(
config_key='ansible',
config_defaults={
'use_hostnames': False,
'expand_hostvars': True,
}
))
inventory = shade.inventory.OpenStackInventory(**inventory_args)
if args.list:
output = get_host_groups(inventory)
output = get_host_groups(inventory, refresh=args.refresh)
elif args.host:
output = to_json(inventory.get_host(args.host))
print(output)

View File

@ -26,3 +26,6 @@ clouds:
username: stack
password: stack
project_name: stack
ansible:
use_hostnames: True
expand_hostvars: False

View File

@ -12,7 +12,7 @@ ansible-galaxy - manage roles using galaxy.ansible.com
SYNOPSIS
--------
ansible-galaxy [init|info|install|list|remove] [--help] [options] ...
ansible-galaxy [delete|import|info|init|install|list|login|remove|search|setup] [--help] [options] ...
DESCRIPTION
@ -20,7 +20,7 @@ DESCRIPTION
*Ansible Galaxy* is a shared repository for Ansible roles.
The ansible-galaxy command can be used to manage these roles,
or by creating a skeleton framework for roles you'd like to upload to Galaxy.
or for creating a skeleton framework for roles you'd like to upload to Galaxy.
COMMON OPTIONS
--------------
@ -29,7 +29,6 @@ COMMON OPTIONS
Show a help message related to the given sub-command.
INSTALL
-------
@ -145,6 +144,204 @@ The path to the directory containing your roles. The default is the *roles_path*
configured in your *ansible.cfg* file (/etc/ansible/roles if not configured)
SEARCH
------
The *search* sub-command returns a filtered list of roles found on the remote
server.
USAGE
~~~~~
$ ansible-galaxy search [options] [searchterm1 searchterm2]
OPTIONS
~~~~~~~
*--galaxy-tags*::
Provide a comma separated list of Galaxy Tags on which to filter.
*--platforms*::
Provide a comma separated list of Platforms on which to filter.
*--author*::
Specify the username of a Galaxy contributor on which to filter.
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
INFO
----
The *info* sub-command shows detailed information for a specific role.
Details returned about the role included information from the local copy
as well as information from galaxy.ansible.com.
USAGE
~~~~~
$ ansible-galaxy info [options] role_name[, version]
OPTIONS
~~~~~~~
*-p* 'ROLES_PATH', *--roles-path=*'ROLES_PATH'::
The path to the directory containing your roles. The default is the *roles_path*
configured in your *ansible.cfg* file (/etc/ansible/roles if not configured)
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
LOGIN
-----
The *login* sub-command is used to authenticate with galaxy.ansible.com.
Authentication is required to use the import, delete and setup commands.
It will authenticate the user, retrieve a token from Galaxy, and store it
in the user's home directory.
USAGE
~~~~~
$ ansible-galaxy login [options]
The *login* sub-command prompts for a *GitHub* username and password. It does
NOT send your password to Galaxy. It actually authenticates with GitHub and
creates a personal access token. It then sends the personal access token to
Galaxy, which in turn verifies that you are you and returns a Galaxy access
token. After authentication completes the *GitHub* personal access token is
destroyed.
If you do not wish to use your GitHub password, or if you have two-factor
authentication enabled with GitHub, use the *--github-token* option to pass a
personal access token that you create. Log into GitHub, go to Settings and
click on Personal Access Token to create a token.
OPTIONS
~~~~~~~
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
*--github-token*::
Authenticate using a *GitHub* personal access token rather than a password.
IMPORT
------
Import a role from *GitHub* to galaxy.ansible.com. Requires the user first
authenticate with galaxy.ansible.com using the *login* subcommand.
USAGE
~~~~~
$ ansible-galaxy import [options] github_user github_repo
OPTIONS
~~~~~~~
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
*--branch*::
Provide a specific branch to import. When a branch is not specified the
branch found in meta/main.yml is used. If no branch is specified in
meta/main.yml, the repo's default branch (usually master) is used.
DELETE
------
The *delete* sub-command will delete a role from galaxy.ansible.com. Requires
the user first authenticate with galaxy.ansible.com using the *login* subcommand.
USAGE
~~~~~
$ ansible-galaxy delete [options] github_user github_repo
OPTIONS
~~~~~~~
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
SETUP
-----
The *setup* sub-command creates an integration point for *Travis CI*, enabling
galaxy.ansible.com to receive notifications from *Travis* on build completion.
Requires the user first authenticate with galaxy.ansible.com using the *login*
subcommand.
USAGE
~~~~~
$ ansible-galaxy setup [options] source github_user github_repo secret
* Use *travis* as the source value. In the future additional source values may
be added.
* Provide your *Travis* user token as the secret. The token is not stored by
galaxy.ansible.com. A hash is created using github_user, github_repo
and your token. The hash value is what actually gets stored.
OPTIONS
~~~~~~~
*-c*, *--ignore-certs*::
Ignore TLS certificate errors.
*-s*, *--server*::
Override the default server https://galaxy.ansible.com.
--list::
Show your configured integrations. Provids the ID of each integration
which can be used with the remove option.
--remove::
Remove a specific integration. Provide the ID of the integration to
be removed.
AUTHOR
------

View File

@ -96,7 +96,7 @@ Show help page and exit
*-i* 'PATH', *--inventory=*'PATH'::
The 'PATH' to the inventory, which defaults to '/etc/ansible/hosts'.
Alternatively you can use a comma separated list of hosts or single host with traling comma 'host,'.
Alternatively, you can use a comma-separated list of hosts or a single host with a trailing comma 'host,'.
*-l* 'SUBSET', *--limit=*'SUBSET'::

View File

@ -95,6 +95,10 @@ Force running of playbook even if unable to update playbook repository. This
can be useful, for example, to enforce run-time state when a network
connection may not always be up or possible.
*--full*::
Do a full clone of the repository. By default ansible-pull will do a shallow clone based on the last revision.
*-h*, *--help*::
Show the help message and exit.

View File

@ -20,6 +20,8 @@ viewdocs: clean staticmin
htmldocs: staticmin
./build-site.py rst
webdocs: htmldocs
clean:
-rm -rf htmlout
-rm -f .buildinfo

View File

@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
__docformat__ = 'restructuredtext'
@ -24,9 +25,9 @@ import traceback
try:
from sphinx.application import Sphinx
except ImportError:
print "#################################"
print "Dependency missing: Python Sphinx"
print "#################################"
print("#################################")
print("Dependency missing: Python Sphinx")
print("#################################")
sys.exit(1)
import os
@ -40,7 +41,7 @@ class SphinxBuilder(object):
"""
Run the DocCommand.
"""
print "Creating html documentation ..."
print("Creating html documentation ...")
try:
buildername = 'html'
@ -69,10 +70,10 @@ class SphinxBuilder(object):
app.builder.build_all()
except ImportError, ie:
except ImportError:
traceback.print_exc()
except Exception, ex:
print >> sys.stderr, "FAIL! exiting ... (%s)" % ex
except Exception as ex:
print("FAIL! exiting ... (%s)" % ex, file=sys.stderr)
def build_docs(self):
self.app.builder.build_all()
@ -83,9 +84,9 @@ def build_rst_docs():
if __name__ == '__main__':
if '-h' in sys.argv or '--help' in sys.argv:
print "This script builds the html documentation from rst/asciidoc sources.\n"
print " Run 'make docs' to build everything."
print " Run 'make viewdocs' to build and then preview in a web browser."
print("This script builds the html documentation from rst/asciidoc sources.\n")
print(" Run 'make docs' to build everything.")
print(" Run 'make viewdocs' to build and then preview in a web browser.")
sys.exit(0)
build_rst_docs()
@ -93,4 +94,4 @@ if __name__ == '__main__':
if "view" in sys.argv:
import webbrowser
if not webbrowser.open('htmlout/index.html'):
print >> sys.stderr, "Could not open on your webbrowser."
print("Could not open on your webbrowser.", file=sys.stderr)

View File

@ -1,5 +1,5 @@
Ansible Privilege Escalation
++++++++++++++++++++++++++++
Become (Privilege Escalation)
+++++++++++++++++++++++++++++
Ansible can use existing privilege escalation systems to allow a user to execute tasks as another.
@ -7,17 +7,17 @@ Ansible can use existing privilege escalation systems to allow a user to execute
Become
``````
Before 1.9 Ansible mostly allowed the use of sudo and a limited use of su to allow a login/remote user to become a different user
and execute tasks, create resources with the 2nd user's permissions. As of 1.9 'become' supersedes the old sudo/su, while still
being backwards compatible. This new system also makes it easier to add other privilege escalation tools like pbrun (Powerbroker),
pfexec and others.
Before 1.9 Ansible mostly allowed the use of `sudo` and a limited use of `su` to allow a login/remote user to become a different user
and execute tasks, create resources with the 2nd user's permissions. As of 1.9 `become` supersedes the old sudo/su, while still
being backwards compatible. This new system also makes it easier to add other privilege escalation tools like `pbrun` (Powerbroker),
`pfexec` and others.
New directives
--------------
become
equivalent to adding 'sudo:' or 'su:' to a play or task, set to 'true'/'yes' to activate privilege escalation
equivalent to adding `sudo:` or `su:` to a play or task, set to 'true'/'yes' to activate privilege escalation
become_user
equivalent to adding 'sudo_user:' or 'su_user:' to a play or task, set to user with desired privileges

View File

@ -11,6 +11,7 @@ Learn how to build modules of your own in any language, and also how to extend A
developing_modules
developing_plugins
developing_test_pr
developing_releases
Developers will also likely be interested in the fully-discoverable in :doc:`tower`. It's great for embedding Ansible in all manner of applications.

View File

@ -17,11 +17,67 @@ This chapter discusses the Python API.
.. _python_api:
Python API
----------
The Python API is very powerful, and is how the ansible CLI and ansible-playbook
are implemented.
are implemented. In version 2.0 the core ansible got rewritten and the API was mostly rewritten.
.. _python_api_20:
Python API 2.0
--------------
In 2.0 things get a bit more complicated to start, but you end up with much more discrete and readable classes::
#!/usr/bin/python2
from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
from ansible.inventory import Inventory
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
Options = namedtuple('Options', ['connection','module_path', 'forks', 'remote_user', 'private_key_file', 'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args', 'scp_extra_args', 'become', 'become_method', 'become_user', 'verbosity', 'check'])
# initialize needed objects
variable_manager = VariableManager()
loader = DataLoader()
options = Options(connection='local', module_path='/path/to/mymodules', forks=100, remote_user=None, private_key_file=None, ssh_common_args=None, ssh_extra_args=None, sftp_extra_args=None, scp_extra_args=None, become=None, become_method=None, become_user=None, verbosity=None, check=False)
passwords = dict(vault_pass='secret')
# create inventory and pass to var manager
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list='localhost')
variable_manager.set_inventory(inventory)
# create play with tasks
play_source = dict(
name = "Ansible Play",
hosts = 'localhost',
gather_facts = 'no',
tasks = [ dict(action=dict(module='debug', args=(msg='Hello Galaxy!'))) ]
)
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
# actually run it
tqm = None
try:
tqm = TaskQueueManager(
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
options=options,
passwords=passwords,
stdout_callback='default',
)
result = tqm.run(play)
finally:
if tqm is not None:
tqm.cleanup()
.. _python_api_old:
Python API pre 2.0
------------------
It's pretty simple::
@ -51,7 +107,7 @@ expressed in the :doc:`modules` documentation.::
A module can return any type of JSON data it wants, so Ansible can
be used as a framework to rapidly build powerful applications and scripts.
.. _detailed_api_example:
.. _detailed_api_old_example:
Detailed API Example
````````````````````
@ -87,9 +143,9 @@ The following script prints out the uptime information for all hosts::
for (hostname, result) in results['dark'].items():
print "%s >>> %s" % (hostname, result)
Advanced programmers may also wish to read the source to ansible itself, for
it uses the Runner() API (with all available options) to implement the
command line tools ``ansible`` and ``ansible-playbook``.
Advanced programmers may also wish to read the source to ansible itself,
for it uses the API (with all available options) to implement the ``ansible``
command line tools (``lib/ansible/cli/``).
.. seealso::

View File

@ -219,7 +219,7 @@ this, just have the module return a `ansible_facts` key, like so, along with oth
}
These 'facts' will be available to all statements called after that module (but not before) in the playbook.
A good idea might be make a module called 'site_facts' and always call it at the top of each playbook, though
A good idea might be to make a module called 'site_facts' and always call it at the top of each playbook, though
we're always open to improving the selection of core facts in Ansible as well.
.. _common_module_boilerplate:

View File

@ -1,55 +1,60 @@
Ansible Galaxy
++++++++++++++
"Ansible Galaxy" can either refer to a website for sharing and downloading Ansible roles, or a command line tool that helps work with roles.
"Ansible Galaxy" can either refer to a website for sharing and downloading Ansible roles, or a command line tool for managing and creating roles.
.. contents:: Topics
The Website
```````````
The website `Ansible Galaxy <https://galaxy.ansible.com>`_, is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects.
The website `Ansible Galaxy <https://galaxy.ansible.com>`_, is a free site for finding, downloading, and sharing community developed Ansible roles. Downloading roles from Galaxy is a great way to jumpstart your automation projects.
You can sign up with social auth and use the download client 'ansible-galaxy' which is included in Ansible 1.4.2 and later.
Access the Galaxy web site using GitHub OAuth, and to install roles use the 'ansible-galaxy' command line tool included in Ansible 1.4.2 and later.
Read the "About" page on the Galaxy site for more information.
The ansible-galaxy command line tool
````````````````````````````````````
The command line ansible-galaxy has many different subcommands.
The ansible-galaxy command has many different sub-commands for managing roles both locally and at `galaxy.ansible.com <https://galaxy.ansible.com>`_.
.. note::
The search, login, import, delete, and setup commands in the Ansible 2.0 version of ansible-galaxy require access to the
2.0 Beta release of the Galaxy web site available at `https://galaxy-qa.ansible.com <https://galaxy-qa.ansible.com>`_.
Use the ``--server`` option to access the beta site. For example::
$ ansible-galaxy search --server https://galaxy-qa.ansible.com mysql --author geerlingguy
Additionally, you can define a server in ansible.cfg::
[galaxy]
server=https://galaxy-qa.ansible.com
Installing Roles
----------------
The most obvious is downloading roles from the Ansible Galaxy website::
The most obvious use of the ansible-galaxy command is downloading roles from `the Ansible Galaxy website <https://galaxy.ansible.com>`_::
ansible-galaxy install username.rolename
.. _galaxy_cli_roles_path:
$ ansible-galaxy install username.rolename
roles_path
===============
==========
You can specify a particular directory where you want the downloaded roles to be placed::
ansible-galaxy install username.role -p ~/Code/ansible_roles/
$ ansible-galaxy install username.role -p ~/Code/ansible_roles/
This can be useful if you have a master folder that contains ansible galaxy roles shared across several projects. The default is the roles_path configured in your ansible.cfg file (/etc/ansible/roles if not configured).
Building out Role Scaffolding
-----------------------------
It can also be used to initialize the base structure of a new role, saving time on creating the various directories and main.yml files a role requires::
ansible-galaxy init rolename
Installing Multiple Roles From A File
-------------------------------------
=====================================
To install multiple roles, the ansible-galaxy CLI can be fed a requirements file. All versions of ansible allow the following syntax for installing roles from the Ansible Galaxy website::
ansible-galaxy install -r requirements.txt
$ ansible-galaxy install -r requirements.txt
Where the requirements.txt looks like::
@ -64,7 +69,7 @@ To request specific versions (tags) of a role, use this syntax in the roles file
Available versions will be listed on the Ansible Galaxy webpage for that role.
Advanced Control over Role Requirements Files
---------------------------------------------
=============================================
For more advanced control over where to download roles from, including support for remote repositories, Ansible 1.8 and later support a new YAML format for the role requirements file, which must end in a 'yml' extension. It works like this::
@ -77,14 +82,14 @@ And here's an example showing some specific version downloads from multiple sour
# from galaxy
- src: yatesr.timezone
# from github
# from GitHub
- src: https://github.com/bennojoy/nginx
# from github installing to a relative path
# from GitHub installing to a relative path
- src: https://github.com/bennojoy/nginx
path: vagrant/roles/
# from github, overriding the name and specifying a specific tag
# from GitHub, overriding the name and specifying a specific tag
- src: https://github.com/bennojoy/nginx
version: master
name: nginx_role
@ -93,15 +98,15 @@ And here's an example showing some specific version downloads from multiple sour
- src: https://some.webserver.example.com/files/master.tar.gz
name: http-role
# from bitbucket, if bitbucket happens to be operational right now :)
# from Bitbucket
- src: git+http://bitbucket.org/willthames/git-ansible-galaxy
version: v1.4
# from bitbucket, alternative syntax and caveats
# from Bitbucket, alternative syntax and caveats
- src: http://bitbucket.org/willthames/hg-ansible-galaxy
scm: hg
# from gitlab or other git-based scm
# from GitLab or other git-based scm
- src: git@gitlab.company.com:mygroup/ansible-base.git
scm: git
version: 0.1.0
@ -121,3 +126,283 @@ Roles pulled from galaxy work as with other SCM sourced roles above. To download
`irc.freenode.net <http://irc.freenode.net>`_
#ansible IRC chat channel
Building Role Scaffolding
-------------------------
Use the init command to initialize the base structure of a new role, saving time on creating the various directories and main.yml files a role requires::
$ ansible-galaxy init rolename
The above will create the following directory structure in the current working directory:
::
README.md
.travis.yml
defaults/
main.yml
files/
handlers/
main.yml
meta/
main.yml
templates/
tests/
inventory
test.yml
vars/
main.yml
.. note::
.travis.yml and tests/ are new in Ansible 2.0
If a directory matching the name of the role already exists in the current working directory, the init command will result in an error. To ignore the error use the --force option. Force will create the above subdirectories and files, replacing anything that matches.
Search for Roles
----------------
The search command provides for querying the Galaxy database, allowing for searching by tags, platforms, author and multiple keywords. For example:
::
$ ansible-galaxy search elasticsearch --author geerlingguy
The search command will return a list of the first 1000 results matching your search:
::
Found 2 roles matching your search:
Name Description
---- -----------
geerlingguy.elasticsearch Elasticsearch for Linux.
geerlingguy.elasticsearch-curator Elasticsearch curator for Linux.
.. note::
The format of results pictured here is new in Ansible 2.0.
Get More Information About a Role
---------------------------------
Use the info command To view more detail about a specific role:
::
$ ansible-galaxy info username.role_name
This returns everything found in Galaxy for the role:
::
Role: username.rolename
description: Installs and configures a thing, a distributed, highly available NoSQL thing.
active: True
commit: c01947b7bc89ebc0b8a2e298b87ab416aed9dd57
commit_message: Adding travis
commit_url: https://github.com/username/repo_name/commit/c01947b7bc89ebc0b8a2e298b87ab
company: My Company, Inc.
created: 2015-12-08T14:17:52.773Z
download_count: 1
forks_count: 0
github_branch:
github_repo: repo_name
github_user: username
id: 6381
is_valid: True
issue_tracker_url:
license: Apache
min_ansible_version: 1.4
modified: 2015-12-08T18:43:49.085Z
namespace: username
open_issues_count: 0
path: /Users/username/projects/roles
scm: None
src: username.repo_name
stargazers_count: 0
travis_status_url: https://travis-ci.org/username/repo_name.svg?branch=master
version:
watchers_count: 1
List Installed Roles
--------------------
The list command shows the name and version of each role installed in roles_path.
::
$ ansible-galaxy list
- chouseknecht.role-install_mongod, master
- chouseknecht.test-role-1, v1.0.2
- chrismeyersfsu.role-iptables, master
- chrismeyersfsu.role-required_vars, master
Remove an Installed Role
------------------------
The remove command will delete a role from roles_path:
::
$ ansible-galaxy remove username.rolename
Authenticate with Galaxy
------------------------
To use the import, delete and setup commands authentication with Galaxy is required. The login command will authenticate the user,retrieve a token from Galaxy, and store it in the user's home directory.
::
$ ansible-galaxy login
We need your Github login to identify you.
This information will not be sent to Galaxy, only to api.github.com.
The password will not be displayed.
Use --github-token if you do not want to enter your password.
Github Username: dsmith
Password for dsmith:
Succesfully logged into Galaxy as dsmith
As depicted above, the login command prompts for a GitHub username and password. It does NOT send your password to Galaxy. It actually authenticates with GitHub and creates a personal access token. It then sends the personal access token to Galaxy, which in turn verifies that you are you and returns a Galaxy access token. After authentication completes the GitHub personal access token is destroyed.
If you do not wish to use your GitHub password, or if you have two-factor authentication enabled with GitHub, use the --github-token option to pass a personal access token that you create. Log into GitHub, go to Settings and click on Personal Access Token to create a token.
.. note::
The login command in Ansible 2.0 requires using the Galaxy 2.0 Beta site. Use the ``--server`` option to access
`https://galaxy-qa.ansible.com <https://galaxy-qa.ansible.com>`_. You can also add a *server* definition in the [galaxy]
section of your ansible.cfg file.
Import a Role
-------------
Roles can be imported using ansible-galaxy. The import command expects that the user previously authenticated with Galaxy using the login command.
Import any GitHub repo you have access to:
::
$ ansible-galaxy import github_user github_repo
By default the command will wait for the role to be imported by Galaxy, displaying the results as the import progresses:
::
Successfully submitted import request 41
Starting import 41: role_name=myrole repo=githubuser/ansible-role-repo ref=
Retrieving Github repo githubuser/ansible-role-repo
Accessing branch: master
Parsing and validating meta/main.yml
Parsing galaxy_tags
Parsing platforms
Adding dependencies
Parsing and validating README.md
Adding repo tags as role versions
Import completed
Status SUCCESS : warnings=0 errors=0
Use the --branch option to import a specific branch. If not specified, the default branch for the repo will be used.
If the --no-wait option is present, the command will not wait for results. Results of the most recent import for any of your roles is available on the Galaxy web site under My Imports.
.. note::
The import command in Ansible 2.0 requires using the Galaxy 2.0 Beta site. Use the ``--server`` option to access
`https://galaxy-qa.ansible.com <https://galaxy-qa.ansible.com>`_. You can also add a *server* definition in the [galaxy]
section of your ansible.cfg file.
Delete a Role
-------------
Remove a role from the Galaxy web site using the delete command. You can delete any role that you have access to in GitHub. The delete command expects that the user previously authenticated with Galaxy using the login command.
::
$ ansible-galaxy delete github_user github_repo
This only removes the role from Galaxy. It does not impact the actual GitHub repo.
.. note::
The delete command in Ansible 2.0 requires using the Galaxy 2.0 Beta site. Use the ``--server`` option to access
`https://galaxy-qa.ansible.com <https://galaxy-qa.ansible.com>`_. You can also add a *server* definition in the [galaxy]
section of your ansible.cfg file.
Setup Travis Integerations
--------------------------
Using the setup command you can enable notifications from `travis <http://travis-ci.org>`_. The setup command expects that the user previously authenticated with Galaxy using the login command.
::
$ ansible-galaxy setup travis github_user github_repo xxxtravistokenxxx
Added integration for travis github_user/github_repo
The setup command requires your Travis token. The Travis token is not stored in Galaxy. It is used along with the GitHub username and repo to create a hash as described in `the Travis documentation <https://docs.travis-ci.com/user/notifications/>`_. The calculated hash is stored in Galaxy and used to verify notifications received from Travis.
The setup command enables Galaxy to respond to notifications. Follow the `Travis getting started guide <https://docs.travis-ci.com/user/getting-started/>`_ to enable the Travis build process for the role repository.
When you create your .travis.yml file add the following to cause Travis to notify Galaxy when a build completes:
::
notifications:
webhooks: https://galaxy.ansible.com/api/v1/notifications/
.. note::
The setup command in Ansible 2.0 requires using the Galaxy 2.0 Beta site. Use the ``--server`` option to access
`https://galaxy-qa.ansible.com <https://galaxy-qa.ansible.com>`_. You can also add a *server* definition in the [galaxy]
section of your ansible.cfg file.
List Travis Integrations
========================
Use the --list option to display your Travis integrations:
::
$ ansible-galaxy setup --list
ID Source Repo
---------- ---------- ----------
2 travis github_user/github_repo
1 travis github_user/github_repo
Remove Travis Integrations
==========================
Use the --remove option to disable and remove a Travis integration:
::
$ ansible-galaxy setup --remove ID
Provide the ID of the integration you want disabled. Use the --list option to get the ID.

View File

@ -178,8 +178,8 @@ Now to the fun part. We create a playbook to create our infrastructure we call i
- name: ensure firewall ports opened
cs_firewall:
ip_address: {{ public_ip }}
port: {{ item.port }}
ip_address: "{{ public_ip }}"
port: "{{ item.port }}"
cidr: "{{ item.cidr | default('0.0.0.0/0') }}"
with_items: cs_firewall
when: public_ip is defined

View File

@ -6,12 +6,13 @@ Using Vagrant and Ansible
Introduction
````````````
Vagrant is a tool to manage virtual machine environments, and allows you to
configure and use reproducible work environments on top of various
virtualization and cloud platforms. It also has integration with Ansible as a
provisioner for these virtual machines, and the two tools work together well.
`Vagrant <http://vagrantup.com/>`_ is a tool to manage virtual machine
environments, and allows you to configure and use reproducible work
environments on top of various virtualization and cloud platforms.
It also has integration with Ansible as a provisioner for these virtual
machines, and the two tools work together well.
This guide will describe how to use Vagrant and Ansible together.
This guide will describe how to use Vagrant 1.7+ and Ansible together.
If you're not familiar with Vagrant, you should visit `the documentation
<http://docs.vagrantup.com/v2/>`_.
@ -27,54 +28,48 @@ Vagrant Setup
The first step once you've installed Vagrant is to create a ``Vagrantfile``
and customize it to suit your needs. This is covered in detail in the Vagrant
documentation, but here is a quick example:
.. code-block:: bash
$ mkdir vagrant-test
$ cd vagrant-test
$ vagrant init precise32 http://files.vagrantup.com/precise32.box
This will create a file called Vagrantfile that you can edit to suit your
needs. The default Vagrantfile has a lot of comments. Here is a simplified
example that includes a section to use the Ansible provisioner:
documentation, but here is a quick example that includes a section to use the
Ansible provisioner to manage a single machine:
.. code-block:: ruby
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "precise32"
config.vm.box_url = "http://files.vagrantup.com/precise32.box"
config.vm.network :public_network
# This guide is optimized for Vagrant 1.7 and above.
# Although versions 1.6.x should behave very similarly, it is recommended
# to upgrade instead of disabling the requirement below.
Vagrant.require_version ">= 1.7.0"
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
end
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
# Disable the new default behavior introduced in Vagrant 1.7, to
# ensure that all Vagrant machines will use the same SSH key pair.
# See https://github.com/mitchellh/vagrant/issues/5005
config.ssh.insert_key = false
config.vm.provision "ansible" do |ansible|
ansible.verbose = "v"
ansible.playbook = "playbook.yml"
end
end
The Vagrantfile has a lot of options, but these are the most important ones.
Notice the ``config.vm.provision`` section that refers to an Ansible playbook
called ``playbook.yml`` in the same directory as the Vagrantfile. Vagrant runs
the provisioner once the virtual machine has booted and is ready for SSH
called ``playbook.yml`` in the same directory as the ``Vagrantfile``. Vagrant
runs the provisioner once the virtual machine has booted and is ready for SSH
access.
There are a lot of Ansible options you can configure in your ``Vagrantfile``.
Visit the `Ansible Provisioner documentation
<http://docs.vagrantup.com/v2/provisioning/ansible.html>`_ for more
information.
.. code-block:: bash
$ vagrant up
This will start the VM and run the provisioning playbook.
This will start the VM, and run the provisioning playbook (on the first VM
startup).
There are a lot of Ansible options you can configure in your Vagrantfile. Some
particularly useful options are ``ansible.extra_vars``, ``ansible.sudo`` and
``ansible.sudo_user``, and ``ansible.host_key_checking`` which you can disable
to avoid SSH connection problems to new virtual machines.
Visit the `Ansible Provisioner documentation
<http://docs.vagrantup.com/v2/provisioning/ansible.html>`_ for more
information.
To re-run a playbook on an existing VM, just run:
@ -82,7 +77,19 @@ To re-run a playbook on an existing VM, just run:
$ vagrant provision
This will re-run the playbook.
This will re-run the playbook against the existing VM.
Note that having the ``ansible.verbose`` option enabled will instruct Vagrant
to show the full ``ansible-playbook`` command used behind the scene, as
illustrated by this example:
.. code-block:: bash
$ PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/home/someone/.vagrant.d/insecure_private_key --user=vagrant --connection=ssh --limit='machine1' --inventory-file=/home/someone/coding-in-a-project/.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory playbook.yml
This information can be quite useful to debug integration issues and can also
be used to manually execute Ansible from a shell, as explained in the next
section.
.. _running_ansible:
@ -90,44 +97,58 @@ Running Ansible Manually
````````````````````````
Sometimes you may want to run Ansible manually against the machines. This is
pretty easy to do.
faster than kicking ``vagrant provision`` and pretty easy to do.
Vagrant automatically creates an inventory file for each Vagrant machine in
the same directory located under ``.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory``.
It configures the inventory file according to the SSH tunnel that Vagrant
automatically creates, and executes ``ansible-playbook`` with the correct
username and SSH key options to allow access. A typical automatically-created
inventory file may look something like this:
With our ``Vagrantfile`` example, Vagrant automatically creates an Ansible
inventory file in ``.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory``.
This inventory is configured according to the SSH tunnel that Vagrant
automatically creates. A typical automatically-created inventory file for a
single machine environment may look something like this:
.. code-block:: none
# Generated by Vagrant
machine ansible_host=127.0.0.1 ansible_port=2222
.. include:: ansible_ssh_changes_note.rst
default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222
If you want to run Ansible manually, you will want to make sure to pass
``ansible`` or ``ansible-playbook`` commands the correct arguments for the
username (usually ``vagrant``) and the SSH key (since Vagrant 1.7.0, this will be something like
``.vagrant/machines/[machine name]/[provider]/private_key``), and the autogenerated inventory file.
``ansible`` or ``ansible-playbook`` commands the correct arguments, at least
for the *username*, the *SSH private key* and the *inventory*.
Here is an example:
Here is an example using the Vagrant global insecure key (``config.ssh.insert_key``
must be set to ``false`` in your ``Vagrantfile``):
.. code-block:: bash
$ ansible-playbook -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory --private-key=.vagrant/machines/default/virtualbox/private_key -u vagrant playbook.yml
Note: Vagrant versions prior to 1.7.0 will use the private key located at ``~/.vagrant.d/insecure_private_key.``
$ ansible-playbook --private-key=~/.vagrant.d/insecure_private_key -u vagrant -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory playbook.yml
Here is a second example using the random private key that Vagrant 1.7+
automatically configures for each new VM (each key is stored in a path like
``.vagrant/machines/[machine name]/[provider]/private_key``):
.. code-block:: bash
$ ansible-playbook --private-key=.vagrant/machines/default/virtualbox/private_key -u vagrant -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory playbook.yml
Advanced Usages
```````````````
The "Tips and Tricks" chapter of the `Ansible Provisioner documentation
<http://docs.vagrantup.com/v2/provisioning/ansible.html>`_ provides detailed information about more advanced Ansible features like:
- how to parallely execute a playbook in a multi-machine environment
- how to integrate a local ``ansible.cfg`` configuration file
.. seealso::
`Vagrant Home <http://www.vagrantup.com/>`_
The Vagrant homepage with downloads
`Vagrant Documentation <http://docs.vagrantup.com/v2/>`_
Vagrant Documentation
`Ansible Provisioner <http://docs.vagrantup.com/v2/provisioning/ansible.html>`_
The Vagrant documentation for the Ansible provisioner
:doc:`playbooks`
An introduction to playbooks
`Vagrant Home <http://www.vagrantup.com/>`_
The Vagrant homepage with downloads
`Vagrant Documentation <http://docs.vagrantup.com/v2/>`_
Vagrant Documentation
`Ansible Provisioner <http://docs.vagrantup.com/v2/provisioning/ansible.html>`_
The Vagrant documentation for the Ansible provisioner
`Vagrant Issue Tracker <https://github.com/mitchellh/vagrant/issues?q=is%3Aopen+is%3Aissue+label%3Aprovisioners%2Fansible>`_
The open issues for the Ansible provisioner in the Vagrant project
:doc:`playbooks`
An introduction to playbooks

View File

@ -112,7 +112,7 @@ For example, using double rather than single quotes in the above example would
evaluate the variable on the box you were on.
So far we've been demoing simple command execution, but most Ansible modules usually do not work like
simple scripts. They make the remote system look like you state, and run the commands necessary to
simple scripts. They make the remote system look like a state, and run the commands necessary to
get it there. This is commonly referred to as 'idempotence', and is a core design goal of Ansible.
However, we also recognize that running arbitrary commands is equally important, so Ansible easily supports both.

View File

@ -897,3 +897,19 @@ The normal behaviour is for operations to copy the existing context or use the u
The default list is: nfs,vboxsf,fuse,ramfs::
special_context_filesystems = nfs,vboxsf,fuse,ramfs,myspecialfs
Galaxy Settings
---------------
The following options can be set in the [galaxy] section of ansible.cfg:
server
======
Override the default Galaxy server value of https://galaxy.ansible.com. Useful if you have a hosted version of the Galaxy web app or want to point to the testing site https://galaxy-qa.ansible.com. It does not work against private, hosted repos, which Galaxy can use for fetching and installing roles.
ignore_certs
============
If set to *yes*, ansible-galaxy will not validate TLS certificates. Handy for testing against a server with a self-signed certificate
.

View File

@ -111,9 +111,8 @@ If you use boto profiles to manage multiple AWS accounts, you can pass ``--profi
aws_access_key_id = <prod access key>
aws_secret_access_key = <prod secret key>
You can then run ``ec2.py --profile prod`` to get the inventory for the prod account, or run playbooks with: ``ansible-playbook -i 'ec2.py --profile prod' myplaybook.yml``.
Alternatively, use the ``AWS_PROFILE`` variable - e.g. ``AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml``
You can then run ``ec2.py --profile prod`` to get the inventory for the prod account, this option is not supported by ``anisble-playbook`` though.
But you can use the ``AWS_PROFILE`` variable - e.g. ``AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml``
Since each region requires its own API call, if you are only using a small set of regions, feel free to edit ``ec2.ini`` and list only the regions you are interested in. There are other config options in ``ec2.ini`` including cache control, and destination variables.

View File

@ -27,12 +27,11 @@ What Version To Pick?
`````````````````````
Because it runs so easily from source and does not require any installation of software on remote
machines, many users will actually track the development version.
machines, many users will actually track the development version.
Ansible's release cycles are usually about two months long. Due to this
short release cycle, minor bugs will generally be fixed in the next release versus maintaining
backports on the stable branch. Major bugs will still have maintenance releases when needed, though
these are infrequent.
Ansible's release cycles are usually about four months long. Due to this short release cycle,
minor bugs will generally be fixed in the next release versus maintaining backports on the stable branch.
Major bugs will still have maintenance releases when needed, though these are infrequent.
If you are wishing to run the latest released version of Ansible and you are running Red Hat Enterprise Linux (TM), CentOS, Fedora, Debian, or Ubuntu, we recommend using the OS package manager.

View File

@ -26,12 +26,12 @@ Installing on the Control Machine
On a Linux control machine::
pip install https://github.com/diyan/pywinrm/archive/master.zip#egg=pywinrm
pip install "pywinrm>=0.1.1"
Active Directory Support
++++++++++++++++++++++++
If you wish to connect to domain accounts published through Active Directory (as opposed to local accounts created on the remote host), you will need to install the "python-kerberos" module and the MIT krb5 libraries it depends on.
If you wish to connect to domain accounts published through Active Directory (as opposed to local accounts created on the remote host), you will need to install the "python-kerberos" module on the Ansible control host (and the MIT krb5 libraries it depends on). The Ansible control host also requires a properly configured computer account in Active Directory.
Installing python-kerberos dependencies
---------------------------------------
@ -131,7 +131,9 @@ To test this, ping the windows host you want to control by name then use the ip
If you get different hostnames back than the name you originally pinged, speak to your active directory administrator and get them to check that DNS Scavenging is enabled and that DNS and DHCP are updating each other.
Check your ansible controller's clock is synchronised with your domain controller. Kerberos is time sensitive and a little clock drift can cause tickets not be granted.
Ensure that the Ansible controller has a properly configured computer account in the domain.
Check your Ansible controller's clock is synchronised with your domain controller. Kerberos is time sensitive and a little clock drift can cause tickets not be granted.
Check you are using the real fully qualified domain name for the domain. Sometimes domains are commonly known to users by aliases. To check this run:
@ -165,6 +167,8 @@ In group_vars/windows.yml, define the following inventory variables::
ansible_password: SecretPasswordGoesHere
ansible_port: 5986
ansible_connection: winrm
# The following is necessary for Python 2.7.9+ when using default WinRM self-signed certificates:
ansible_winrm_server_cert_validation: ignore
Although Ansible is mostly an SSH-oriented system, Windows management will not happen over SSH (`yet <http://blogs.msdn.com/b/powershell/archive/2015/06/03/looking-forward-microsoft-support-for-secure-shell-ssh.aspx>`).
@ -189,6 +193,7 @@ Since 2.0, the following custom inventory variables are also supported for addit
* ``ansible_winrm_path``: Specify an alternate path to the WinRM endpoint. Ansible uses ``/wsman`` by default.
* ``ansible_winrm_realm``: Specify the realm to use for Kerberos authentication. If the username contains ``@``, Ansible will use the part of the username after ``@`` by default.
* ``ansible_winrm_transport``: Specify one or more transports as a comma-separated list. By default, Ansible will use ``kerberos,plaintext`` if the ``kerberos`` module is installed and a realm is defined, otherwise ``plaintext``.
* ``ansible_winrm_server_cert_validation``: Specify the server certificate validation mode (``ignore`` or ``validate``). Ansible defaults to ``validate`` on Python 2.7.9 and higher, which will result in certificate validation errors against the Windows self-signed certificates. Unless verifiable certificates have been configured on the WinRM listeners, this should be set to ``ignore``
* ``ansible_winrm_*``: Any additional keyword arguments supported by ``winrm.Protocol`` may be provided.
.. _windows_system_prep:
@ -221,7 +226,7 @@ Getting to PowerShell 3.0 or higher
PowerShell 3.0 or higher is needed for most provided Ansible modules for Windows, and is also required to run the above setup script. Note that PowerShell 3.0 is only supported on Windows 7 SP1, Windows Server 2008 SP1, and later releases of Windows.
Looking at an ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 <https://github.com/cchurch/ansible/blob/devel/examples/scripts/upgrade_to_ps3.ps1>`_ script onto the remote host and run a PowerShell console as an administrator. You will now be running PowerShell 3 and can try connectivity again using the win_ping technique referenced above.
Looking at an Ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 <https://github.com/cchurch/ansible/blob/devel/examples/scripts/upgrade_to_ps3.ps1>`_ script onto the remote host and run a PowerShell console as an administrator. You will now be running PowerShell 3 and can try connectivity again using the win_ping technique referenced above.
.. _what_windows_modules_are_available:
@ -248,10 +253,10 @@ Note there are a few other Ansible modules that don't start with "win" that also
Developers: Supported modules and how it works
``````````````````````````````````````````````
Developing ansible modules are covered in a `later section of the documentation <http://docs.ansible.com/developing_modules.html>`_, with a focus on Linux/Unix.
What if you want to write Windows modules for ansible though?
Developing Ansible modules are covered in a `later section of the documentation <http://docs.ansible.com/developing_modules.html>`_, with a focus on Linux/Unix.
What if you want to write Windows modules for Ansible though?
For Windows, ansible modules are implemented in PowerShell. Skim those Linux/Unix module development chapters before proceeding.
For Windows, Ansible modules are implemented in PowerShell. Skim those Linux/Unix module development chapters before proceeding.
Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree. For example, if a module is named
"library/windows/win_ping", there will be embedded documentation in the "win_ping" file, and the actual PowerShell code will live in a "win_ping.ps1" file. Take a look at the sources and this will make more sense.
@ -351,7 +356,7 @@ form of new modules, tweaks to existing modules, documentation, or something els
:doc:`developing_modules`
How to write modules
:doc:`playbooks`
Learning ansible's configuration management language
Learning Ansible's configuration management language
`List of Windows Modules <http://docs.ansible.com/list_of_windows_modules.html>`_
Windows specific module list, all implemented in PowerShell
`Mailing List <http://groups.google.com/group/ansible-project>`_

View File

@ -8,6 +8,6 @@ The source of these modules is hosted on GitHub in the `ansible-modules-core <ht
If you believe you have found a bug in a core module and are already running the latest stable or development version of Ansible, first look in the `issue tracker at github.com/ansible/ansible-modules-core <http://github.com/ansible/ansible-modules-core>`_ to see if a bug has already been filed. If not, we would be grateful if you would file one.
Should you have a question rather than a bug report, inquries are welcome on the `ansible-project google group <https://groups.google.com/forum/#!forum/ansible-project>`_ or on Ansible's "#ansible" channel, located on irc.freenode.net. Development oriented topics should instead use the similar `ansible-devel google group <https://groups.google.com/forum/#!forum/ansible-devel>`_.
Should you have a question rather than a bug report, inquiries are welcome on the `ansible-project google group <https://groups.google.com/forum/#!forum/ansible-project>`_ or on Ansible's "#ansible" channel, located on irc.freenode.net. Development oriented topics should instead use the similar `ansible-devel google group <https://groups.google.com/forum/#!forum/ansible-devel>`_.
Documentation updates for these modules can also be edited directly in the module itself and by submitting a pull request to the module source code, just look for the "DOCUMENTATION" block in the source tree.

View File

@ -130,6 +130,29 @@ Here is an example::
Note that you must have passphrase-less SSH keys or an ssh-agent configured for this to work, otherwise rsync
will need to ask for a passphrase.
.. _delegate_facts:
Delegated facts
```````````````
.. versionadded:: 2.0
By default, any fact gathered by a delegated task are assigned to the `inventory_hostname` (the current host) instead of the host which actually produced the facts (the delegated to host).
In 2.0, the directive `delegate_facts` may be set to `True` to assign the task's gathered facts to the delegated host instead of the current one.::
- hosts: app_servers
tasks:
- name: gather facts from db servers
setup:
delegate_to: "{{item}}"
delegate_facts: True
with_items: "{{groups['dbservers'}}"
The above will gather facts for the machines in the dbservers group and assign the facts to those machines and not to app_servers.
This way you can lookup `hostvars['dbhost1']['default_ipv4_addresses'][0]` even though dbservers were not part of the play, or left out by using `--limit`.
.. _run_once:
Run Once
@ -159,13 +182,18 @@ This can be optionally paired with "delegate_to" to specify an individual host t
delegate_to: web01.example.org
When "run_once" is not used with "delegate_to" it will execute on the first host, as defined by inventory,
in the group(s) of hosts targeted by the play. e.g. webservers[0] if the play targeted "hosts: webservers".
in the group(s) of hosts targeted by the play - e.g. webservers[0] if the play targeted "hosts: webservers".
This approach is similar, although more concise and cleaner than applying a conditional to a task such as::
This approach is similar to applying a conditional to a task such as::
- command: /opt/application/upgrade_db.py
when: inventory_hostname == webservers[0]
.. note::
When used together with "serial", tasks marked as "run_once" will be ran on one host in *each* serial batch.
If it's crucial that the task is run only once regardless of "serial" mode, use
:code:`inventory_hostname == my_group_name[0]` construct.
.. _local_playbooks:
Local Playbooks

View File

@ -31,7 +31,7 @@ The environment can also be stored in a variable, and accessed like so::
tasks:
- apt: name=cobbler state=installed
environment: proxy_env
environment: "{{proxy_env}}"
You can also use it at a playbook level::

View File

@ -514,20 +514,25 @@ To match strings against a regex, use the "match" or "search" filter::
To replace text in a string with regex, use the "regex_replace" filter::
# convert "ansible" to "able"
# convert "ansible" to "able"
{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}
# convert "foobar" to "bar"
{{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }}
# convert "localhost:80" to "localhost, 80" using named groups
{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}
.. note:: Prior to ansible 2.0, if "regex_replace" filter was used with variables inside YAML arguments (as opposed to simpler 'key=value' arguments),
then you needed to escape backreferences (e.g. ``\\1``) with 4 backslashes (``\\\\``) instead of 2 (``\\``).
.. versionadded:: 2.0
To escape special characters within a regex, use the "regex_escape" filter::
# convert '^f.*o(.*)$' to '\^f\.\*o\(\.\*\)\$'
{{ '^f.*o(.*)$' | regex_escape() }}
To make use of one attribute from each item in a list of complex variables, use the "map" filter (see the `Jinja2 map() docs`_ for more)::
# get a comma-separated list of the mount points (e.g. "/,/mnt/stuff") on a host

View File

@ -386,6 +386,7 @@ won't need them for much else.
* Handler names live in a global namespace.
* If two handler tasks have the same name, only one will run.
`* <https://github.com/ansible/ansible/issues/4943>`_
* You cannot notify a handler that is defined inside of an include
Roles are described later on, but it's worthwhile to point out that:

View File

@ -240,6 +240,112 @@ If you're not using 2.0 yet, you can do something similar with the credstash too
debug: msg="Poor man's credstash lookup! {{ lookup('pipe', 'credstash -r us-west-1 get my-other-password') }}"
.. _dns_lookup:
The DNS Lookup (dig)
````````````````````
.. versionadded:: 1.9.0
.. warning:: This lookup depends on the `dnspython <http://www.dnspython.org/>`_
library.
The ``dig`` lookup runs queries against DNS servers to retrieve DNS records for
a specific name (*FQDN* - fully qualified domain name). It is possible to lookup any DNS record in this manner.
There is a couple of different syntaxes that can be used to specify what record
should be retrieved, and for which name. It is also possible to explicitly
specify the DNS server(s) to use for lookups.
In its simplest form, the ``dig`` lookup plugin can be used to retrieve an IPv4
address (DNS ``A`` record) associated with *FQDN*:
.. note:: If you need to obtain the ``AAAA`` record (IPv6 address), you must
specify the record type explicitly. Syntax for specifying the record
type is described below.
.. note:: The trailing dot in most of the examples listed is purely optional,
but is specified for completeness/correctness sake.
::
- debug: msg="The IPv4 address for example.com. is {{ lookup('dig', 'example.com.')}}"
In addition to (default) ``A`` record, it is also possible to specify a different
record type that should be queried. This can be done by either passing-in
additional parameter of format ``qtype=TYPE`` to the ``dig`` lookup, or by
appending ``/TYPE`` to the *FQDN* being queried. For example::
- debug: msg="The TXT record for gmail.com. is {{ lookup('dig', 'gmail.com.', 'qtype=TXT') }}"
- debug: msg="The TXT record for gmail.com. is {{ lookup('dig', 'gmail.com./TXT') }}"
If multiple values are associated with the requested record, the results will be
returned as a comma-separated list. In such cases you may want to pass option
``wantlist=True`` to the plugin, which will result in the record values being
returned as a list over which you can iterate later on::
- debug: msg="One of the MX records for gmail.com. is {{ item }}"
with_items: "{{ lookup('dig', 'gmail.com./MX', wantlist=True) }}"
In case of reverse DNS lookups (``PTR`` records), you can also use a convenience
syntax of format ``IP_ADDRESS/PTR``. The following three lines would produce the
same output::
- debug: msg="Reverse DNS for 8.8.8.8 is {{ lookup('dig', '8.8.8.8/PTR') }}"
- debug: msg="Reverse DNS for 8.8.8.8 is {{ lookup('dig', '8.8.8.8.in-addr.arpa./PTR') }}"
- debug: msg="Reverse DNS for 8.8.8.8 is {{ lookup('dig', '8.8.8.8.in-addr.arpa.', 'qtype=PTR') }}"
By default, the lookup will rely on system-wide configured DNS servers for
performing the query. It is also possible to explicitly specify DNS servers to
query using the ``@DNS_SERVER_1,DNS_SERVER_2,...,DNS_SERVER_N`` notation. This
needs to be passed-in as an additional parameter to the lookup. For example::
- debug: msg="Querying 8.8.8.8 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@8.8.8.8') }}"
In some cases the DNS records may hold a more complex data structure, or it may
be useful to obtain the results in a form of a dictionary for future
processing. The ``dig`` lookup supports parsing of a number of such records,
with the result being returned as a dictionary. This way it is possible to
easily access such nested data. This return format can be requested by
passing-in the ``flat=0`` option to the lookup. For example::
- debug: msg="XMPP service for gmail.com. is available at {{ item.target }} on port {{ item.port }}"
with_items: "{{ lookup('dig', '_xmpp-server._tcp.gmail.com./SRV', 'flat=0', wantlist=True) }}"
Take note that due to the way Ansible lookups work, you must pass the
``wantlist=True`` argument to the lookup, otherwise Ansible will report errors.
Currently the dictionary results are supported for the following records:
.. note:: *ALL* is not a record per-se, merely the listed fields are available
for any record results you retrieve in the form of a dictionary.
========== =============================================================================
Record Fields
---------- -----------------------------------------------------------------------------
*ALL* owner, ttl, type
A address
AAAA address
CNAME target
DNAME target
DLV algorithm, digest_type, key_tag, digest
DNSKEY flags, algorithm, protocol, key
DS algorithm, digest_type, key_tag, digest
HINFO cpu, os
LOC latitude, longitude, altitude, size, horizontal_precision, vertical_precision
MX preference, exchange
NAPTR order, preference, flags, service, regexp, replacement
NS target
NSEC3PARAM algorithm, flags, iterations, salt
PTR target
RP mbox, txt
SOA mname, rname, serial, refresh, retry, expire, minimum
SPF strings
SRV priority, weight, port, target
SSHFP algorithm, fp_type, fingerprint
TLSA usage, selector, mtype, cert
TXT strings
========== =============================================================================
.. _more_lookups:
More Lookups

View File

@ -96,7 +96,7 @@ And you want to print every user's name and phone number. You can loop through
Looping over Files
``````````````````
``with_file`` iterates over a list of files, setting `item` to the content of each file in sequence. It can be used like this::
``with_file`` iterates over the content of a list of files, `item` will be set to the content of each file in sequence. It can be used like this::
---
- hosts: all

View File

@ -132,7 +132,7 @@ Note that you cannot do variable substitution when including one playbook
inside another.
.. note::
You can not conditionally path the location to an include file,
You can not conditionally pass the location to an include file,
like you can with 'vars_files'. If you find yourself needing to do
this, consider how you can restructure your playbook to be more
class/role oriented. This is to say you cannot use a 'fact' to
@ -191,11 +191,8 @@ This designates the following behaviors, for each role 'x':
- If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play
- If roles/x/vars/main.yml exists, variables listed therein will be added to the play
- If roles/x/meta/main.yml exists, any role dependencies listed therein will be added to the list of roles (1.3 and later)
- Any copy tasks can reference files in roles/x/files/ without having to path them relatively or absolutely
- Any script tasks can reference scripts in roles/x/files/ without having to path them relatively or absolutely
- Any template tasks can reference files in roles/x/templates/ without having to path them relatively or absolutely
- Any include tasks can reference files in roles/x/tasks/ without having to path them relatively or absolutely
- Any copy, script, template or include tasks (in the role) can reference files in roles/x/{files,templates,tasks}/ (dir depends on task) without having to path them relatively or absolutely
In Ansible 1.4 and later you can configure a roles_path to search for roles. Use this to check all of your common roles out to one location, and share
them easily between multiple playbook projects. See :doc:`intro_configuration` for details about how to set this up in ansible.cfg.

View File

@ -793,10 +793,10 @@ Basically, anything that goes into "role defaults" (the defaults folder inside t
.. rubric:: Footnotes
.. [1] Tasks in each role will see their own role's defaults tasks outside of roles will the last role's defaults
.. [2] Variables defined in inventory file or provided by dynamic inventory
.. [1] Tasks in each role will see their own role's defaults. Tasks defined outside of a role will see the last role's defaults.
.. [2] Variables defined in inventory file or provided by dynamic inventory.
.. note:: Within a any section, redefining a var will overwrite the previous instance.
.. note:: Within any section, redefining a var will overwrite the previous instance.
If multiple groups have the same variable, the last one loaded wins.
If you define a variable twice in a play's vars: section, the 2nd one wins.
.. note:: the previous describes the default config `hash_behavior=replace`, switch to 'merge' to only partially overwrite.

View File

@ -0,0 +1,202 @@
Porting Guide
=============
Playbook
--------
* backslash escapes When specifying parameters in jinja2 expressions in YAML
dicts, backslashes sometimes needed to be escaped twice. This has been fixed
in 2.0.x so that escaping once works. The following example shows how
playbooks must be modified::
# Syntax in 1.9.x
- debug:
msg: "{{ 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') }}"
# Syntax in 2.0.x
- debug:
msg: "{{ 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') }}"
# Output:
"msg": "test1 1\\3"
To make an escaped string that will work on all versions you have two options::
- debug: msg="{{ 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') }}"
uses key=value escaping which has not changed. The other option is to check for the ansible version::
"{{ (ansible_version|version_compare('ge', '2.0'))|ternary( 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') , 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') ) }}"
* trailing newline When a string with a trailing newline was specified in the
playbook via yaml dict format, the trailing newline was stripped. When
specified in key=value format, the trailing newlines were kept. In v2, both
methods of specifying the string will keep the trailing newlines. If you
relied on the trailing newline being stripped, you can change your playbook
using the following as an example::
# Syntax in 1.9.x
vars:
message: >
Testing
some things
tasks:
- debug:
msg: "{{ message }}"
# Syntax in 2.0.x
vars:
old_message: >
Testing
some things
message: "{{ old_messsage[:-1] }}"
- debug:
msg: "{{ message }}"
# Output
"msg": "Testing some things"
* When specifying complex args as a variable, the variable must use the full jinja2
variable syntax ('{{var_name}}') - bare variable names there are no longer accepted.
In fact, even specifying args with variables has been deprecated, and will not be
allowed in future versions::
---
- hosts: localhost
connection: local
gather_facts: false
vars:
my_dirs:
- { path: /tmp/3a, state: directory, mode: 0755 }
- { path: /tmp/3b, state: directory, mode: 0700 }
tasks:
- file:
args: "{{item}}" # <- args here uses the full variable syntax
with_items: my_dirs
* porting task includes
* More dynamic. Corner-case formats that were not supposed to work now do not, as expected.
* variables defined in the yaml dict format https://github.com/ansible/ansible/issues/13324
* templating (variables in playbooks and template lookups) has improved with regard to keeping the original instead of turning everything into a string.
If you need the old behavior, quote the value to pass it around as a string.
* Empty variables and variables set to null in yaml are no longer converted to empty strings. They will retain the value of `None`.
You can override the `null_representation` setting to an empty string in your config file by setting the `ANSIBLE_NULL_REPRESENTATION` environment variable.
* Extras callbacks must be whitelisted in ansible.cfg. Copying is no longer necessary but whitelisting in ansible.cfg must be completed.
* dnf module has been rewritten. Some minor changes in behavior may be observed.
* win_updates has been rewritten and works as expected now.
Deprecated
----------
While all items listed here will show a deprecation warning message, they still work as they did in 1.9.x. Please note that they will be removed in 2.2 (Ansible always waits two major releases to remove a deprecated feature).
* Bare variables in `with_` loops should instead use the “{{var}}” syntax, which helps eliminate ambiguity.
* The ansible-galaxy text format requirements file. Users should use the YAML format for requirements instead.
* Undefined variables within a `with_` loop’s list currently do not interrupt the loop, but they do issue a warning; in the future, they will issue an error.
* Using variables for task parameters is unsafe and will be removed in a future version. For example::
- hosts: localhost
gather_facts: no
vars:
debug_params:
msg: "hello there"
tasks:
- debug: "{{debug_params}}"
* Host patterns should use a comma (,) or colon (:) instead of a semicolon (;) to separate hosts/groups in the pattern.
* Ranges specified in host patterns should use the [x:y] syntax, instead of [x-y].
* Playbooks using privilege escalation should always use “become*” options rather than the old su*/sudo* options.
* The “short form” for vars_prompt is no longer supported.
For example::
vars_prompt:
variable_name: "Prompt string"
* Specifying variables at the top level of a task include statement is no longer supported. For example::
- include: foo.yml
a: 1
Should now be::
- include: foo.yml
args:
a: 1
* Setting any_errors_fatal on a task is no longer supported. This should be set at the play level only.
* Bare variables in the `environment` dictionary (for plays/tasks/etc.) are no longer supported. Variables specified there should use the full variable syntax: ‘{{foo}}’.
* Tags should no longer be specified with other parameters in a task include. Instead, they should be specified as an option on the task.
For example::
- include: foo.yml tags=a,b,c
Should be::
- include: foo.yml
tags: [a, b, c]
* The first_available_file option on tasks has been deprecated. Users should use the with_first_found option or lookup (‘first_found’, …) plugin.
Porting plugins
===============
In ansible-1.9.x, you would generally copy an existing plugin to create a new one. Simply implementing the methods and attributes that the caller of the plugin expected made it a plugin of that type. In ansible-2.0, most plugins are implemented by subclassing a base class for each plugin type. This way the custom plugin does not need to contain methods which are not customized.
Lookup plugins
--------------
* lookup plugins ; import version
Connection plugins
------------------
* connection plugins
Action plugins
--------------
* action plugins
Callback plugins
----------------
Although Ansible 2.0 provides a new callback API the old one continues to work
for most callback plugins. However, if your callback plugin makes use of
:attr:`self.playbook`, :attr:`self.play`, or :attr:`self.task` then you will
have to store the values for these yourself as ansible no longer automatically
populates the callback with them. Here's a short snippet that shows you how::
from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
def __init__(self):
self.playbook = None
self.play = None
self.task = None
def v2_playbook_on_start(self, playbook):
self.playbook = playbook
def v2_playbook_on_play_start(self, play):
self.play = play
def v2_playbook_on_task_start(self, task, is_conditional):
self.task = task
def v2_on_any(self, *args, **kwargs):
self._display.display('%s: %s: %s' % (self.playbook.name,
self.play.name, self.task))
Connection plugins
------------------
* connection plugins
Porting custom scripts
======================
Custom scripts that used the ``ansible.runner.Runner`` API in 1.x have to be ported in 2.x. Please refer to:
https://github.com/ansible/ansible/blob/devel/docsite/rst/developing_api.rst

View File

@ -14,7 +14,6 @@
#inventory = /etc/ansible/hosts
#library = /usr/share/my_modules/
#remote_tmp = $HOME/.ansible/tmp
#pattern = *
#forks = 5
#poll_interval = 15
#sudo_user = root
@ -182,7 +181,7 @@
#no_log = False
# prevents logging of tasks, but only on the targets, data is still logged on the master/controller
#no_target_syslog = True
#no_target_syslog = False
# controls the compression level of variables sent to
# worker processes. At the default of 0, no compression

View File

@ -10,35 +10,35 @@
# Ex 1: Ungrouped hosts, specify before any group headers.
green.example.com
blue.example.com
192.168.100.1
192.168.100.10
## 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
## [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
## 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
## [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
## db-[99:101]-node.example.com

View File

@ -140,7 +140,7 @@ def list_modules(module_dir, depth=0):
if os.path.isdir(d):
res = list_modules(d, depth + 1)
for key in res.keys():
for key in list(res.keys()):
if key in categories:
categories[key] = merge_hash(categories[key], res[key])
res.pop(key, None)
@ -451,7 +451,7 @@ def main():
categories = list_modules(options.module_dir)
last_category = None
category_names = categories.keys()
category_names = list(categories.keys())
category_names.sort()
category_list_path = os.path.join(options.output_dir, "modules_by_category.rst")

View File

@ -19,5 +19,5 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
__version__ = '2.0.0'
__version__ = '2.0.1.0'
__author__ = 'Ansible, Inc.'

View File

@ -27,12 +27,13 @@ import time
import yaml
import re
import getpass
import signal
import subprocess
from ansible import __version__
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.utils.unicode import to_bytes
from ansible.utils.unicode import to_bytes, to_unicode
try:
from __main__ import display
@ -44,7 +45,7 @@ except ImportError:
class SortedOptParser(optparse.OptionParser):
'''Optparser which sorts the options by opt before outputting --help'''
# TODO: epilog parsing: OptionParser.format_epilog = lambda self, formatter: self.epilog
#FIXME: epilog parsing: OptionParser.format_epilog = lambda self, formatter: self.epilog
def format_help(self, formatter=None, epilog=None):
self.option_list.sort(key=operator.methodcaller('get_opt_string'))
@ -66,7 +67,7 @@ class CLI(object):
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
# -S (chop long lines) -X (disable termcap init and de-init)
def __init__(self, args):
def __init__(self, args, callback=None):
"""
Base init method for all command line programs
"""
@ -75,6 +76,21 @@ class CLI(object):
self.options = None
self.parser = None
self.action = None
self.callback = callback
def _terminate(self, signum=None, framenum=None):
if signum == signal.SIGTERM:
if hasattr(os, 'getppid'):
display.debug("Termination requested in parent, shutting down gracefully")
signal.signal(signal.SIGTERM, signal.SIG_DFL)
else:
display.debug("Term signal in child, harakiri!")
signal.signal(signal.SIGTERM, signal.SIG_IGN)
raise SystemExit
#NOTE: if ever want to make this immediately kill children use on parent:
#os.killpg(os.getpgid(0), signal.SIGTERM)
def set_action(self):
"""
@ -104,9 +120,12 @@ class CLI(object):
if self.options.verbosity > 0:
if C.CONFIG_FILE:
display.display("Using %s as config file" % C.CONFIG_FILE)
display.display(u"Using %s as config file" % to_unicode(C.CONFIG_FILE))
else:
display.display("No config file found; using defaults")
display.display(u"No config file found; using defaults")
# Manage user interruptions
#signal.signal(signal.SIGTERM, self._terminate)
@staticmethod
def ask_vault_passwords(ask_new_vault_pass=False, rekey=False):
@ -191,12 +210,9 @@ class CLI(object):
if runas_opts:
# Check for privilege escalation conflicts
if (op.su or op.su_user or op.ask_su_pass) and \
(op.sudo or op.sudo_user or op.ask_sudo_pass) or \
(op.su or op.su_user or op.ask_su_pass) and \
(op.become or op.become_user or op.become_ask_pass) or \
(op.sudo or op.sudo_user or op.ask_sudo_pass) and \
(op.become or op.become_user or op.become_ask_pass):
if (op.su or op.su_user) and (op.sudo or op.sudo_user) or \
(op.su or op.su_user) and (op.become or op.become_user) or \
(op.sudo or op.sudo_user) and (op.become or op.become_user):
self.parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
"and su arguments ('-su', '--su-user', and '--ask-su-pass') "
@ -213,7 +229,7 @@ class CLI(object):
@staticmethod
def base_parser(usage="", output_opts=False, runas_opts=False, meta_opts=False, runtask_opts=False, vault_opts=False, module_opts=False,
async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, inventory_opts=False, epilog=None, fork_opts=False):
async_opts=False, connect_opts=False, subset_opts=False, check_opts=False, inventory_opts=False, epilog=None, fork_opts=False, runas_prompt_opts=False):
''' create an options parser for most ansible scripts '''
# TODO: implement epilog parsing
@ -226,7 +242,7 @@ class CLI(object):
if inventory_opts:
parser.add_option('-i', '--inventory-file', dest='inventory',
help="specify inventory host path (default=%s) or comma separated host list" % C.DEFAULT_HOST_LIST,
help="specify inventory host path (default=%s) or comma separated host list." % C.DEFAULT_HOST_LIST,
default=C.DEFAULT_HOST_LIST, action="callback", callback=CLI.expand_tilde, type=str)
parser.add_option('--list-hosts', dest='listhosts', action='store_true',
help='outputs a list of matching hosts; does not execute anything else')
@ -246,14 +262,15 @@ class CLI(object):
help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS)
if vault_opts:
parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true',
parser.add_option('--ask-vault-pass', default=C.DEFAULT_ASK_VAULT_PASS, dest='ask_vault_pass', action='store_true',
help='ask for vault password')
parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE, dest='vault_password_file',
help="vault password file", action="callback", callback=CLI.expand_tilde, type=str)
parser.add_option('--new-vault-password-file', dest='new_vault_password_file',
help="new vault password file for rekey", action="callback", callback=CLI.expand_tilde, type=str)
parser.add_option('--output', default=None, dest='output_file',
help='output file name for encrypt or decrypt; use - for stdout')
help='output file name for encrypt or decrypt; use - for stdout',
action="callback", callback=CLI.expand_tilde, type=str)
if subset_opts:
parser.add_option('-t', '--tags', dest='tags', default='all',
@ -269,10 +286,6 @@ class CLI(object):
if runas_opts:
# priv user defaults to root later on to enable detecting when this option was given here
parser.add_option('-K', '--ask-sudo-pass', default=C.DEFAULT_ASK_SUDO_PASS, dest='ask_sudo_pass', action='store_true',
help='ask for sudo password (deprecated, use become)')
parser.add_option('--ask-su-pass', default=C.DEFAULT_ASK_SU_PASS, dest='ask_su_pass', action='store_true',
help='ask for su password (deprecated, use become)')
parser.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", dest='sudo',
help="run operations with sudo (nopasswd) (deprecated, use become)")
parser.add_option('-U', '--sudo-user', dest='sudo_user', default=None,
@ -289,6 +302,12 @@ class CLI(object):
help="privilege escalation method to use (default=%s), valid choices: [ %s ]" % (C.DEFAULT_BECOME_METHOD, ' | '.join(C.BECOME_METHODS)))
parser.add_option('--become-user', default=None, dest='become_user', type='string',
help='run operations as this user (default=%s)' % C.DEFAULT_BECOME_USER)
if runas_opts or runas_prompt_opts:
parser.add_option('-K', '--ask-sudo-pass', default=C.DEFAULT_ASK_SUDO_PASS, dest='ask_sudo_pass', action='store_true',
help='ask for sudo password (deprecated, use become)')
parser.add_option('--ask-su-pass', default=C.DEFAULT_ASK_SU_PASS, dest='ask_su_pass', action='store_true',
help='ask for su password (deprecated, use become)')
parser.add_option('--ask-become-pass', default=False, dest='become_ask_pass', action='store_true',
help='ask for privilege escalation password')
@ -458,7 +477,7 @@ class CLI(object):
os.environ['LESS'] = CLI.LESS_OPTS
try:
cmd = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
cmd.communicate(input=text.encode(sys.stdout.encoding))
cmd.communicate(input=to_bytes(text))
except IOError:
pass
except KeyboardInterrupt:

View File

@ -70,7 +70,7 @@ class AdHocCLI(CLI):
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
default=C.DEFAULT_MODULE_NAME)
self.options, self.args = self.parser.parse_args()
self.options, self.args = self.parser.parse_args(self.args[1:])
if len(self.args) != 1:
raise AnsibleOptionsError("Missing target hosts")
@ -124,17 +124,13 @@ class AdHocCLI(CLI):
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=self.options.inventory)
variable_manager.set_inventory(inventory)
hosts = inventory.list_hosts(pattern)
no_hosts = False
if len(hosts) == 0:
display.warning("provided hosts list is empty, only localhost is available")
no_hosts = True
if self.options.subset:
inventory.subset(self.options.subset)
if len(inventory.list_hosts(pattern)) == 0 and not no_hosts:
# Invalid limit
raise AnsibleError("Specified --limit does not match any hosts")
hosts = inventory.list_hosts(pattern)
if len(hosts) == 0:
raise AnsibleError("Specified hosts options do not match any hosts")
if self.options.listhosts:
display.display(' hosts (%d):' % len(hosts))
@ -158,14 +154,18 @@ class AdHocCLI(CLI):
play_ds = self._play_ds(pattern, self.options.seconds, self.options.poll_interval)
play = Play().load(play_ds, variable_manager=variable_manager, loader=loader)
if self.options.one_line:
if self.callback:
cb = self.callback
elif self.options.one_line:
cb = 'oneline'
else:
cb = 'minimal'
run_tree=False
if self.options.tree:
C.DEFAULT_CALLBACK_WHITELIST.append('tree')
C.TREE_DIR = self.options.tree
run_tree=True
# now create a task queue manager to execute the play
self._tqm = None
@ -177,7 +177,10 @@ class AdHocCLI(CLI):
options=self.options,
passwords=passwords,
stdout_callback=cb,
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
run_tree=run_tree,
)
result = self._tqm.run(play)
finally:
if self._tqm:

View File

@ -62,7 +62,7 @@ class DocCLI(CLI):
self.parser.add_option("-s", "--snippet", action="store_true", default=False, dest='show_snippet',
help='Show playbook snippet for specified module(s)')
self.options, self.args = self.parser.parse_args()
self.options, self.args = self.parser.parse_args(self.args[1:])
display.verbosity = self.options.verbosity
def run(self):
@ -90,7 +90,8 @@ class DocCLI(CLI):
for module in self.args:
try:
filename = module_loader.find_plugin(module)
# if the module lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs
filename = module_loader.find_plugin(module, mod_type='.py')
if filename is None:
display.warning("module %s not found in %s\n" % (module, DocCLI.print_paths(module_loader)))
continue
@ -167,7 +168,8 @@ class DocCLI(CLI):
if module in module_docs.BLACKLIST_MODULES:
continue
filename = module_loader.find_plugin(module)
# if the module lives in a non-python file (eg, win_X.ps1), require the corresponding python file for docs
filename = module_loader.find_plugin(module, mod_type='.py')
if filename is None:
continue

View File

@ -22,10 +22,10 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import os.path
import sys
import yaml
import time
from collections import defaultdict
from jinja2 import Environment
@ -36,7 +36,10 @@ from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.galaxy import Galaxy
from ansible.galaxy.api import GalaxyAPI
from ansible.galaxy.role import GalaxyRole
from ansible.galaxy.login import GalaxyLogin
from ansible.galaxy.token import GalaxyToken
from ansible.playbook.role.requirement import RoleRequirement
from ansible.utils.unicode import to_unicode
try:
from __main__ import display
@ -44,14 +47,12 @@ except ImportError:
from ansible.utils.display import Display
display = Display()
class GalaxyCLI(CLI):
VALID_ACTIONS = ("init", "info", "install", "list", "remove", "search")
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_fields", "average_aw_composite", "average_aw_score", "url" )
VALID_ACTIONS = ("delete", "import", "info", "init", "install", "list", "login", "remove", "search", "setup")
def __init__(self, args):
self.api = None
self.galaxy = None
super(GalaxyCLI, self).__init__(args)
@ -67,7 +68,17 @@ class GalaxyCLI(CLI):
self.set_action()
# options specific to actions
if self.action == "info":
if self.action == "delete":
self.parser.set_usage("usage: %prog delete [options] github_user github_repo")
elif self.action == "import":
self.parser.set_usage("usage: %prog import [options] github_user github_repo")
self.parser.add_option('--no-wait', dest='wait', action='store_false', default=True,
help='Don\'t wait for import results.')
self.parser.add_option('--branch', dest='reference',
help='The name of a branch to import. Defaults to the repository\'s default branch (usually master)')
self.parser.add_option('--status', dest='check_status', action='store_true', default=False,
help='Check the status of the most recent import request for given github_user/github_repo.')
elif self.action == "info":
self.parser.set_usage("usage: %prog info [options] role_name[,version]")
elif self.action == "init":
self.parser.set_usage("usage: %prog init [options] role_name")
@ -88,31 +99,42 @@ class GalaxyCLI(CLI):
self.parser.set_usage("usage: %prog remove role1 role2 ...")
elif self.action == "list":
self.parser.set_usage("usage: %prog list [role_name]")
elif self.action == "login":
self.parser.set_usage("usage: %prog login [options]")
self.parser.add_option('--github-token', dest='token', default=None,
help='Identify with github token rather than username and password.')
elif self.action == "search":
self.parser.add_option('--platforms', dest='platforms',
help='list of OS platforms to filter by')
self.parser.add_option('--galaxy-tags', dest='tags',
help='list of galaxy tags to filter by')
self.parser.set_usage("usage: %prog search [<search_term>] [--galaxy-tags <galaxy_tag1,galaxy_tag2>] [--platforms platform]")
self.parser.add_option('--author', dest='author',
help='GitHub username')
self.parser.set_usage("usage: %prog search [searchterm1 searchterm2] [--galaxy-tags galaxy_tag1,galaxy_tag2] [--platforms platform1,platform2] [--author username]")
elif self.action == "setup":
self.parser.set_usage("usage: %prog setup [options] source github_user github_repo secret")
self.parser.add_option('--remove', dest='remove_id', default=None,
help='Remove the integration matching the provided ID value. Use --list to see ID values.')
self.parser.add_option('--list', dest="setup_list", action='store_true', default=False,
help='List all of your integrations.')
# options that apply to more than one action
if self.action != "init":
if not self.action in ("delete","import","init","login","setup"):
self.parser.add_option('-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
help='The path to the directory containing your roles. '
'The default is the roles_path configured in your '
'ansible.cfg file (/etc/ansible/roles if not configured)')
if self.action in ("info","init","install","search"):
self.parser.add_option('-s', '--server', dest='api_server', default="https://galaxy.ansible.com",
if self.action in ("import","info","init","install","login","search","setup","delete"):
self.parser.add_option('-s', '--server', dest='api_server', default=C.GALAXY_SERVER,
help='The API server destination')
self.parser.add_option('-c', '--ignore-certs', action='store_false', dest='validate_certs', default=True,
self.parser.add_option('-c', '--ignore-certs', action='store_true', dest='ignore_certs', default=False,
help='Ignore SSL certificate validation errors.')
if self.action in ("init","install"):
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False,
help='Force overwriting an existing role')
# get options, args and galaxy object
self.options, self.args =self.parser.parse_args()
display.verbosity = self.options.verbosity
self.galaxy = Galaxy(self.options)
@ -120,15 +142,13 @@ class GalaxyCLI(CLI):
return True
def run(self):
super(GalaxyCLI, self).run()
# if not offline, get connect to galaxy api
if self.action in ("info","install", "search") or (self.action == 'init' and not self.options.offline):
api_server = self.options.api_server
self.api = GalaxyAPI(self.galaxy, api_server)
if not self.api:
raise AnsibleError("The API server (%s) is not responding, please try again later." % api_server)
if self.action in ("import","info","install","search","login","setup","delete") or \
(self.action == 'init' and not self.options.offline):
self.api = GalaxyAPI(self.galaxy)
self.execute()
@ -142,8 +162,8 @@ class GalaxyCLI(CLI):
def _display_role_info(self, role_info):
text = "\nRole: %s \n" % role_info['name']
text += "\tdescription: %s \n" % role_info.get('description', '')
text = [u"", u"Role: %s" % to_unicode(role_info['name'])]
text.append(u"\tdescription: %s" % role_info.get('description', ''))
for k in sorted(role_info.keys()):
@ -152,14 +172,15 @@ class GalaxyCLI(CLI):
if isinstance(role_info[k], dict):
text += "\t%s: \n" % (k)
text.append(u"\t%s:" % (k))
for key in sorted(role_info[k].keys()):
if key in self.SKIP_INFO_KEYS:
continue
text += "\t\t%s: %s\n" % (key, role_info[k][key])
text.append(u"\t\t%s: %s" % (key, role_info[k][key]))
else:
text += "\t%s: %s\n" % (k, role_info[k])
text.append(u"\t%s: %s" % (k, role_info[k]))
return text
return u'\n'.join(text)
############################
# execute actions
@ -188,7 +209,7 @@ class GalaxyCLI(CLI):
"however it will reset any main.yml files that may have\n"
"been modified there already." % role_path)
# create the default README.md
# create default README.md
if not os.path.exists(role_path):
os.makedirs(role_path)
readme_path = os.path.join(role_path, "README.md")
@ -196,9 +217,16 @@ class GalaxyCLI(CLI):
f.write(self.galaxy.default_readme)
f.close()
# create default .travis.yml
travis = Environment().from_string(self.galaxy.default_travis).render()
f = open(os.path.join(role_path, '.travis.yml'), 'w')
f.write(travis)
f.close()
for dir in GalaxyRole.ROLE_DIRS:
dir_path = os.path.join(init_path, role_name, dir)
main_yml_path = os.path.join(dir_path, 'main.yml')
# create the directory if it doesn't exist already
if not os.path.exists(dir_path):
os.makedirs(dir_path)
@ -234,6 +262,20 @@ class GalaxyCLI(CLI):
f.write(rendered_meta)
f.close()
pass
elif dir == "tests":
# create tests/test.yml
inject = dict(
role_name = role_name
)
playbook = Environment().from_string(self.galaxy.default_test).render(inject)
f = open(os.path.join(dir_path, 'test.yml'), 'w')
f.write(playbook)
f.close()
# create tests/inventory
f = open(os.path.join(dir_path, 'inventory'), 'w')
f.write('localhost')
f.close()
elif dir not in ('files','templates'):
# just write a (mostly) empty YAML file for main.yml
f = open(main_yml_path, 'w')
@ -282,9 +324,11 @@ class GalaxyCLI(CLI):
if role_spec:
role_info.update(role_spec)
data += self._display_role_info(role_info)
data = self._display_role_info(role_info)
### FIXME: This is broken in both 1.9 and 2.0 as
# _display_role_info() always returns something
if not data:
data += "\n- the role %s was not found" % role
data = u"\n- the role %s was not found" % role
self.pager(data)
@ -325,7 +369,7 @@ class GalaxyCLI(CLI):
for role in required_roles:
role = RoleRequirement.role_yaml_parse(role)
display.debug('found role %s in yaml file' % str(role))
display.vvv('found role %s in yaml file' % str(role))
if 'name' not in role and 'scm' not in role:
raise AnsibleError("Must specify name or src for role")
roles_left.append(GalaxyRole(self.galaxy, **role))
@ -345,10 +389,11 @@ class GalaxyCLI(CLI):
# roles were specified directly, so we'll just go out grab them
# (and their dependencies, unless the user doesn't want us to).
for rname in self.args:
roles_left.append(GalaxyRole(self.galaxy, rname.strip()))
role = RoleRequirement.role_yaml_parse(rname.strip())
roles_left.append(GalaxyRole(self.galaxy, **role))
for role in roles_left:
display.debug('Installing role %s ' % role.name)
display.vvv('Installing role %s ' % role.name)
# query the galaxy API for the role data
if role.install_info is not None and not force:
@ -458,21 +503,188 @@ class GalaxyCLI(CLI):
return 0
def execute_search(self):
page_size = 1000
search = None
if len(self.args) > 1:
raise AnsibleOptionsError("At most a single search term is allowed.")
elif len(self.args) == 1:
search = self.args.pop()
response = self.api.search_roles(search, self.options.platforms, self.options.tags)
if len(self.args):
terms = []
for i in range(len(self.args)):
terms.append(self.args.pop())
search = '+'.join(terms[::-1])
if 'count' in response:
display.display("Found %d roles matching your search:\n" % response['count'])
if not search and not self.options.platforms and not self.options.tags and not self.options.author:
raise AnsibleError("Invalid query. At least one search term, platform, galaxy tag or author must be provided.")
data = ''
if 'results' in response:
for role in response['results']:
data += self._display_role_info(role)
response = self.api.search_roles(search, platforms=self.options.platforms,
tags=self.options.tags, author=self.options.author, page_size=page_size)
if response['count'] == 0:
display.display("No roles match your search.", color="yellow")
return True
data = [u'']
if response['count'] > page_size:
data.append(u"Found %d roles matching your search. Showing first %s." % (response['count'], page_size))
else:
data.append(u"Found %d roles matching your search:" % response['count'])
max_len = []
for role in response['results']:
max_len.append(len(role['username'] + '.' + role['name']))
name_len = max(max_len)
format_str = u" %%-%ds %%s" % name_len
data.append(u'')
data.append(format_str % (u"Name", u"Description"))
data.append(format_str % (u"----", u"-----------"))
for role in response['results']:
data.append(format_str % (u'%s.%s' % (role['username'], role['name']), role['description']))
data = u'\n'.join(data)
self.pager(data)
return True
def execute_login(self):
"""
Verify user's identify via Github and retreive an auth token from Galaxy.
"""
# Authenticate with github and retrieve a token
if self.options.token is None:
login = GalaxyLogin(self.galaxy)
github_token = login.create_github_token()
else:
github_token = self.options.token
galaxy_response = self.api.authenticate(github_token)
if self.options.token is None:
# Remove the token we created
login.remove_github_token()
# Store the Galaxy token
token = GalaxyToken()
token.set(galaxy_response['token'])
display.display("Succesfully logged into Galaxy as %s" % galaxy_response['username'])
return 0
def execute_import(self):
"""
Import a role into Galaxy
"""
colors = {
'INFO': 'normal',
'WARNING': 'yellow',
'ERROR': 'red',
'SUCCESS': 'green',
'FAILED': 'red'
}
if len(self.args) < 2:
raise AnsibleError("Expected a github_username and github_repository. Use --help.")
github_repo = self.args.pop()
github_user = self.args.pop()
if self.options.check_status:
task = self.api.get_import_task(github_user=github_user, github_repo=github_repo)
else:
# Submit an import request
task = self.api.create_import_task(github_user, github_repo, reference=self.options.reference)
if len(task) > 1:
# found multiple roles associated with github_user/github_repo
display.display("WARNING: More than one Galaxy role associated with Github repo %s/%s." % (github_user,github_repo),
color='yellow')
display.display("The following Galaxy roles are being updated:" + u'\n', color='yellow')
for t in task:
display.display('%s.%s' % (t['summary_fields']['role']['namespace'],t['summary_fields']['role']['name']), color='yellow')
display.display(u'\n' + "To properly namespace this role, remove each of the above and re-import %s/%s from scratch" % (github_user,github_repo),
color='yellow')
return 0
# found a single role as expected
display.display("Successfully submitted import request %d" % task[0]['id'])
if not self.options.wait:
display.display("Role name: %s" % task[0]['summary_fields']['role']['name'])
display.display("Repo: %s/%s" % (task[0]['github_user'],task[0]['github_repo']))
if self.options.check_status or self.options.wait:
# Get the status of the import
msg_list = []
finished = False
while not finished:
task = self.api.get_import_task(task_id=task[0]['id'])
for msg in task[0]['summary_fields']['task_messages']:
if msg['id'] not in msg_list:
display.display(msg['message_text'], color=colors[msg['message_type']])
msg_list.append(msg['id'])
if task[0]['state'] in ['SUCCESS', 'FAILED']:
finished = True
else:
time.sleep(10)
return 0
def execute_setup(self):
"""
Setup an integration from Github or Travis
"""
if self.options.setup_list:
# List existing integration secrets
secrets = self.api.list_secrets()
if len(secrets) == 0:
# None found
display.display("No integrations found.")
return 0
display.display(u'\n' + "ID Source Repo", color="green")
display.display("---------- ---------- ----------", color="green")
for secret in secrets:
display.display("%-10s %-10s %s/%s" % (secret['id'], secret['source'], secret['github_user'],
secret['github_repo']),color="green")
return 0
if self.options.remove_id:
# Remove a secret
self.api.remove_secret(self.options.remove_id)
display.display("Secret removed. Integrations using this secret will not longer work.", color="green")
return 0
if len(self.args) < 4:
raise AnsibleError("Missing one or more arguments. Expecting: source github_user github_repo secret")
return 0
secret = self.args.pop()
github_repo = self.args.pop()
github_user = self.args.pop()
source = self.args.pop()
resp = self.api.add_secret(source, github_user, github_repo, secret)
display.display("Added integration for %s %s/%s" % (resp['source'], resp['github_user'], resp['github_repo']))
return 0
def execute_delete(self):
"""
Delete a role from galaxy.ansible.com
"""
if len(self.args) < 2:
raise AnsibleError("Missing one or more arguments. Expected: github_user github_repo")
github_repo = self.args.pop()
github_user = self.args.pop()
resp = self.api.delete_role(github_user, github_repo)
if len(resp['deleted_roles']) > 1:
display.display("Deleted the following roles:")
display.display("ID User Name")
display.display("------ --------------- ----------")
for role in resp['deleted_roles']:
display.display("%-8s %-15s %s" % (role.id,role.namespace,role.name))
display.display(resp['status'])
return True

View File

@ -30,6 +30,7 @@ from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.inventory import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play_context import PlayContext
from ansible.utils.vars import load_extra_vars
from ansible.vars import VariableManager
@ -72,7 +73,7 @@ class PlaybookCLI(CLI):
parser.add_option('--start-at-task', dest='start_at_task',
help="start the playbook at the task matching this name")
self.options, self.args = parser.parse_args()
self.options, self.args = parser.parse_args(self.args[1:])
self.parser = parser
@ -152,18 +153,10 @@ class PlaybookCLI(CLI):
for p in results:
display.display('\nplaybook: %s' % p['playbook'])
i = 1
for play in p['plays']:
if play.name:
playname = play.name
else:
playname = '#' + str(i)
msg = "\n PLAY: %s" % (playname)
mytags = set()
if self.options.listtags and play.tags:
mytags = mytags.union(set(play.tags))
msg += ' TAGS: [%s]' % (','.join(mytags))
for idx, play in enumerate(p['plays']):
msg = "\n play #%d (%s): %s" % (idx + 1, ','.join(play.hosts), play.name)
mytags = set(play.tags)
msg += '\tTAGS: [%s]' % (','.join(mytags))
if self.options.listhosts:
playhosts = set(inventory.get_hosts(play.hosts))
@ -173,23 +166,40 @@ class PlaybookCLI(CLI):
display.display(msg)
all_tags = set()
if self.options.listtags or self.options.listtasks:
taskmsg = ' tasks:'
taskmsg = ''
if self.options.listtasks:
taskmsg = ' tasks:\n'
all_vars = variable_manager.get_vars(loader=loader, play=play)
play_context = PlayContext(play=play, options=self.options)
for block in play.compile():
block = block.filter_tagged_tasks(play_context, all_vars)
if not block.has_tasks():
continue
j = 1
for task in block.block:
taskmsg += "\n %s" % task
if self.options.listtags and task.tags:
taskmsg += " TAGS: [%s]" % ','.join(mytags.union(set(task.tags)))
j = j + 1
if task.action == 'meta':
continue
all_tags.update(task.tags)
if self.options.listtasks:
cur_tags = list(mytags.union(set(task.tags)))
cur_tags.sort()
if task.name:
taskmsg += " %s" % task.get_name()
else:
taskmsg += " %s" % task.action
taskmsg += "\tTAGS: [%s]\n" % ', '.join(cur_tags)
if self.options.listtags:
cur_tags = list(mytags.union(all_tags))
cur_tags.sort()
taskmsg += " TASK TAGS: [%s]\n" % ', '.join(cur_tags)
display.display(taskmsg)
i = i + 1
return 0
else:
return results

View File

@ -64,18 +64,24 @@ class PullCLI(CLI):
subset_opts=True,
inventory_opts=True,
module_opts=True,
runas_prompt_opts=True,
)
# options unique to pull
self.parser.add_option('--purge', default=False, action='store_true', help='purge checkout after playbook run')
self.parser.add_option('--purge', default=False, action='store_true',
help='purge checkout after playbook run')
self.parser.add_option('-o', '--only-if-changed', dest='ifchanged', default=False, action='store_true',
help='only run the playbook if the repository has been updated')
self.parser.add_option('-s', '--sleep', dest='sleep', default=None,
help='sleep for random interval (between 0 and n number of seconds) before starting. This is a useful way to disperse git requests')
self.parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
help='run the playbook even if the repository could not be updated')
self.parser.add_option('-d', '--directory', dest='dest', default='~/.ansible/pull', help='directory to checkout repository to')
self.parser.add_option('-U', '--url', dest='url', default=None, help='URL of the playbook repository')
self.parser.add_option('-d', '--directory', dest='dest', default=None,
help='directory to checkout repository to')
self.parser.add_option('-U', '--url', dest='url', default=None,
help='URL of the playbook repository')
self.parser.add_option('--full', dest='fullclone', action='store_true',
help='Do a full clone, instead of a shallow one.')
self.parser.add_option('-C', '--checkout', dest='checkout',
help='branch/tag/commit to checkout. ' 'Defaults to behavior of repository module.')
self.parser.add_option('--accept-host-key', default=False, dest='accept_host_key', action='store_true',
@ -86,7 +92,16 @@ class PullCLI(CLI):
help='verify GPG signature of checked out commit, if it fails abort running the playbook.'
' This needs the corresponding VCS module to support such an operation')
self.options, self.args = self.parser.parse_args()
# for pull we don't wan't a default
self.parser.set_defaults(inventory=None)
self.options, self.args = self.parser.parse_args(self.args[1:])
if not self.options.dest:
hostname = socket.getfqdn()
# use a hostname dependent directory, in case of $HOME on nfs
self.options.dest = os.path.join('~/.ansible/pull', hostname)
self.options.dest = os.path.expandvars(os.path.expanduser(self.options.dest))
if self.options.sleep:
try:
@ -119,18 +134,18 @@ class PullCLI(CLI):
node = platform.node()
host = socket.getfqdn()
limit_opts = 'localhost,%s,127.0.0.1' % ','.join(set([host, node, host.split('.')[0], node.split('.')[0]]))
base_opts = '-c local "%s"' % limit_opts
base_opts = '-c local '
if self.options.verbosity > 0:
base_opts += ' -%s' % ''.join([ "v" for x in range(0, self.options.verbosity) ])
# Attempt to use the inventory passed in as an argument
# It might not yet have been downloaded so use localhost if note
if not self.options.inventory or not os.path.exists(self.options.inventory):
# It might not yet have been downloaded so use localhost as default
if not self.options.inventory or ( ',' not in self.options.inventory and not os.path.exists(self.options.inventory)):
inv_opts = 'localhost,'
else:
inv_opts = self.options.inventory
#TODO: enable more repo modules hg/svn?
#FIXME: enable more repo modules hg/svn?
if self.options.module_name == 'git':
repo_opts = "name=%s dest=%s" % (self.options.url, self.options.dest)
if self.options.checkout:
@ -145,14 +160,16 @@ class PullCLI(CLI):
if self.options.verify:
repo_opts += ' verify_commit=yes'
if not self.options.fullclone:
repo_opts += ' depth=1'
path = module_loader.find_plugin(self.options.module_name)
if path is None:
raise AnsibleOptionsError(("module '%s' not found.\n" % self.options.module_name))
bin_path = os.path.dirname(os.path.abspath(sys.argv[0]))
cmd = '%s/ansible -i "%s" %s -m %s -a "%s"' % (
bin_path, inv_opts, base_opts, self.options.module_name, repo_opts
)
# hardcode local and inventory/host as this is just meant to fetch the repo
cmd = '%s/ansible -i "%s" %s -m %s -a "%s" all -l "%s"' % (bin_path, inv_opts, base_opts, self.options.module_name, repo_opts, limit_opts)
for ev in self.options.extra_vars:
cmd += ' -e "%s"' % ev
@ -163,6 +180,8 @@ class PullCLI(CLI):
time.sleep(self.options.sleep)
# RUN the Checkout command
display.debug("running ansible with VCS module to checkout repo")
display.vvvv('EXEC: %s' % cmd)
rc, out, err = run_cmd(cmd, live=True)
if rc != 0:
@ -174,8 +193,7 @@ class PullCLI(CLI):
display.display("Repository has not changed, quitting.")
return 0
playbook = self.select_playbook(path)
playbook = self.select_playbook(self.options.dest)
if playbook is None:
raise AnsibleOptionsError("Could not find a playbook to run.")
@ -187,16 +205,20 @@ class PullCLI(CLI):
cmd += ' -i "%s"' % self.options.inventory
for ev in self.options.extra_vars:
cmd += ' -e "%s"' % ev
if self.options.ask_sudo_pass:
cmd += ' -K'
if self.options.ask_sudo_pass or self.options.ask_su_pass or self.options.become_ask_pass:
cmd += ' --ask-become-pass'
if self.options.tags:
cmd += ' -t "%s"' % self.options.tags
if self.options.limit:
cmd += ' -l "%s"' % self.options.limit
if self.options.subset:
cmd += ' -l "%s"' % self.options.subset
else:
cmd += ' -l "%s"' % limit_opts
os.chdir(self.options.dest)
# RUN THE PLAYBOOK COMMAND
display.debug("running ansible-playbook to do actual work")
display.debug('EXEC: %s' % cmd)
rc, out, err = run_cmd(cmd, live=True)
if self.options.purge:

View File

@ -26,6 +26,7 @@ from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.parsing.dataloader import DataLoader
from ansible.parsing.vault import VaultEditor
from ansible.cli import CLI
from ansible.utils.unicode import to_unicode
try:
from __main__ import display
@ -69,7 +70,7 @@ class VaultCLI(CLI):
elif self.action == "rekey":
self.parser.set_usage("usage: %prog rekey [options] file_name")
self.options, self.args = self.parser.parse_args()
self.options, self.args = self.parser.parse_args(self.args[1:])
display.verbosity = self.options.verbosity
can_output = ['encrypt', 'decrypt']
@ -157,7 +158,12 @@ class VaultCLI(CLI):
def execute_view(self):
for f in self.args:
self.pager(self.editor.plaintext(f))
# Note: vault should return byte strings because it could encrypt
# and decrypt binary files. We are responsible for changing it to
# unicode here because we are displaying it and therefore can make
# the decision that the display doesn't have to be precisely what
# the input was (leave that to decrypt instead)
self.pager(to_unicode(self.editor.plaintext(f)))
def execute_rekey(self):
for f in self.args:

View File

@ -120,19 +120,23 @@ DEFAULT_COW_WHITELIST = ['bud-frogs', 'bunny', 'cheese', 'daemon', 'default', 'd
# sections in config file
DEFAULTS='defaults'
# FIXME: add deprecation warning when these get set
#### DEPRECATED VARS ####
# use more sanely named 'inventory'
DEPRECATED_HOST_LIST = get_config(p, DEFAULTS, 'hostfile', 'ANSIBLE_HOSTS', '/etc/ansible/hosts', ispath=True)
# this is not used since 0.5 but people might still have in config
DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, None)
# generally configurable things
#### GENERALLY CONFIGURABLE THINGS ####
DEFAULT_DEBUG = get_config(p, DEFAULTS, 'debug', 'ANSIBLE_DEBUG', False, boolean=True)
DEFAULT_HOST_LIST = get_config(p, DEFAULTS,'inventory', 'ANSIBLE_INVENTORY', DEPRECATED_HOST_LIST, ispath=True)
DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None, ispath=True)
DEFAULT_ROLES_PATH = get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles', ispath=True)
DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp')
DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command')
DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, '*')
DEFAULT_FORKS = get_config(p, DEFAULTS, 'forks', 'ANSIBLE_FORKS', 5, integer=True)
DEFAULT_MODULE_ARGS = get_config(p, DEFAULTS, 'module_args', 'ANSIBLE_MODULE_ARGS', '')
DEFAULT_MODULE_LANG = get_config(p, DEFAULTS, 'module_lang', 'ANSIBLE_MODULE_LANG', 'en_US.UTF-8')
DEFAULT_MODULE_LANG = get_config(p, DEFAULTS, 'module_lang', 'ANSIBLE_MODULE_LANG', os.getenv('LANG', 'en_US.UTF-8'))
DEFAULT_TIMEOUT = get_config(p, DEFAULTS, 'timeout', 'ANSIBLE_TIMEOUT', 10, integer=True)
DEFAULT_POLL_INTERVAL = get_config(p, DEFAULTS, 'poll_interval', 'ANSIBLE_POLL_INTERVAL', 15, integer=True)
DEFAULT_REMOTE_USER = get_config(p, DEFAULTS, 'remote_user', 'ANSIBLE_REMOTE_USER', None)
@ -159,7 +163,7 @@ DEFAULT_VAR_COMPRESSION_LEVEL = get_config(p, DEFAULTS, 'var_compression_level',
# disclosure
DEFAULT_NO_LOG = get_config(p, DEFAULTS, 'no_log', 'ANSIBLE_NO_LOG', False, boolean=True)
DEFAULT_NO_TARGET_SYSLOG = get_config(p, DEFAULTS, 'no_target_syslog', 'ANSIBLE_NO_TARGET_SYSLOG', True, boolean=True)
DEFAULT_NO_TARGET_SYSLOG = get_config(p, DEFAULTS, 'no_target_syslog', 'ANSIBLE_NO_TARGET_SYSLOG', False, boolean=True)
# selinux
DEFAULT_SELINUX_SPECIAL_FS = get_config(p, 'selinux', 'special_context_filesystems', None, 'fuse, nfs, vboxsf, ramfs', islist=True)
@ -197,7 +201,7 @@ DEFAULT_BECOME_ASK_PASS = get_config(p, 'privilege_escalation', 'become_ask_pa
# the module takes both, bad things could happen.
# In the future we should probably generalize this even further
# (mapping of param: squash field)
DEFAULT_SQUASH_ACTIONS = get_config(p, DEFAULTS, 'squash_actions', 'ANSIBLE_SQUASH_ACTIONS', "apt, yum, pkgng, zypper, dnf", islist=True)
DEFAULT_SQUASH_ACTIONS = get_config(p, DEFAULTS, 'squash_actions', 'ANSIBLE_SQUASH_ACTIONS', "apt, dnf, package, pkgng, yum, zypper", islist=True)
# paths
DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action:/usr/share/ansible/plugins/action', ispath=True)
DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache', ispath=True)
@ -231,7 +235,7 @@ COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'AN
DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks', 'ANSIBLE_LOAD_CALLBACK_PLUGINS', False, boolean=True)
DEFAULT_CALLBACK_WHITELIST = get_config(p, DEFAULTS, 'callback_whitelist', 'ANSIBLE_CALLBACK_WHITELIST', [], islist=True)
RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True)
RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/', ispath=True)
RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', None, ispath=True)
DEFAULT_NULL_REPRESENTATION = get_config(p, DEFAULTS, 'null_representation', 'ANSIBLE_NULL_REPRESENTATION', None, isnone=True)
# CONNECTION RELATED
@ -255,12 +259,14 @@ ACCELERATE_MULTI_KEY = get_config(p, 'accelerate', 'accelerate_multi_k
PARAMIKO_PTY = get_config(p, 'paramiko_connection', 'pty', 'ANSIBLE_PARAMIKO_PTY', True, boolean=True)
# galaxy related
DEFAULT_GALAXY_URI = get_config(p, 'galaxy', 'server_uri', 'ANSIBLE_GALAXY_SERVER_URI', 'https://galaxy.ansible.com')
GALAXY_SERVER = get_config(p, 'galaxy', 'server', 'ANSIBLE_GALAXY_SERVER', 'https://galaxy.ansible.com')
GALAXY_IGNORE_CERTS = get_config(p, 'galaxy', 'ignore_certs', 'ANSIBLE_GALAXY_IGNORE', False, boolean=True)
# this can be configured to blacklist SCMS but cannot add new ones unless the code is also updated
GALAXY_SCMS = get_config(p, 'galaxy', 'scms', 'ANSIBLE_GALAXY_SCMS', 'git, hg', islist=True)
# characters included in auto-generated passwords
DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_"
STRING_TYPE_FILTERS = get_config(p, 'jinja2', 'dont_type_filters', 'ANSIBLE_STRING_TYPE_FILTERS', ['string', 'to_json', 'to_nice_json', 'to_yaml', 'ppretty', 'json'], islist=True )
# non-configurable things
MODULE_REQUIRE_ARGS = ['command', 'shell', 'raw', 'script']

View File

@ -44,7 +44,7 @@ class AnsibleError(Exception):
which should be returned by the DataLoader() class.
'''
def __init__(self, message, obj=None, show_content=True):
def __init__(self, message="", obj=None, show_content=True):
# we import this here to prevent an import loop problem,
# since the objects code also imports ansible.errors
from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
@ -54,9 +54,9 @@ class AnsibleError(Exception):
if obj and isinstance(obj, AnsibleBaseYAMLObject):
extended_error = self._get_extended_error()
if extended_error:
self.message = 'ERROR! %s\n\n%s' % (message, to_str(extended_error))
self.message = '%s\n\n%s' % (to_str(message), to_str(extended_error))
else:
self.message = 'ERROR! %s' % message
self.message = '%s' % to_str(message)
def __str__(self):
return self.message

View File

@ -97,7 +97,7 @@ def _find_snippet_imports(module_data, module_path, strip_comments):
if " import *" not in line:
import_error = True
if import_error:
raise AnsibleError("error importing module in %s, expecting format like 'from ansible.module_utils.basic import *'" % module_path)
raise AnsibleError("error importing module in %s, expecting format like 'from ansible.module_utils.<lib name> import *'" % module_path)
snippet_name = tokens[2].split()[0]
snippet_names.append(snippet_name)
output.write(_slurp(os.path.join(_SNIPPET_PATH, snippet_name + ".py")))

View File

@ -49,6 +49,7 @@ class HostState:
self.cur_rescue_task = 0
self.cur_always_task = 0
self.cur_role = None
self.cur_dep_chain = None
self.run_state = PlayIterator.ITERATING_SETUP
self.fail_state = PlayIterator.FAILED_NONE
self.pending_setup = False
@ -57,14 +58,32 @@ class HostState:
self.always_child_state = None
def __repr__(self):
return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%d, fail_state=%d, pending_setup=%s, tasks child state? %s, rescue child state? %s, always child state? %s" % (
def _run_state_to_string(n):
states = ["ITERATING_SETUP", "ITERATING_TASKS", "ITERATING_RESCUE", "ITERATING_ALWAYS", "ITERATING_COMPLETE"]
try:
return states[n]
except IndexError:
return "UNKNOWN STATE"
def _failed_state_to_string(n):
states = {1:"FAILED_SETUP", 2:"FAILED_TASKS", 4:"FAILED_RESCUE", 8:"FAILED_ALWAYS"}
if n == 0:
return "FAILED_NONE"
else:
ret = []
for i in (1, 2, 4, 8):
if n & i:
ret.append(states[i])
return "|".join(ret)
return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%s, fail_state=%s, pending_setup=%s, tasks child state? %s, rescue child state? %s, always child state? %s" % (
self.cur_block,
self.cur_regular_task,
self.cur_rescue_task,
self.cur_always_task,
self.cur_role,
self.run_state,
self.fail_state,
_run_state_to_string(self.run_state),
_failed_state_to_string(self.fail_state),
self.pending_setup,
self.tasks_child_state,
self.rescue_child_state,
@ -84,6 +103,8 @@ class HostState:
new_state.run_state = self.run_state
new_state.fail_state = self.fail_state
new_state.pending_setup = self.pending_setup
if self.cur_dep_chain is not None:
new_state.cur_dep_chain = self.cur_dep_chain[:]
if self.tasks_child_state is not None:
new_state.tasks_child_state = self.tasks_child_state.copy()
if self.rescue_child_state is not None:
@ -111,38 +132,54 @@ class PlayIterator:
def __init__(self, inventory, play, play_context, variable_manager, all_vars, start_at_done=False):
self._play = play
self._blocks = []
setup_block = Block(play=self._play)
setup_task = Task(block=setup_block)
setup_task.action = 'setup'
setup_task.tags = ['always']
setup_task.args = {}
setup_task.set_loader(self._play._loader)
setup_block.block = [setup_task]
setup_block = setup_block.filter_tagged_tasks(play_context, all_vars)
self._blocks.append(setup_block)
for block in self._play.compile():
new_block = block.filter_tagged_tasks(play_context, all_vars)
if new_block.has_tasks():
self._blocks.append(new_block)
self._host_states = {}
start_at_matched = False
for host in inventory.get_hosts(self._play.hosts):
self._host_states[host.name] = HostState(blocks=self._blocks)
# if the host's name is in the variable manager's fact cache, then set
# its _gathered_facts flag to true for smart gathering tests later
if host.name in variable_manager._fact_cache:
host._gathered_facts = True
# if we're looking to start at a specific task, iterate through
# the tasks for this host until we find the specified task
if play_context.start_at_task is not None and not start_at_done:
while True:
(s, task) = self.get_next_task_for_host(host, peek=True)
if s.run_state == self.ITERATING_COMPLETE:
break
if task.name == play_context.start_at_task or fnmatch.fnmatch(task.name, play_context.start_at_task) or \
task.get_name() == play_context.start_at_task or fnmatch.fnmatch(task.get_name(), play_context.start_at_task):
# we have our match, so clear the start_at_task field on the
# play context to flag that we've started at a task (and future
# plays won't try to advance)
play_context.start_at_task = None
break
else:
self.get_next_task_for_host(host)
# finally, reset the host's state to ITERATING_SETUP
self._host_states[host.name].run_state = self.ITERATING_SETUP
self._host_states[host.name] = HostState(blocks=self._blocks)
# if the host's name is in the variable manager's fact cache, then set
# its _gathered_facts flag to true for smart gathering tests later
if host.name in variable_manager._fact_cache:
host._gathered_facts = True
# if we're looking to start at a specific task, iterate through
# the tasks for this host until we find the specified task
if play_context.start_at_task is not None and not start_at_done:
while True:
(s, task) = self.get_next_task_for_host(host, peek=True)
if s.run_state == self.ITERATING_COMPLETE:
break
if task.name == play_context.start_at_task or fnmatch.fnmatch(task.name, play_context.start_at_task) or \
task.get_name() == play_context.start_at_task or fnmatch.fnmatch(task.get_name(), play_context.start_at_task):
start_at_matched = True
break
else:
self.get_next_task_for_host(host)
# finally, reset the host's state to ITERATING_SETUP
self._host_states[host.name].run_state = self.ITERATING_SETUP
if start_at_matched:
# we have our match, so clear the start_at_task field on the
# play context to flag that we've started at a task (and future
# plays won't try to advance)
play_context.start_at_task = None
# Extend the play handlers list to include the handlers defined in roles
self._play.handlers.extend(play.compile_roles_handlers())
@ -161,41 +198,23 @@ class PlayIterator:
task = None
if s.run_state == self.ITERATING_COMPLETE:
display.debug("host %s is done iterating, returning" % host.name)
return (None, None)
elif s.run_state == self.ITERATING_SETUP:
s.run_state = self.ITERATING_TASKS
s.pending_setup = True
return (s, None)
# Gather facts if the default is 'smart' and we have not yet
# done it for this host; or if 'explicit' and the play sets
# gather_facts to True; or if 'implicit' and the play does
# NOT explicitly set gather_facts to False.
old_s = s
(s, task) = self._get_next_task_from_state(s, host=host, peek=peek)
gathering = C.DEFAULT_GATHERING
implied = self._play.gather_facts is None or boolean(self._play.gather_facts)
if (gathering == 'implicit' and implied) or \
(gathering == 'explicit' and boolean(self._play.gather_facts)) or \
(gathering == 'smart' and implied and not host._gathered_facts):
if not peek:
# mark the host as having gathered facts
host.set_gathered_facts(True)
task = Task()
task.action = 'setup'
task.args = {}
task.set_loader(self._play._loader)
def _roles_are_different(ra, rb):
if ra != rb:
return True
else:
s.pending_setup = False
if not task:
(s, task) = self._get_next_task_from_state(s, peek=peek)
return old_s.cur_dep_chain != task._block.get_dep_chain()
if task and task._role:
# if we had a current role, mark that role as completed
if s.cur_role and task._role != s.cur_role and host.name in s.cur_role._had_task_run and not peek:
if s.cur_role and _roles_are_different(task._role, s.cur_role) and host.name in s.cur_role._had_task_run and not peek:
s.cur_role._completed[host.name] = True
s.cur_role = task._role
s.cur_dep_chain = task._block.get_dep_chain()
if not peek:
self._host_states[host.name] = s
@ -206,7 +225,7 @@ class PlayIterator:
return (s, task)
def _get_next_task_from_state(self, state, peek):
def _get_next_task_from_state(self, state, host, peek):
task = None
@ -221,7 +240,38 @@ class PlayIterator:
state.run_state = self.ITERATING_COMPLETE
return (state, None)
if state.run_state == self.ITERATING_TASKS:
if state.run_state == self.ITERATING_SETUP:
if not state.pending_setup:
state.pending_setup = True
# Gather facts if the default is 'smart' and we have not yet
# done it for this host; or if 'explicit' and the play sets
# gather_facts to True; or if 'implicit' and the play does
# NOT explicitly set gather_facts to False.
gathering = C.DEFAULT_GATHERING
implied = self._play.gather_facts is None or boolean(self._play.gather_facts)
if (gathering == 'implicit' and implied) or \
(gathering == 'explicit' and boolean(self._play.gather_facts)) or \
(gathering == 'smart' and implied and not host._gathered_facts):
# mark the host as having gathered facts
setup_block = self._blocks[0]
if setup_block.has_tasks() and len(setup_block.block) > 0:
task = setup_block.block[0]
if not peek:
host.set_gathered_facts(True)
else:
state.pending_setup = False
state.cur_block += 1
state.cur_regular_task = 0
state.cur_rescue_task = 0
state.cur_always_task = 0
state.run_state = self.ITERATING_TASKS
state.child_state = None
elif state.run_state == self.ITERATING_TASKS:
# clear the pending setup flag, since we're past that and it didn't fail
if state.pending_setup:
state.pending_setup = False
@ -238,7 +288,7 @@ class PlayIterator:
state.tasks_child_state = HostState(blocks=[task])
state.tasks_child_state.run_state = self.ITERATING_TASKS
state.tasks_child_state.cur_role = state.cur_role
(state.tasks_child_state, task) = self._get_next_task_from_state(state.tasks_child_state, peek=peek)
(state.tasks_child_state, task) = self._get_next_task_from_state(state.tasks_child_state, host=host, peek=peek)
if task is None:
# check to see if the child state was failed, if so we need to
# fail here too so we don't continue iterating tasks
@ -264,7 +314,7 @@ class PlayIterator:
state.rescue_child_state = HostState(blocks=[task])
state.rescue_child_state.run_state = self.ITERATING_TASKS
state.rescue_child_state.cur_role = state.cur_role
(state.rescue_child_state, task) = self._get_next_task_from_state(state.rescue_child_state, peek=peek)
(state.rescue_child_state, task) = self._get_next_task_from_state(state.rescue_child_state, host=host, peek=peek)
if task is None:
# check to see if the child state was failed, if so we need to
# fail here too so we don't continue iterating rescue
@ -294,7 +344,7 @@ class PlayIterator:
state.always_child_state = HostState(blocks=[task])
state.always_child_state.run_state = self.ITERATING_TASKS
state.always_child_state.cur_role = state.cur_role
(state.always_child_state, task) = self._get_next_task_from_state(state.always_child_state, peek=peek)
(state.always_child_state, task) = self._get_next_task_from_state(state.always_child_state, host=host, peek=peek)
if task is None:
# check to see if the child state was failed, if so we need to
# fail here too so we don't continue iterating always
@ -324,13 +374,21 @@ class PlayIterator:
state.tasks_child_state = self._set_failed_state(state.tasks_child_state)
else:
state.fail_state |= self.FAILED_TASKS
state.run_state = self.ITERATING_RESCUE
if state._blocks[state.cur_block].rescue:
state.run_state = self.ITERATING_RESCUE
elif state._blocks[state.cur_block].always:
state.run_state = self.ITERATING_ALWAYS
else:
state.run_state = self.ITERATING_COMPLETE
elif state.run_state == self.ITERATING_RESCUE:
if state.rescue_child_state is not None:
state.rescue_child_state = self._set_failed_state(state.rescue_child_state)
else:
state.fail_state |= self.FAILED_RESCUE
state.run_state = self.ITERATING_ALWAYS
if state._blocks[state.cur_block].always:
state.run_state = self.ITERATING_ALWAYS
else:
state.run_state = self.ITERATING_COMPLETE
elif state.run_state == self.ITERATING_ALWAYS:
if state.always_child_state is not None:
state.always_child_state = self._set_failed_state(state.always_child_state)
@ -347,6 +405,32 @@ class PlayIterator:
def get_failed_hosts(self):
return dict((host, True) for (host, state) in iteritems(self._host_states) if state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE)
def _check_failed_state(self, state):
if state is None:
return False
elif state.run_state == self.ITERATING_TASKS and self._check_failed_state(state.tasks_child_state):
cur_block = self._blocks[state.cur_block]
if len(cur_block.rescue) > 0 and state.fail_state & self.FAILED_RESCUE == 0:
return False
else:
return True
elif state.run_state == self.ITERATING_RESCUE and self._check_failed_state(state.rescue_child_state):
return True
elif state.run_state == self.ITERATING_ALWAYS and self._check_failed_state(state.always_child_state):
return True
elif state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE:
if state.run_state == self.ITERATING_RESCUE and state.fail_state&self.FAILED_RESCUE == 0:
return False
elif state.run_state == self.ITERATING_ALWAYS and state.fail_state&self.FAILED_ALWAYS == 0:
return False
else:
return True
return False
def is_failed(self, host):
s = self.get_host_state(host)
return self._check_failed_state(s)
def get_original_task(self, host, task):
'''
Finds the task in the task list which matches the UUID of the given task.
@ -396,7 +480,8 @@ class PlayIterator:
return None
def _insert_tasks_into_state(self, state, task_list):
if state.fail_state != self.FAILED_NONE:
# if we've failed at all, or if the task list is empty, just return the current state
if state.fail_state != self.FAILED_NONE and state.run_state not in (self.ITERATING_RESCUE, self.ITERATING_ALWAYS) or not task_list:
return state
if state.run_state == self.ITERATING_TASKS:

View File

@ -19,22 +19,15 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import getpass
import locale
import os
import signal
import sys
from ansible.compat.six import string_types
from ansible import constants as C
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.playbook import Playbook
from ansible.template import Templar
from ansible.utils.color import colorize, hostcolor
from ansible.utils.encrypt import do_encrypt
from ansible.utils.unicode import to_unicode
try:
from __main__ import display
except ImportError:
@ -70,8 +63,6 @@ class PlaybookExecutor:
may limit the runs to serialized groups, etc.
'''
signal.signal(signal.SIGINT, self._cleanup)
result = 0
entrylist = []
entry = {}
@ -83,6 +74,10 @@ class PlaybookExecutor:
if self._tqm is None: # we are doing a listing
entry = {'playbook': playbook_path}
entry['plays'] = []
else:
# make sure the tqm has callbacks loaded
self._tqm.load_callbacks()
self._tqm.send_callback('v2_playbook_on_start', pb)
i = 1
plays = pb.get_plays()
@ -108,10 +103,12 @@ class PlaybookExecutor:
salt_size = var.get("salt_size", None)
salt = var.get("salt", None)
if vname not in play.vars:
if vname not in self._variable_manager.extra_vars:
if self._tqm:
self._tqm.send_callback('v2_playbook_on_vars_prompt', vname, private, prompt, encrypt, confirm, salt_size, salt, default)
play.vars[vname] = self._do_var_prompt(vname, private, prompt, encrypt, confirm, salt_size, salt, default)
play.vars[vname] = display.do_var_prompt(vname, private, prompt, encrypt, confirm, salt_size, salt, default)
else: # we are either in --list-<option> or syntax check
play.vars[vname] = default
# Create a temporary copy of the play here, so we can run post_validate
# on it without the templating changes affecting the original object.
@ -128,8 +125,6 @@ class PlaybookExecutor:
entry['plays'].append(new_play)
else:
# make sure the tqm has callbacks loaded
self._tqm.load_callbacks()
self._tqm._unreachable_hosts.update(self._unreachable_hosts)
# we are actually running plays
@ -149,9 +144,7 @@ class PlaybookExecutor:
# conditions are met, we break out, otherwise we only break out if the entire
# batch failed
failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts)
if new_play.any_errors_fatal and failed_hosts_count > 0:
break
elif new_play.max_fail_percentage is not None and \
if new_play.max_fail_percentage is not None and \
int((new_play.max_fail_percentage)/100.0 * len(batch)) > int((len(batch) - failed_hosts_count) / len(batch) * 100.0):
break
elif len(batch) == failed_hosts_count:
@ -171,6 +164,24 @@ class PlaybookExecutor:
if entry:
entrylist.append(entry) # per playbook
# send the stats callback for this playbook
if self._tqm is not None:
if C.RETRY_FILES_ENABLED:
retries = list(set(self._tqm._failed_hosts.keys() + self._tqm._unreachable_hosts.keys()))
retries.sort()
if len(retries) > 0:
if C.RETRY_FILES_SAVE_PATH:
basedir = C.shell_expand(C.RETRY_FILES_SAVE_PATH)
else:
basedir = os.path.dirname(playbook_path)
(retry_name, _) = os.path.splitext(os.path.basename(playbook_path))
filename = os.path.join(basedir, "%s.retry" % retry_name)
if self._generate_retry_inventory(filename, retries):
display.display("\tto retry, use: --limit @%s\n" % filename)
self._tqm.send_callback('v2_playbook_on_stats', self._tqm._stats)
# if the last result wasn't zero, break out of the playbook file name loop
if result != 0:
break
@ -180,46 +191,14 @@ class PlaybookExecutor:
finally:
if self._tqm is not None:
self._cleanup()
self._tqm.cleanup()
if self._options.syntax:
display.display("No issues encountered")
return result
# TODO: this stat summary stuff should be cleaned up and moved
# to a new method, if it even belongs here...
display.banner("PLAY RECAP")
hosts = sorted(self._tqm._stats.processed.keys())
for h in hosts:
t = self._tqm._stats.summarize(h)
display.display(u"%s : %s %s %s %s" % (
hostcolor(h, t),
colorize(u'ok', t['ok'], 'green'),
colorize(u'changed', t['changed'], 'yellow'),
colorize(u'unreachable', t['unreachable'], 'red'),
colorize(u'failed', t['failures'], 'red')),
screen_only=True
)
display.display(u"%s : %s %s %s %s" % (
hostcolor(h, t, False),
colorize(u'ok', t['ok'], None),
colorize(u'changed', t['changed'], None),
colorize(u'unreachable', t['unreachable'], None),
colorize(u'failed', t['failures'], None)),
log_only=True
)
display.display("", screen_only=True)
# END STATS STUFF
return result
def _cleanup(self, signum=None, framenum=None):
return self._tqm.cleanup()
def _get_serialized_batches(self, play):
'''
Returns a list of hosts, subdivided into batches based on
@ -258,48 +237,19 @@ class PlaybookExecutor:
return serialized_batches
def _do_var_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None):
def _generate_retry_inventory(self, retry_path, replay_hosts):
'''
Called when a playbook run fails. It generates an inventory which allows
re-running on ONLY the failed hosts. This may duplicate some variable
information in group_vars/host_vars but that is ok, and expected.
'''
if sys.__stdin__.isatty():
if prompt and default is not None:
msg = "%s [%s]: " % (prompt, default)
elif prompt:
msg = "%s: " % prompt
else:
msg = 'input for %s: ' % varname
try:
with open(retry_path, 'w') as fd:
for x in replay_hosts:
fd.write("%s\n" % x)
except Exception as e:
display.error("Could not create retry file '%s'. The error was: %s" % (retry_path, e))
return False
def do_prompt(prompt, private):
if sys.stdout.encoding:
msg = prompt.encode(sys.stdout.encoding)
else:
# when piping the output, or at other times when stdout
# may not be the standard file descriptor, the stdout
# encoding may not be set, so default to something sane
msg = prompt.encode(locale.getpreferredencoding())
if private:
return getpass.getpass(msg)
return raw_input(msg)
if confirm:
while True:
result = do_prompt(msg, private)
second = do_prompt("confirm " + msg, private)
if result == second:
break
display.display("***** VALUES ENTERED DO NOT MATCH ****")
else:
result = do_prompt(msg, private)
else:
result = None
display.warning("Not prompting as we are not in interactive mode")
# if result is false and default is not None
if not result and default is not None:
result = default
if encrypt:
result = do_encrypt(result, encrypt, salt_size, salt)
# handle utf-8 chars
result = to_unicode(result, errors='strict')
return result
return True

View File

@ -110,7 +110,7 @@ class ResultProcess(multiprocessing.Process):
# if this task is registering a result, do it now
if result._task.register:
self._send_result(('register_host_var', result._host, result._task.register, clean_copy))
self._send_result(('register_host_var', result._host, result._task, clean_copy))
# send callbacks, execute other options based on the result status
# TODO: this should all be cleaned up and probably moved to a sub-function.
@ -163,7 +163,7 @@ class ResultProcess(multiprocessing.Process):
except queue.Empty:
pass
except (KeyboardInterrupt, IOError, EOFError):
except (KeyboardInterrupt, SystemExit, IOError, EOFError):
break
except:
# TODO: we should probably send a proper callback here instead of

View File

@ -48,6 +48,7 @@ from ansible.playbook.task import Task
from ansible.vars.unsafe_proxy import AnsibleJSONUnsafeDecoder
from ansible.utils.debug import debug
from ansible.utils.unicode import to_unicode
__all__ = ['WorkerProcess']
@ -59,14 +60,18 @@ class WorkerProcess(multiprocessing.Process):
for reading later.
'''
def __init__(self, tqm, main_q, rslt_q, hostvars_manager, loader):
def __init__(self, rslt_q, task_vars, host, task, play_context, loader, variable_manager, shared_loader_obj):
super(WorkerProcess, self).__init__()
# takes a task queue manager as the sole param:
self._main_q = main_q
self._rslt_q = rslt_q
self._hostvars = hostvars_manager
self._loader = loader
self._rslt_q = rslt_q
self._task_vars = task_vars
self._host = host
self._task = task
self._play_context = play_context
self._loader = loader
self._variable_manager = variable_manager
self._shared_loader_obj = shared_loader_obj
# dupe stdin, if we have one
self._new_stdin = sys.stdin
@ -97,73 +102,45 @@ class WorkerProcess(multiprocessing.Process):
if HAS_ATFORK:
atfork()
while True:
task = None
try:
#debug("waiting for work")
(host, task, basedir, zip_vars, compressed_vars, play_context, shared_loader_obj) = self._main_q.get(block=False)
try:
# execute the task and build a TaskResult from the result
debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
executor_result = TaskExecutor(
self._host,
self._task,
self._task_vars,
self._play_context,
self._new_stdin,
self._loader,
self._shared_loader_obj,
).run()
if compressed_vars:
job_vars = json.loads(zlib.decompress(zip_vars))
else:
job_vars = zip_vars
debug("done running TaskExecutor() for %s/%s" % (self._host, self._task))
self._host.vars = dict()
self._host.groups = []
task_result = TaskResult(self._host, self._task, executor_result)
job_vars['hostvars'] = self._hostvars.hostvars()
# put the result on the result queue
debug("sending task result")
self._rslt_q.put(task_result)
debug("done sending task result")
debug("there's work to be done! got a task/handler to work on: %s" % task)
except AnsibleConnectionFailure:
self._host.vars = dict()
self._host.groups = []
task_result = TaskResult(self._host, self._task, dict(unreachable=True))
self._rslt_q.put(task_result, block=False)
# because the task queue manager starts workers (forks) before the
# playbook is loaded, set the basedir of the loader inherted by
# this fork now so that we can find files correctly
self._loader.set_basedir(basedir)
# Serializing/deserializing tasks does not preserve the loader attribute,
# since it is passed to the worker during the forking of the process and
# would be wasteful to serialize. So we set it here on the task now, and
# the task handles updating parent/child objects as needed.
task.set_loader(self._loader)
# execute the task and build a TaskResult from the result
debug("running TaskExecutor() for %s/%s" % (host, task))
executor_result = TaskExecutor(
host,
task,
job_vars,
play_context,
self._new_stdin,
self._loader,
shared_loader_obj,
).run()
debug("done running TaskExecutor() for %s/%s" % (host, task))
task_result = TaskResult(host, task, executor_result)
# put the result on the result queue
debug("sending task result")
self._rslt_q.put(task_result)
debug("done sending task result")
except queue.Empty:
time.sleep(0.0001)
except AnsibleConnectionFailure:
except Exception as e:
if not isinstance(e, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(e, TemplateNotFound):
try:
if task:
task_result = TaskResult(host, task, dict(unreachable=True))
self._rslt_q.put(task_result, block=False)
self._host.vars = dict()
self._host.groups = []
task_result = TaskResult(self._host, self._task, dict(failed=True, exception=to_unicode(traceback.format_exc()), stdout=''))
self._rslt_q.put(task_result, block=False)
except:
break
except Exception as e:
if isinstance(e, (IOError, EOFError, KeyboardInterrupt)) and not isinstance(e, TemplateNotFound):
break
else:
try:
if task:
task_result = TaskResult(host, task, dict(failed=True, exception=traceback.format_exc(), stdout=''))
self._rslt_q.put(task_result, block=False)
except:
debug("WORKER EXCEPTION: %s" % e)
debug("WORKER EXCEPTION: %s" % traceback.format_exc())
break
debug(u"WORKER EXCEPTION: %s" % to_unicode(e))
debug(u"WORKER TRACEBACK: %s" % to_unicode(traceback.format_exc()))
debug("WORKER PROCESS EXITING")

View File

@ -24,6 +24,7 @@ import json
import subprocess
import sys
import time
import traceback
from ansible.compat.six import iteritems, string_types
@ -35,7 +36,7 @@ from ansible.template import Templar
from ansible.utils.encrypt import key_for_hostname
from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.unicode import to_unicode
from ansible.vars.unsafe_proxy import UnsafeProxy
from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var
try:
from __main__ import display
@ -67,6 +68,7 @@ class TaskExecutor:
self._new_stdin = new_stdin
self._loader = loader
self._shared_loader_obj = shared_loader_obj
self._connection = None
def run(self):
'''
@ -139,13 +141,15 @@ class TaskExecutor:
return res
except AnsibleError as e:
return dict(failed=True, msg=to_unicode(e, nonstring='simplerepr'))
except Exception as e:
return dict(failed=True, msg='Unexpected failure during module execution.', exception=to_unicode(traceback.format_exc()), stdout='')
finally:
try:
self._connection.close()
except AttributeError:
pass
except Exception as e:
display.debug("error closing connection: %s" % to_unicode(e))
display.debug(u"error closing connection: %s" % to_unicode(e))
def _get_loop_items(self):
'''
@ -174,21 +178,15 @@ class TaskExecutor:
# first_found loops are special. If the item is undefined
# then we want to fall through to the next value rather
# than failing.
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop_args, templar=templar,
loader=self._loader, fail_on_undefined=False, convert_bare=True)
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop_args, templar=templar, loader=self._loader, fail_on_undefined=False, convert_bare=True)
loop_terms = [t for t in loop_terms if not templar._contains_vars(t)]
else:
try:
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop_args, templar=templar,
loader=self._loader, fail_on_undefined=True, convert_bare=True)
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop_args, templar=templar, loader=self._loader, fail_on_undefined=True, convert_bare=True)
except AnsibleUndefinedVariable as e:
if 'has no attribute' in str(e):
loop_terms = []
display.deprecated("Skipping task due to undefined attribute, in the future this will be a fatal error.")
else:
raise
items = self._shared_loader_obj.lookup_loader.get(self._task.loop, loader=self._loader,
templar=templar).run(terms=loop_terms, variables=self._job_vars)
loop_terms = []
display.deprecated("Skipping task due to undefined Error, in the future this will be a fatal error.")
items = self._shared_loader_obj.lookup_loader.get(self._task.loop, loader=self._loader, templar=templar).run(terms=loop_terms, variables=self._job_vars)
else:
raise AnsibleError("Unexpected failure in finding the lookup named '%s' in the available lookup plugins" % self._task.loop)
@ -230,7 +228,7 @@ class TaskExecutor:
tmp_task = self._task.copy()
tmp_play_context = self._play_context.copy()
except AnsibleParserError as e:
results.append(dict(failed=True, msg=str(e)))
results.append(dict(failed=True, msg=to_unicode(e)))
continue
# now we swap the internal task and play context with their copies,
@ -244,6 +242,7 @@ class TaskExecutor:
# now update the result with the item info, and append the result
# to the list of results
res['item'] = item
#TODO: send item results to callback here, instead of all at the end
results.append(res)
return results
@ -267,28 +266,39 @@ class TaskExecutor:
if all(isinstance(o, string_types) for o in items):
final_items = []
name = self._task.args.pop('name', None) or self._task.args.pop('pkg', None)
# The user is doing an upgrade or some other operation
# that doesn't take name or pkg.
if name:
# This gets the information to check whether the name field
# contains a template that we can squash for
template_no_item = template_with_item = None
if templar._contains_vars(name):
variables['item'] = '\0$'
template_no_item = templar.template(name, variables, cache=False)
variables['item'] = '\0@'
template_with_item = templar.template(name, variables, cache=False)
del variables['item']
# Check if the user is doing some operation that doesn't take
# name/pkg or the name/pkg field doesn't have any variables
# and thus the items can't be squashed
if name and (template_no_item != template_with_item):
for item in items:
variables['item'] = item
if self._task.evaluate_conditional(templar, variables):
if templar._contains_vars(name):
new_item = templar.template(name, cache=False)
final_items.append(new_item)
else:
final_items.append(item)
new_item = templar.template(name, cache=False)
final_items.append(new_item)
self._task.args['name'] = final_items
# Wrap this in a list so that the calling function loop
# executes exactly once
return [final_items]
else:
# Restore the name parameter
self._task.args['name'] = name
#elif:
# Right now we only optimize single entries. In the future we
# could optimize more types:
# * lists can be squashed together
# * dicts could squash entries that match in all cases except the
# name or pkg field.
# Note: we really should be checking that the name or pkg field
# contains a template that expands with our with_items values.
# If it doesn't then we may break things
return items
def _execute(self, variables=None):
@ -314,6 +324,11 @@ class TaskExecutor:
# do the same kind of post validation step on it here before we use it.
self._play_context.post_validate(templar=templar)
# now that the play context is finalized, if the remote_addr is not set
# default to using the host's address field as the remote address
if not self._play_context.remote_addr:
self._play_context.remote_addr = self._host.address
# We also add "magic" variables back into the variables dict to make sure
# a certain subset of variables exist.
self._play_context.update_vars(variables)
@ -360,8 +375,13 @@ class TaskExecutor:
self._task.args = variable_params
# get the connection and the handler for this execution
self._connection = self._get_connection(variables=variables, templar=templar)
self._connection.set_host_overrides(host=self._host)
if not self._connection or not getattr(self._connection, 'connected', False) or self._play_context.remote_addr != self._connection._play_context.remote_addr:
self._connection = self._get_connection(variables=variables, templar=templar)
self._connection.set_host_overrides(host=self._host)
else:
# if connection is reused, its _play_context is no longer valid and needs
# to be replaced with the one templated above, in case other data changed
self._connection._play_context = self._play_context
self._handler = self._get_action_handler(connection=self._connection, templar=templar)
@ -384,7 +404,6 @@ class TaskExecutor:
# make a copy of the job vars here, in case we need to update them
# with the registered variable value later on when testing conditions
#vars_copy = variables.copy()
vars_copy = variables.copy()
display.debug("starting attempt loop")
@ -398,9 +417,14 @@ class TaskExecutor:
try:
result = self._handler.run(task_vars=variables)
except AnsibleConnectionFailure as e:
return dict(unreachable=True, msg=str(e))
return dict(unreachable=True, msg=to_unicode(e))
display.debug("handler run complete")
# update the local copy of vars with the registered value, if specified,
# or any facts which may have been generated by the module execution
if self._task.register:
vars_copy[self._task.register] = wrap_var(result.copy())
if self._task.async > 0:
# the async_wrapper module returns dumped JSON via its stdout
# response, so we parse it here and replace the result
@ -409,7 +433,7 @@ class TaskExecutor:
return result
result = json.loads(result.get('stdout'))
except (TypeError, ValueError) as e:
return dict(failed=True, msg="The async task did not return valid JSON: %s" % str(e))
return dict(failed=True, msg=u"The async task did not return valid JSON: %s" % to_unicode(e))
if self._task.poll > 0:
result = self._poll_async_result(result=result, templar=templar)
@ -430,11 +454,6 @@ class TaskExecutor:
return failed_when_result
return False
# update the local copy of vars with the registered value, if specified,
# or any facts which may have been generated by the module execution
if self._task.register:
vars_copy[self._task.register] = result
if 'ansible_facts' in result:
vars_copy.update(result['ansible_facts'])
@ -451,7 +470,7 @@ class TaskExecutor:
if attempt < retries - 1:
cond = Conditional(loader=self._loader)
cond.when = self._task.until
cond.when = [ self._task.until ]
if cond.evaluate_conditional(templar, vars_copy):
break
@ -464,7 +483,7 @@ class TaskExecutor:
# do the final update of the local variables here, for both registered
# values and any facts which may have been created
if self._task.register:
variables[self._task.register] = result
variables[self._task.register] = wrap_var(result)
if 'ansible_facts' in result:
variables.update(result['ansible_facts'])
@ -542,9 +561,6 @@ class TaskExecutor:
correct connection object from the list of connection plugins
'''
if not self._play_context.remote_addr:
self._play_context.remote_addr = self._host.address
if self._task.delegate_to is not None:
# since we're delegating, we don't want to use interpreter values
# which would have been set for the original target host

View File

@ -19,7 +19,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from multiprocessing.managers import SyncManager, DictProxy
import multiprocessing
import os
import tempfile
@ -27,13 +26,14 @@ import tempfile
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.executor.play_iterator import PlayIterator
from ansible.executor.process.worker import WorkerProcess
from ansible.executor.process.result import ResultProcess
from ansible.executor.stats import AggregateStats
from ansible.playbook.play_context import PlayContext
from ansible.plugins import callback_loader, strategy_loader, module_loader
from ansible.template import Templar
from ansible.vars.hostvars import HostVars
from ansible.plugins.callback import CallbackBase
from ansible.utils.unicode import to_unicode
try:
from __main__ import display
@ -56,7 +56,7 @@ class TaskQueueManager:
which dispatches the Play's tasks to hosts.
'''
def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None):
def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None, run_additional_callbacks=True, run_tree=False):
self._inventory = inventory
self._variable_manager = variable_manager
@ -65,6 +65,8 @@ class TaskQueueManager:
self._stats = AggregateStats()
self.passwords = passwords
self._stdout_callback = stdout_callback
self._run_additional_callbacks = run_additional_callbacks
self._run_tree = run_tree
self._callbacks_loaded = False
self._callback_plugins = []
@ -96,14 +98,10 @@ class TaskQueueManager:
def _initialize_processes(self, num):
self._workers = []
for i in xrange(num):
for i in range(num):
main_q = multiprocessing.Queue()
rslt_q = multiprocessing.Queue()
prc = WorkerProcess(self, main_q, rslt_q, self._hostvars_manager, self._loader)
prc.start()
self._workers.append((prc, main_q, rslt_q))
self._workers.append([None, main_q, rslt_q])
self._result_prc = ResultProcess(self._final_q, self._workers)
self._result_prc.start()
@ -144,8 +142,16 @@ class TaskQueueManager:
if self._stdout_callback is None:
self._stdout_callback = C.DEFAULT_STDOUT_CALLBACK
if self._stdout_callback not in callback_loader:
raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback)
if isinstance(self._stdout_callback, CallbackBase):
stdout_callback_loaded = True
elif isinstance(self._stdout_callback, basestring):
if self._stdout_callback not in callback_loader:
raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback)
else:
self._stdout_callback = callback_loader.get(self._stdout_callback)
stdout_callback_loaded = True
else:
raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin")
for callback_plugin in callback_loader.all(class_only=True):
if hasattr(callback_plugin, 'CALLBACK_VERSION') and callback_plugin.CALLBACK_VERSION >= 2.0:
@ -159,7 +165,9 @@ class TaskQueueManager:
if callback_name != self._stdout_callback or stdout_callback_loaded:
continue
stdout_callback_loaded = True
elif callback_needs_whitelist and (C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST):
elif callback_name == 'tree' and self._run_tree:
pass
elif not self._run_additional_callbacks or (callback_needs_whitelist and (C.DEFAULT_CALLBACK_WHITELIST is None or callback_name not in C.DEFAULT_CALLBACK_WHITELIST)):
continue
self._callback_plugins.append(callback_plugin())
@ -184,31 +192,12 @@ class TaskQueueManager:
new_play = play.copy()
new_play.post_validate(templar)
class HostVarsManager(SyncManager):
pass
hostvars = HostVars(
play=new_play,
self.hostvars = HostVars(
inventory=self._inventory,
variable_manager=self._variable_manager,
loader=self._loader,
)
HostVarsManager.register(
'hostvars',
callable=lambda: hostvars,
# FIXME: this is the list of exposed methods to the DictProxy object, plus our
# special ones (set_variable_manager/set_inventory). There's probably a better way
# to do this with a proper BaseProxy/DictProxy derivative
exposed=(
'set_variable_manager', 'set_inventory', '__contains__', '__delitem__',
'__getitem__', '__len__', '__setitem__', 'clear', 'copy', 'get', 'has_key',
'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'
),
)
self._hostvars_manager = HostVarsManager()
self._hostvars_manager.start()
# Fork # of forks, # of hosts or serial, whichever is lowest
contenders = [self._options.forks, play.serial, len(self._inventory.get_hosts(new_play.hosts))]
contenders = [ v for v in contenders if v is not None and v > 0 ]
@ -248,7 +237,6 @@ class TaskQueueManager:
# and run the play using the strategy and cleanup on way out
play_return = strategy.run(iterator, play_context)
self._cleanup_processes()
self._hostvars_manager.shutdown()
return play_return
def cleanup(self):
@ -264,7 +252,11 @@ class TaskQueueManager:
for (worker_prc, main_q, rslt_q) in self._workers:
rslt_q.close()
main_q.close()
worker_prc.terminate()
if worker_prc and worker_prc.is_alive():
try:
worker_prc.terminate()
except AttributeError:
pass
def clear_failed_hosts(self):
self._failed_hosts = dict()
@ -288,22 +280,36 @@ class TaskQueueManager:
self._terminated = True
def send_callback(self, method_name, *args, **kwargs):
for callback_plugin in self._callback_plugins:
for callback_plugin in [self._stdout_callback] + self._callback_plugins:
# a plugin that set self.disabled to True will not be called
# see osx_say.py example for such a plugin
if getattr(callback_plugin, 'disabled', False):
continue
methods = [
getattr(callback_plugin, method_name, None),
getattr(callback_plugin, 'v2_on_any', None)
]
# try to find v2 method, fallback to v1 method, ignore callback if no method found
methods = []
for possible in [method_name, 'v2_on_any']:
gotit = getattr(callback_plugin, possible, None)
if gotit is None:
gotit = getattr(callback_plugin, possible.replace('v2_',''), None)
if gotit is not None:
methods.append(gotit)
for method in methods:
if method is not None:
try:
try:
# temporary hack, required due to a change in the callback API, so
# we don't break backwards compatibility with callbacks which were
# designed to use the original API
# FIXME: target for removal and revert to the original code here after a year (2017-01-14)
if method_name == 'v2_playbook_on_start':
import inspect
(f_args, f_varargs, f_keywords, f_defaults) = inspect.getargspec(method)
if 'playbook' in f_args:
method(*args, **kwargs)
else:
method()
else:
method(*args, **kwargs)
except Exception as e:
try:
v1_method = method.replace('v2_','')
v1_method(*args, **kwargs)
except Exception:
display.warning('Error when using %s: %s' % (method, str(e)))
except Exception as e:
#TODO: add config toggle to make this fatal or not?
display.warning(u"Failure when attempting to use callback plugin (%s): %s" % (to_unicode(callback_plugin), to_unicode(e)))

View File

@ -49,9 +49,34 @@ class Galaxy(object):
this_dir, this_filename = os.path.split(__file__)
self.DATA_PATH = os.path.join(this_dir, "data")
#TODO: move to getter for lazy loading
self.default_readme = self._str_from_data_file('readme')
self.default_meta = self._str_from_data_file('metadata_template.j2')
self._default_readme = None
self._default_meta = None
self._default_test = None
self._default_travis = None
@property
def default_readme(self):
if self._default_readme is None:
self._default_readme = self._str_from_data_file('readme')
return self._default_readme
@property
def default_meta(self):
if self._default_meta is None:
self._default_meta = self._str_from_data_file('metadata_template.j2')
return self._default_meta
@property
def default_test(self):
if self._default_test is None:
self._default_test = self._str_from_data_file('test_playbook.j2')
return self._default_test
@property
def default_travis(self):
if self._default_travis is None:
self._default_travis = self._str_from_data_file('travis.j2')
return self._default_travis
def add_role(self, role):
self.roles[role.name] = role

View File

@ -25,11 +25,15 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import urllib
from urllib2 import quote as urlquote, HTTPError
from urlparse import urlparse
import ansible.constants as C
from ansible.errors import AnsibleError
from ansible.module_utils.urls import open_url
from ansible.galaxy.token import GalaxyToken
try:
from __main__ import display
@ -43,45 +47,111 @@ class GalaxyAPI(object):
SUPPORTED_VERSIONS = ['v1']
def __init__(self, galaxy, api_server):
def __init__(self, galaxy):
self.galaxy = galaxy
self.token = GalaxyToken()
self._api_server = C.GALAXY_SERVER
self._validate_certs = not C.GALAXY_IGNORE_CERTS
try:
urlparse(api_server, scheme='https')
except:
raise AnsibleError("Invalid server API url passed: %s" % api_server)
# set validate_certs
if galaxy.options.ignore_certs:
self._validate_certs = False
display.vvv('Validate TLS certificates: %s' % self._validate_certs)
server_version = self.get_server_api_version('%s/api/' % (api_server))
if not server_version:
raise AnsibleError("Could not retrieve server API version: %s" % api_server)
# set the API server
if galaxy.options.api_server != C.GALAXY_SERVER:
self._api_server = galaxy.options.api_server
display.vvv("Connecting to galaxy_server: %s" % self._api_server)
if server_version in self.SUPPORTED_VERSIONS:
self.baseurl = '%s/api/%s' % (api_server, server_version)
self.version = server_version # for future use
display.vvvvv("Base API: %s" % self.baseurl)
else:
server_version = self.get_server_api_version()
if not server_version in self.SUPPORTED_VERSIONS:
raise AnsibleError("Unsupported Galaxy server API version: %s" % server_version)
def get_server_api_version(self, api_server):
self.baseurl = '%s/api/%s' % (self._api_server, server_version)
self.version = server_version # for future use
display.vvv("Base API: %s" % self.baseurl)
def __auth_header(self):
token = self.token.get()
if token is None:
raise AnsibleError("No access token. You must first use login to authenticate and obtain an access token.")
return {'Authorization': 'Token ' + token}
def __call_galaxy(self, url, args=None, headers=None, method=None):
if args and not headers:
headers = self.__auth_header()
try:
display.vvv(url)
resp = open_url(url, data=args, validate_certs=self._validate_certs, headers=headers, method=method)
data = json.load(resp)
except HTTPError as e:
res = json.load(e)
raise AnsibleError(res['detail'])
return data
@property
def api_server(self):
return self._api_server
@property
def validate_certs(self):
return self._validate_certs
def get_server_api_version(self):
"""
Fetches the Galaxy API current version to ensure
the API server is up and reachable.
"""
#TODO: fix galaxy server which returns current_version path (/api/v1) vs actual version (v1)
# also should set baseurl using supported_versions which has path
return 'v1'
try:
data = json.load(open_url(api_server, validate_certs=self.galaxy.options.validate_certs))
return data.get("current_version", 'v1')
except Exception:
# TODO: report error
return None
url = '%s/api/' % self._api_server
data = json.load(open_url(url, validate_certs=self._validate_certs))
return data['current_version']
except Exception as e:
raise AnsibleError("The API server (%s) is not responding, please try again later." % url)
def authenticate(self, github_token):
"""
Retrieve an authentication token
"""
url = '%s/tokens/' % self.baseurl
args = urllib.urlencode({"github_token": github_token})
resp = open_url(url, data=args, validate_certs=self._validate_certs, method="POST")
data = json.load(resp)
return data
def create_import_task(self, github_user, github_repo, reference=None):
"""
Post an import request
"""
url = '%s/imports/' % self.baseurl
args = urllib.urlencode({
"github_user": github_user,
"github_repo": github_repo,
"github_reference": reference if reference else ""
})
data = self.__call_galaxy(url, args=args)
if data.get('results', None):
return data['results']
return data
def get_import_task(self, task_id=None, github_user=None, github_repo=None):
"""
Check the status of an import task.
"""
url = '%s/imports/' % self.baseurl
if not task_id is None:
url = "%s?id=%d" % (url,task_id)
elif not github_user is None and not github_repo is None:
url = "%s?github_user=%s&github_repo=%s" % (url,github_user,github_repo)
else:
raise AnsibleError("Expected task_id or github_user and github_repo")
data = self.__call_galaxy(url)
return data['results']
def lookup_role_by_name(self, role_name, notify=True):
"""
Find a role by name
Find a role by name.
"""
role_name = urlquote(role_name)
@ -92,18 +162,12 @@ class GalaxyAPI(object):
if notify:
display.display("- downloading role '%s', owned by %s" % (role_name, user_name))
except:
raise AnsibleError("- invalid role name (%s). Specify role as format: username.rolename" % role_name)
raise AnsibleError("Invalid role name (%s). Specify role as format: username.rolename" % role_name)
url = '%s/roles/?owner__username=%s&name=%s' % (self.baseurl, user_name, role_name)
display.vvvv("- %s" % (url))
try:
data = json.load(open_url(url, validate_certs=self.galaxy.options.validate_certs))
if len(data["results"]) != 0:
return data["results"][0]
except:
# TODO: report on connection/availability errors
pass
data = self.__call_galaxy(url)
if len(data["results"]) != 0:
return data["results"][0]
return None
def fetch_role_related(self, related, role_id):
@ -114,13 +178,12 @@ class GalaxyAPI(object):
try:
url = '%s/roles/%d/%s/?page_size=50' % (self.baseurl, int(role_id), related)
data = json.load(open_url(url, validate_certs=self.galaxy.options.validate_certs))
data = self.__call_galaxy(url)
results = data['results']
done = (data.get('next', None) is None)
while not done:
url = '%s%s' % (self.baseurl, data['next'])
display.display(url)
data = json.load(open_url(url, validate_certs=self.galaxy.options.validate_certs))
data = self.__call_galaxy(url)
results += data['results']
done = (data.get('next', None) is None)
return results
@ -131,10 +194,9 @@ class GalaxyAPI(object):
"""
Fetch the list of items specified.
"""
try:
url = '%s/%s/?page_size' % (self.baseurl, what)
data = json.load(open_url(url, validate_certs=self.galaxy.options.validate_certs))
data = self.__call_galaxy(url)
if "results" in data:
results = data['results']
else:
@ -144,41 +206,64 @@ class GalaxyAPI(object):
done = (data.get('next', None) is None)
while not done:
url = '%s%s' % (self.baseurl, data['next'])
display.display(url)
data = json.load(open_url(url, validate_certs=self.galaxy.options.validate_certs))
data = self.__call_galaxy(url)
results += data['results']
done = (data.get('next', None) is None)
return results
except Exception as error:
raise AnsibleError("Failed to download the %s list: %s" % (what, str(error)))
def search_roles(self, search, platforms=None, tags=None):
def search_roles(self, search, **kwargs):
search_url = self.baseurl + '/roles/?page=1'
search_url = self.baseurl + '/search/roles/?'
if search:
search_url += '&search=' + urlquote(search)
search_url += '&autocomplete=' + urlquote(search)
if tags is None:
tags = []
elif isinstance(tags, basestring):
tags = kwargs.get('tags',None)
platforms = kwargs.get('platforms', None)
page_size = kwargs.get('page_size', None)
author = kwargs.get('author', None)
if tags and isinstance(tags, basestring):
tags = tags.split(',')
for tag in tags:
search_url += '&chain__tags__name=' + urlquote(tag)
if platforms is None:
platforms = []
elif isinstance(platforms, basestring):
search_url += '&tags_autocomplete=' + '+'.join(tags)
if platforms and isinstance(platforms, basestring):
platforms = platforms.split(',')
search_url += '&platforms_autocomplete=' + '+'.join(platforms)
for plat in platforms:
search_url += '&chain__platforms__name=' + urlquote(plat)
display.debug("Executing query: %s" % search_url)
try:
data = json.load(open_url(search_url, validate_certs=self.galaxy.options.validate_certs))
except HTTPError as e:
raise AnsibleError("Unsuccessful request to server: %s" % str(e))
if page_size:
search_url += '&page_size=%s' % page_size
if author:
search_url += '&username_autocomplete=%s' % author
data = self.__call_galaxy(search_url)
return data
def add_secret(self, source, github_user, github_repo, secret):
url = "%s/notification_secrets/" % self.baseurl
args = urllib.urlencode({
"source": source,
"github_user": github_user,
"github_repo": github_repo,
"secret": secret
})
data = self.__call_galaxy(url, args=args)
return data
def list_secrets(self):
url = "%s/notification_secrets" % self.baseurl
data = self.__call_galaxy(url, headers=self.__auth_header())
return data
def remove_secret(self, secret_id):
url = "%s/notification_secrets/%s/" % (self.baseurl, secret_id)
data = self.__call_galaxy(url, headers=self.__auth_header(), method='DELETE')
return data
def delete_role(self, github_user, github_repo):
url = "%s/removerole/?github_user=%s&github_repo=%s" % (self.baseurl,github_user,github_repo)
data = self.__call_galaxy(url, headers=self.__auth_header(), method='DELETE')
return data

View File

@ -2,9 +2,11 @@ galaxy_info:
author: {{ author }}
description: {{description}}
company: {{ company }}
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: {{ issue_tracker_url }}
# Some suggested licenses:
# - BSD (default)
# - MIT
@ -13,7 +15,17 @@ galaxy_info:
# - Apache
# - CC-BY
license: {{ license }}
min_ansible_version: {{ min_ansible_version }}
# Optionally specify the branch Galaxy will use when accessing the GitHub
# repo for this role. During role install, if no tags are available,
# Galaxy will use this branch. During import Galaxy will access files on
# this branch. If travis integration is cofigured, only notification for this
# branch will be accepted. Otherwise, in all cases, the repo's default branch
# (usually master) will be used.
#github_branch:
#
# Below are all platforms currently available. Just uncomment
# the ones that apply to your role. If you don't see your
@ -28,6 +40,7 @@ galaxy_info:
# - {{ version }}
{%- endfor %}
{%- endfor %}
galaxy_tags: []
# List tags for your role here, one per line. A tag is
# a keyword that describes and categorizes the role.
@ -36,6 +49,7 @@ galaxy_info:
#
# NOTE: A tag is limited to a single word comprised of
# alphanumeric characters. Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line.
# Be sure to remove the '[]' above if you add dependencies

View File

@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- {{ role_name }}

View File

@ -0,0 +1,29 @@
---
language: python
python: "2.7"
# Use the new container infrastructure
sudo: false
# Install ansible
addons:
apt:
packages:
- python-pip
install:
# Install ansible
- pip install ansible
# Check ansible version
- ansible --version
# Create ansible.cfg with correct roles_path
- printf '[defaults]\nroles_path=../' >ansible.cfg
script:
# Basic role syntax check
- ansible-playbook tests/test.yml -i tests/inventory --syntax-check
notifications:
webhooks: https://galaxy.ansible.com/api/v1/notifications/

113
lib/ansible/galaxy/login.py Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env python
########################################################################
#
# (C) 2015, Chris Houseknecht <chouse@ansible.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 __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import getpass
import json
import urllib
from urllib2 import quote as urlquote, HTTPError
from urlparse import urlparse
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.module_utils.urls import open_url
from ansible.utils.color import stringc
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
class GalaxyLogin(object):
''' Class to handle authenticating user with Galaxy API prior to performing CUD operations '''
GITHUB_AUTH = 'https://api.github.com/authorizations'
def __init__(self, galaxy, github_token=None):
self.galaxy = galaxy
self.github_username = None
self.github_password = None
if github_token == None:
self.get_credentials()
def get_credentials(self):
display.display(u'\n\n' + "We need your " + stringc("Github login",'bright cyan') +
" to identify you.", screen_only=True)
display.display("This information will " + stringc("not be sent to Galaxy",'bright cyan') +
", only to " + stringc("api.github.com.","yellow"), screen_only=True)
display.display("The password will not be displayed." + u'\n\n', screen_only=True)
display.display("Use " + stringc("--github-token",'yellow') +
" if you do not want to enter your password." + u'\n\n', screen_only=True)
try:
self.github_username = raw_input("Github Username: ")
except:
pass
try:
self.github_password = getpass.getpass("Password for %s: " % self.github_username)
except:
pass
if not self.github_username or not self.github_password:
raise AnsibleError("Invalid Github credentials. Username and password are required.")
def remove_github_token(self):
'''
If for some reason an ansible-galaxy token was left from a prior login, remove it. We cannot
retrieve the token after creation, so we are forced to create a new one.
'''
try:
tokens = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username,
url_password=self.github_password, force_basic_auth=True,))
except HTTPError as e:
res = json.load(e)
raise AnsibleError(res['message'])
for token in tokens:
if token['note'] == 'ansible-galaxy login':
display.vvvvv('removing token: %s' % token['token_last_eight'])
try:
open_url('https://api.github.com/authorizations/%d' % token['id'], url_username=self.github_username,
url_password=self.github_password, method='DELETE', force_basic_auth=True,)
except HTTPError as e:
res = json.load(e)
raise AnsibleError(res['message'])
def create_github_token(self):
'''
Create a personal authorization token with a note of 'ansible-galaxy login'
'''
self.remove_github_token()
args = json.dumps({"scopes":["public_repo"], "note":"ansible-galaxy login"})
try:
data = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username,
url_password=self.github_password, force_basic_auth=True, data=args))
except HTTPError as e:
res = json.load(e)
raise AnsibleError(res['message'])
return data['token']

View File

@ -46,7 +46,7 @@ class GalaxyRole(object):
SUPPORTED_SCMS = set(['git', 'hg'])
META_MAIN = os.path.join('meta', 'main.yml')
META_INSTALL = os.path.join('meta', '.galaxy_install_info')
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars')
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars','tests')
def __init__(self, galaxy, name, src=None, version=None, scm=None, path=None):
@ -130,13 +130,11 @@ class GalaxyRole(object):
install_date=datetime.datetime.utcnow().strftime("%c"),
)
info_path = os.path.join(self.path, self.META_INSTALL)
try:
f = open(info_path, 'w+')
self._install_info = yaml.safe_dump(info, f)
except:
return False
finally:
f.close()
with open(info_path, 'w+') as f:
try:
self._install_info = yaml.safe_dump(info, f)
except:
return False
return True
@ -198,10 +196,10 @@ class GalaxyRole(object):
role_data = self.src
tmp_file = self.fetch(role_data)
else:
api = GalaxyAPI(self.galaxy, self.options.api_server)
api = GalaxyAPI(self.galaxy)
role_data = api.lookup_role_by_name(self.src)
if not role_data:
raise AnsibleError("- sorry, %s was not found on %s." % (self.src, self.options.api_server))
raise AnsibleError("- sorry, %s was not found on %s." % (self.src, api.api_server))
role_versions = api.fetch_role_related('versions', role_data['id'])
if not self.version:
@ -213,8 +211,10 @@ class GalaxyRole(object):
loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions]
loose_versions.sort()
self.version = str(loose_versions[-1])
elif role_data.get('github_branch', None):
self.version = role_data['github_branch']
else:
self.version = 'master'
self.version = 'master'
elif self.version != 'master':
if role_versions and self.version not in [a.get('name', None) for a in role_versions]:
raise AnsibleError("- the specified version (%s) of %s was not found in the list of available versions (%s)." % (self.version, self.name, role_versions))

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
########################################################################
#
# (C) 2015, Chris Houseknecht <chouse@ansible.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 __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import yaml
from stat import *
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
class GalaxyToken(object):
''' Class to storing and retrieving token in ~/.ansible_galaxy '''
def __init__(self):
self.file = os.path.expanduser("~") + '/.ansible_galaxy'
self.config = yaml.safe_load(self.__open_config_for_read())
if not self.config:
self.config = {}
def __open_config_for_read(self):
if os.path.isfile(self.file):
display.vvv('Opened %s' % self.file)
return open(self.file, 'r')
# config.yml not found, create and chomd u+rw
f = open(self.file,'w')
f.close()
os.chmod(self.file,S_IRUSR|S_IWUSR) # owner has +rw
display.vvv('Created %s' % self.file)
return open(self.file, 'r')
def set(self, token):
self.config['token'] = token
self.save()
def get(self):
return self.config.get('token', None)
def save(self):
with open(self.file,'w') as f:
yaml.safe_dump(self.config,f,default_flow_style=False)

View File

@ -78,6 +78,10 @@ class Inventory(object):
self._restriction = None
self._subset = None
# clear the cache here, which is only useful if more than
# one Inventory objects are created when using the API directly
self.clear_pattern_cache()
self.parse_inventory(host_list)
def serialize(self):
@ -109,7 +113,12 @@ class Inventory(object):
pass
elif isinstance(host_list, list):
for h in host_list:
(host, port) = parse_address(h, allow_ranges=False)
try:
(host, port) = parse_address(h, allow_ranges=False)
except AnsibleError as e:
display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_unicode(e))
host = h
port = None
all.add_host(Host(host, port))
elif self._loader.path_exists(host_list):
#TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins'
@ -178,25 +187,26 @@ class Inventory(object):
if self._restriction:
pattern_hash += u":%s" % to_unicode(self._restriction)
if pattern_hash in HOSTS_PATTERNS_CACHE:
return HOSTS_PATTERNS_CACHE[pattern_hash][:]
if pattern_hash not in HOSTS_PATTERNS_CACHE:
patterns = Inventory.split_host_pattern(pattern)
hosts = self._evaluate_patterns(patterns)
patterns = Inventory.split_host_pattern(pattern)
hosts = self._evaluate_patterns(patterns)
# mainly useful for hostvars[host] access
if not ignore_limits_and_restrictions:
# exclude hosts not in a subset, if defined
if self._subset:
subset = self._evaluate_patterns(self._subset)
hosts = [ h for h in hosts if h in subset ]
# mainly useful for hostvars[host] access
if not ignore_limits_and_restrictions:
# exclude hosts not in a subset, if defined
if self._subset:
subset = self._evaluate_patterns(self._subset)
hosts = [ h for h in hosts if h in subset ]
# exclude hosts mentioned in any restriction (ex: failed hosts)
if self._restriction is not None:
hosts = [ h for h in hosts if h in self._restriction ]
# exclude hosts mentioned in any restriction (ex: failed hosts)
if self._restriction is not None:
hosts = [ h for h in hosts if h in self._restriction ]
HOSTS_PATTERNS_CACHE[pattern_hash] = hosts[:]
return hosts
seen = set()
HOSTS_PATTERNS_CACHE[pattern_hash] = [x for x in hosts if x not in seen and not seen.add(x)]
return HOSTS_PATTERNS_CACHE[pattern_hash][:]
@classmethod
def split_host_pattern(cls, pattern):
@ -227,15 +237,13 @@ class Inventory(object):
# If it doesn't, it could still be a single pattern. This accounts for
# non-separator uses of colons: IPv6 addresses and [x:y] host ranges.
else:
(base, port) = parse_address(pattern, allow_ranges=True)
if base:
try:
(base, port) = parse_address(pattern, allow_ranges=True)
patterns = [pattern]
# The only other case we accept is a ':'-separated list of patterns.
# This mishandles IPv6 addresses, and is retained only for backwards
# compatibility.
else:
except:
# The only other case we accept is a ':'-separated list of patterns.
# This mishandles IPv6 addresses, and is retained only for backwards
# compatibility.
patterns = re.findall(
r'''(?: # We want to match something comprising:
[^\s:\[\]] # (anything other than whitespace or ':[]'
@ -388,7 +396,7 @@ class Inventory(object):
end = -1
subscript = (int(start), int(end))
if sep == '-':
display.deprecated("Use [x:y] inclusive subscripts instead of [x-y]", version=2.0, removed=True)
display.warning("Use [x:y] inclusive subscripts instead of [x-y] which has been removed")
return (pattern, subscript)
@ -731,12 +739,12 @@ class Inventory(object):
if group and host is None:
# load vars in dir/group_vars/name_of_group
base_path = os.path.realpath(os.path.join(basedir, "group_vars/%s" % group.name))
results = self._variable_manager.add_group_vars_file(base_path, self._loader)
base_path = os.path.realpath(os.path.join(to_unicode(basedir, errors='strict'), "group_vars/%s" % group.name))
results = combine_vars(results, self._variable_manager.add_group_vars_file(base_path, self._loader))
elif host and group is None:
# same for hostvars in dir/host_vars/name_of_host
base_path = os.path.realpath(os.path.join(basedir, "host_vars/%s" % host.name))
results = self._variable_manager.add_host_vars_file(base_path, self._loader)
base_path = os.path.realpath(os.path.join(to_unicode(basedir, errors='strict'), "host_vars/%s" % host.name))
results = combine_vars(results, self._variable_manager.add_host_vars_file(base_path, self._loader))
# all done, results is a dictionary of variables for this particular host.
return results

View File

@ -192,6 +192,8 @@ class InventoryDirectory(object):
if group.name not in self.groups:
# it's brand new, add him!
self.groups[group.name] = group
# the Group class does not (yet) implement __eq__/__ne__,
# so unlike Host we do a regular comparison here
if self.groups[group.name] != group:
# different object, merge
self._merge_groups(self.groups[group.name], group)
@ -200,6 +202,9 @@ class InventoryDirectory(object):
if host.name not in self.hosts:
# Papa's got a brand new host
self.hosts[host.name] = host
# because the __eq__/__ne__ methods in Host() compare the
# name fields rather than references, we use id() here to
# do the object comparison for merges
if self.hosts[host.name] != host:
# different object, merge
self._merge_hosts(self.hosts[host.name], host)

View File

@ -19,6 +19,8 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import uuid
from ansible.inventory.group import Group
from ansible.utils.vars import combine_vars
@ -38,7 +40,7 @@ class Host:
def __eq__(self, other):
if not isinstance(other, Host):
return False
return self.name == other.name
return self._uuid == other._uuid
def __ne__(self, other):
return not self.__eq__(other)
@ -55,6 +57,7 @@ class Host:
name=self.name,
vars=self.vars.copy(),
address=self.address,
uuid=self._uuid,
gathered_facts=self._gathered_facts,
groups=groups,
)
@ -65,6 +68,7 @@ class Host:
self.name = data.get('name')
self.vars = data.get('vars', dict())
self.address = data.get('address', '')
self._uuid = data.get('uuid', uuid.uuid4())
groups = data.get('groups', [])
for group_data in groups:
@ -84,6 +88,7 @@ class Host:
self.set_variable('ansible_port', int(port))
self._gathered_facts = False
self._uuid = uuid.uuid4()
def __repr__(self):
return self.get_name()

View File

@ -124,6 +124,9 @@ class InventoryParser(object):
del pending_declarations[groupname]
continue
elif line.startswith('['):
self._raise_error("Invalid section entry: '%s'. Please make sure that there are no spaces" % line + \
"in the section entry, and that there are no other invalid characters")
# It's not a section, so the current state tells us what kind of
# definition it must be. The individual parsers will raise an
@ -264,9 +267,12 @@ class InventoryParser(object):
# Can the given hostpattern be parsed as a host with an optional port
# specification?
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
if not pattern:
self._raise_error("Can't parse '%s' as host[:port]" % hostpattern)
try:
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
except:
# not a recognizable host pattern
pattern = hostpattern
port = None
# Once we have separated the pattern, we expand it into list of one or
# more hostnames, depending on whether it contains any [x:y] ranges.

View File

@ -31,6 +31,7 @@ from ansible.errors import AnsibleError
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible.module_utils.basic import json_dict_bytes_to_unicode
from ansible.utils.unicode import to_str, to_unicode
class InventoryScript:
@ -57,12 +58,17 @@ class InventoryScript:
if sp.returncode != 0:
raise AnsibleError("Inventory script (%s) had an execution error: %s " % (filename,stderr))
self.data = stdout
# make sure script output is unicode so that json loader will output
# unicode strings itself
try:
self.data = to_unicode(stdout, errors="strict")
except Exception as e:
raise AnsibleError("inventory data from {0} contained characters that cannot be interpreted as UTF-8: {1}".format(to_str(self.filename), to_str(e)))
# see comment about _meta below
self.host_vars_from_top = None
self._parse(stderr)
def _parse(self, err):
all_hosts = {}
@ -72,13 +78,11 @@ class InventoryScript:
self.raw = self._loader.load(self.data)
except Exception as e:
sys.stderr.write(err + "\n")
raise AnsibleError("failed to parse executable inventory script results from {0}: {1}".format(self.filename, str(e)))
raise AnsibleError("failed to parse executable inventory script results from {0}: {1}".format(to_str(self.filename), to_str(e)))
if not isinstance(self.raw, Mapping):
sys.stderr.write(err + "\n")
raise AnsibleError("failed to parse executable inventory script results from {0}: data needs to be formatted as a json dict".format(self.filename))
self.raw = json_dict_bytes_to_unicode(self.raw)
raise AnsibleError("failed to parse executable inventory script results from {0}: data needs to be formatted as a json dict".format(to_str(self.filename)))
group = None
for (group_name, data) in self.raw.items():
@ -103,7 +107,7 @@ class InventoryScript:
if not isinstance(data, dict):
data = {'hosts': data}
# is not those subkeys, then simplified syntax, host with vars
elif not any(k in data for k in ('hosts','vars')):
elif not any(k in data for k in ('hosts','vars','children')):
data = {'hosts': [group_name], 'vars': data}
if 'hosts' in data:
@ -112,7 +116,7 @@ class InventoryScript:
"data for the host list:\n %s" % (group_name, data))
for hostname in data['hosts']:
if not hostname in all_hosts:
if hostname not in all_hosts:
all_hosts[hostname] = Host(hostname)
host = all_hosts[hostname]
group.add_host(host)
@ -145,10 +149,12 @@ class InventoryScript:
def get_host_variables(self, host):
""" Runs <script> --host <hostname> to determine additional host variables """
if self.host_vars_from_top is not None:
got = self.host_vars_from_top.get(host.name, {})
try:
got = self.host_vars_from_top.get(host.name, {})
except AttributeError as e:
raise AnsibleError("Improperly formated host information for %s: %s" % (host.name,to_str(e)))
return got
cmd = [self.filename, "--host", host.name]
try:
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@ -161,4 +167,3 @@ class InventoryScript:
return json_dict_bytes_to_unicode(self._loader.load(out))
except ValueError:
raise AnsibleError("could not parse post variable response: %s, %s" % (cmd, out))

View File

@ -65,6 +65,7 @@ import grp
import pwd
import platform
import errno
import datetime
from itertools import repeat, chain
try:
@ -213,7 +214,7 @@ except ImportError:
elif isinstance(node, ast.List):
return list(map(_convert, node.nodes))
elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v in node.items)
return dict((_convert(k), _convert(v)) for k, v in node.items())
elif isinstance(node, ast.Name):
if node.name in _safe_names:
return _safe_names[node.name]
@ -369,7 +370,12 @@ def return_values(obj):
sensitive values pre-jsonification."""
if isinstance(obj, basestring):
if obj:
yield obj
if isinstance(obj, bytes):
yield obj
else:
# Unicode objects should all convert to utf-8
# (still must deal with surrogateescape on python3)
yield obj.encode('utf-8')
return
elif isinstance(obj, Sequence):
for element in obj:
@ -391,10 +397,22 @@ def remove_values(value, no_log_strings):
""" Remove strings in no_log_strings from value. If value is a container
type, then remove a lot more"""
if isinstance(value, basestring):
if value in no_log_strings:
if isinstance(value, unicode):
# This should work everywhere on python2. Need to check
# surrogateescape on python3
bytes_value = value.encode('utf-8')
value_is_unicode = True
else:
bytes_value = value
value_is_unicode = False
if bytes_value in no_log_strings:
return 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
for omit_me in no_log_strings:
value = value.replace(omit_me, '*' * 8)
bytes_value = bytes_value.replace(omit_me, '*' * 8)
if value_is_unicode:
value = unicode(bytes_value, 'utf-8', errors='replace')
else:
value = bytes_value
elif isinstance(value, Sequence):
return [remove_values(elem, no_log_strings) for elem in value]
elif isinstance(value, Mapping):
@ -406,10 +424,13 @@ def remove_values(value, no_log_strings):
for omit_me in no_log_strings:
if omit_me in stringy_value:
return 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
elif isinstance(value, datetime.datetime):
value = value.isoformat()
else:
raise TypeError('Value of unknown type: %s, %s' % (type(value), value))
return value
def heuristic_log_sanitize(data, no_log_values=None):
''' Remove strings that look like passwords from log messages '''
# Currently filters:
@ -497,8 +518,12 @@ class AnsibleModule(object):
self.no_log = no_log
self.cleanup_files = []
self._debug = False
# May be used to set modifications to the environment for any
# run_command invocation
self.run_command_environ_update = {}
self.aliases = {}
self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug']
if add_file_common_args:
for k, v in FILE_COMMON_ARGUMENTS.items():
@ -507,6 +532,15 @@ class AnsibleModule(object):
self.params = self._load_params()
# append to legal_inputs and then possibly check against them
try:
self.aliases = self._handle_aliases()
except Exception:
e = get_exception()
# use exceptions here cause its not safe to call vail json until no_log is processed
print('{"failed": true, "msg": "Module alias error: %s"}' % str(e))
sys.exit(1)
# Save parameter values that should never be logged
self.no_log_values = set()
# Use the argspec to determine which args are no_log
@ -517,15 +551,10 @@ class AnsibleModule(object):
if no_log_object:
self.no_log_values.update(return_values(no_log_object))
# check the locale as set by the current environment, and
# reset to LANG=C if it's an invalid/unavailable locale
# check the locale as set by the current environment, and reset to
# a known valid (LANG=C) if it's an invalid/unavailable locale
self._check_locale()
self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug']
# append to legal_inputs and then possibly check against them
self.aliases = self._handle_aliases()
self._check_arguments(check_invalid_arguments)
# check exclusive early
@ -1034,7 +1063,6 @@ class AnsibleModule(object):
# as it would be returned by locale.getdefaultlocale()
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
e = get_exception()
# fallback to the 'C' locale, which may cause unicode
# issues but is preferable to simply failing because
# of an unknown locale
@ -1047,6 +1075,7 @@ class AnsibleModule(object):
self.fail_json(msg="An unknown error was encountered while attempting to validate the locale: %s" % e)
def _handle_aliases(self):
# this uses exceptions as it happens before we can safely call fail_json
aliases_results = {} #alias:canon
for (k,v) in self.argument_spec.items():
self._legal_inputs.append(k)
@ -1055,11 +1084,11 @@ class AnsibleModule(object):
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 mutually exclusive for %s" % k)
raise Exception("internal error: required and default are mutually exclusive for %s" % k)
if aliases is None:
continue
if type(aliases) != list:
self.fail_json(msg='internal error: aliases must be a list')
raise Exception('internal error: aliases must be a list')
for alias in aliases:
self._legal_inputs.append(alias)
aliases_results[alias] = k
@ -1257,7 +1286,7 @@ class AnsibleModule(object):
if isinstance(value, bool):
return value
if isinstance(value, basestring):
if isinstance(value, basestring) or isinstance(value, int):
return self.boolean(value)
raise TypeError('%s cannot be converted to a bool' % type(value))
@ -1414,7 +1443,6 @@ class AnsibleModule(object):
self.log(msg, log_args=log_args)
def _set_cwd(self):
try:
cwd = os.getcwd()
@ -1507,6 +1535,8 @@ class AnsibleModule(object):
self.add_path_info(kwargs)
if not 'changed' in kwargs:
kwargs['changed'] = False
if 'invocation' not in kwargs:
kwargs['invocation'] = {'module_args': self.params}
kwargs = remove_values(kwargs, self.no_log_values)
self.do_cleanup_files()
print(self.jsonify(kwargs))
@ -1517,6 +1547,8 @@ class AnsibleModule(object):
self.add_path_info(kwargs)
assert 'msg' in kwargs, "implementation error -- msg to explain the error is required"
kwargs['failed'] = True
if 'invocation' not in kwargs:
kwargs['invocation'] = {'module_args': self.params}
kwargs = remove_values(kwargs, self.no_log_values)
self.do_cleanup_files()
print(self.jsonify(kwargs))
@ -1687,25 +1719,29 @@ class AnsibleModule(object):
# rename might not preserve context
self.set_context_if_different(dest, context, False)
def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None):
def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None, environ_update=None):
'''
Execute a command, returns rc, stdout, and stderr.
args is the command to run
If args is a list, the command will be run with shell=False.
If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False
If args is a string and use_unsafe_shell=True it run with shell=True.
Other arguments:
- check_rc (boolean) Whether to call fail_json in case of
non zero RC. Default is False.
- close_fds (boolean) See documentation for subprocess.Popen().
Default is True.
- executable (string) See documentation for subprocess.Popen().
Default is None.
- prompt_regex (string) A regex string (not a compiled regex) which
can be used to detect prompts in the stdout
which would otherwise cause the execution
to hang (especially if no input data is
specified)
:arg args: is the command to run
* If args is a list, the command will be run with shell=False.
* If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False
* If args is a string and use_unsafe_shell=True it runs with shell=True.
:kw check_rc: Whether to call fail_json in case of non zero RC.
Default False
:kw close_fds: See documentation for subprocess.Popen(). Default True
:kw executable: See documentation for subprocess.Popen(). Default None
:kw data: If given, information to write to the stdin of the command
:kw binary_data: If False, append a newline to the data. Default False
:kw path_prefix: If given, additional path to find the command in.
This adds to the PATH environment vairable so helper commands in
the same directory can also be found
:kw cwd: iIf given, working directory to run the command inside
:kw use_unsafe_shell: See `args` parameter. Default False
:kw prompt_regex: Regex string (not a compiled regex) which can be
used to detect prompts in the stdout which would otherwise cause
the execution to hang (especially if no input data is specified)
:kwarg environ_update: dictionary to *update* os.environ with
'''
shell = False
@ -1716,7 +1752,9 @@ class AnsibleModule(object):
elif isinstance(args, basestring) and use_unsafe_shell:
shell = True
elif isinstance(args, basestring):
args = shlex.split(args.encode('utf-8'))
if isinstance(args, unicode):
args = args.encode('utf-8')
args = shlex.split(args)
else:
msg = "Argument 'args' to run_command must be list or string"
self.fail_json(rc=257, cmd=args, msg=msg)
@ -1736,10 +1774,19 @@ class AnsibleModule(object):
msg = None
st_in = None
# Set a temporary env path if a prefix is passed
env=os.environ
# Manipulate the environ we'll send to the new process
old_env_vals = {}
# We can set this from both an attribute and per call
for key, val in self.run_command_environ_update.items():
old_env_vals[key] = os.environ.get(key, None)
os.environ[key] = val
if environ_update:
for key, val in environ_update.items():
old_env_vals[key] = os.environ.get(key, None)
os.environ[key] = val
if path_prefix:
env['PATH']="%s:%s" % (path_prefix, env['PATH'])
old_env_vals['PATH'] = os.environ['PATH']
os.environ['PATH'] = "%s:%s" % (path_prefix, os.environ['PATH'])
# create a printable version of the command for use
# in reporting later, which strips out things like
@ -1781,11 +1828,10 @@ class AnsibleModule(object):
close_fds=close_fds,
stdin=st_in,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
env=os.environ,
)
if path_prefix:
kwargs['env'] = env
if cwd and os.path.isdir(cwd):
kwargs['cwd'] = cwd
@ -1808,7 +1854,6 @@ class AnsibleModule(object):
else:
running = args
self.log('Executing: ' + running)
cmd = subprocess.Popen(args, **kwargs)
# the communication logic here is essentially taken from that
@ -1864,6 +1909,13 @@ class AnsibleModule(object):
except:
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
# Restore env settings
for key, val in old_env_vals.items():
if val is None:
del os.environ[key]
else:
os.environ[key] = val
if rc != 0 and check_rc:
msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)

View File

@ -78,6 +78,10 @@ class AnsibleCloudStack(object):
self.returns = {}
# these values will be casted to int
self.returns_to_int = {}
# these keys will be compared case sensitive in self.has_changed()
self.case_sensitive_keys = [
'id',
]
self.module = module
self._connect()
@ -138,16 +142,14 @@ class AnsibleCloudStack(object):
continue
if key in current_dict:
# API returns string for int in some cases, just to make sure
if isinstance(value, int):
current_dict[key] = int(current_dict[key])
elif isinstance(value, str):
current_dict[key] = str(current_dict[key])
# Only need to detect a singe change, not every item
if value != current_dict[key]:
if self.case_sensitive_keys and key in self.case_sensitive_keys:
if str(value) != str(current_dict[key]):
return True
# Test for diff in case insensitive way
elif str(value).lower() != str(current_dict[key]).lower():
return True
else:
return True
return False
@ -218,7 +220,7 @@ class AnsibleCloudStack(object):
vms = self.cs.listVirtualMachines(**args)
if vms:
for v in vms['virtualmachine']:
if vm in [ v['name'], v['displayname'], v['id'] ]:
if vm.lower() in [ v['name'].lower(), v['displayname'].lower(), v['id'] ]:
self.vm = v
return self._get_by_key(key, self.vm)
self.module.fail_json(msg="Virtual machine '%s' not found" % vm)
@ -238,7 +240,7 @@ class AnsibleCloudStack(object):
if zones:
for z in zones['zone']:
if zone in [ z['name'], z['id'] ]:
if zone.lower() in [ z['name'].lower(), z['id'] ]:
self.zone = z
return self._get_by_key(key, self.zone)
self.module.fail_json(msg="zone '%s' not found" % zone)

View File

@ -1,155 +0,0 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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 module adds shared support for Arista EOS devices using eAPI over
HTTP/S transport. It is built on module_utils/urls.py which is required
for proper operation.
In order to use this module, include it as part of a custom
module as shown below.
** Note: The order of the import statements does matter. **
from ansible.module_utils.basic import *
from ansible.module_utils.urls import *
from ansible.module_utils.eapi import *
The eapi module provides the following common argument spec:
* host (str) - [Required] The IPv4 address or FQDN of the network device
* port (str) - Overrides the default port to use for the HTTP/S
connection. The default values are 80 for HTTP and
443 for HTTPS
* url_username (str) - [Required] The username to use to authenticate
the HTTP/S connection. Aliases: username
* url_password (str) - [Required] The password to use to authenticate
the HTTP/S connection. Aliases: password
* use_ssl (bool) - Specifies whether or not to use an encrypted (HTTPS)
connection or not. The default value is False.
* enable_mode (bool) - Specifies whether or not to enter `enable` mode
prior to executing the command list. The default value is True
* enable_password (str) - The password for entering `enable` mode
on the switch if configured.
In order to communicate with Arista EOS devices, the eAPI feature
must be enabled and configured on the device.
"""
def eapi_argument_spec(spec=None):
"""Creates an argument spec for working with eAPI
"""
arg_spec = url_argument_spec()
arg_spec.update(dict(
host=dict(required=True),
port=dict(),
url_username=dict(required=True, aliases=['username']),
url_password=dict(required=True, aliases=['password']),
use_ssl=dict(default=True, type='bool'),
enable_mode=dict(default=True, type='bool'),
enable_password=dict()
))
if spec:
arg_spec.update(spec)
return arg_spec
def eapi_url(module):
"""Construct a valid Arist eAPI URL
"""
if module.params['use_ssl']:
proto = 'https'
else:
proto = 'http'
host = module.params['host']
url = '{}://{}'.format(proto, host)
if module.params['port']:
url = '{}:{}'.format(url, module.params['port'])
return '{}/command-api'.format(url)
def to_list(arg):
"""Convert the argument to a list object
"""
if isinstance(arg, (list, tuple)):
return list(arg)
elif arg is not None:
return [arg]
else:
return []
def eapi_body(commands, encoding, reqid=None):
"""Create a valid eAPI JSON-RPC request message
"""
params = dict(version=1, cmds=to_list(commands), format=encoding)
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
def eapi_enable_mode(module):
"""Build commands for entering `enable` mode on the switch
"""
if module.params['enable_mode']:
passwd = module.params['enable_password']
if passwd:
return dict(cmd='enable', input=passwd)
else:
return 'enable'
def eapi_command(module, commands, encoding='json'):
"""Send an ordered list of commands to the device over eAPI
"""
commands = to_list(commands)
url = eapi_url(module)
enable = eapi_enable_mode(module)
if enable:
commands.insert(0, enable)
data = eapi_body(commands, encoding)
data = module.jsonify(data)
headers = {'Content-Type': 'application/json-rpc'}
response, headers = fetch_url(module, url, data=data, headers=headers,
method='POST')
if headers['status'] != 200:
module.fail_json(**headers)
response = module.from_json(response.read())
if 'error' in response:
err = response['error']
module.fail_json(msg='json-rpc error', **err)
if enable:
response['result'].pop(0)
return response['result'], headers
def eapi_configure(module, commands):
"""Send configuration commands to the device over eAPI
"""
commands.insert(0, 'configure')
response, headers = eapi_command(module, commands)
response.pop(0)
return response, headers

View File

@ -152,8 +152,7 @@ def get_aws_connection_info(module, boto3=False):
boto_params = dict(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
aws_session_token=security_token)
if validate_certs:
boto_params['verify'] = validate_certs
boto_params['verify'] = validate_certs
if profile_name:
boto_params['profile_name'] = profile_name
@ -170,7 +169,7 @@ def get_aws_connection_info(module, boto3=False):
module.fail_json("boto does not support profile_name before 2.24")
boto_params['profile_name'] = profile_name
if validate_certs and HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"):
if HAS_LOOSE_VERSION and LooseVersion(boto.Version) >= LooseVersion("2.6.0"):
boto_params['validate_certs'] = validate_certs
return region, ec2_url, boto_params

View File

@ -0,0 +1,236 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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/>.
#
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(type='int'),
username=dict(required=True),
password=dict(no_log=True),
authorize=dict(default=False, type='bool'),
auth_pass=dict(no_log=True),
transport=dict(choices=['cli', 'eapi']),
use_ssl=dict(default=True, type='bool'),
provider=dict()
)
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Eapi(object):
def __init__(self, module):
self.module = module
# sets the module_utils/urls.py req parameters
self.module.params['url_username'] = module.params['username']
self.module.params['url_password'] = module.params['password']
self.url = None
self.enable = None
def _get_body(self, commands, encoding, reqid=None):
"""Create a valid eAPI JSON-RPC request message
"""
params = dict(version=1, cmds=commands, format=encoding)
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
def connect(self):
host = self.module.params['host']
port = self.module.params['port']
if self.module.params['use_ssl']:
proto = 'https'
if not port:
port = 443
else:
proto = 'http'
if not port:
port = 80
self.url = '%s://%s:%s/command-api' % (proto, host, port)
def authorize(self):
if self.module.params['auth_pass']:
passwd = self.module.params['auth_pass']
self.enable = dict(cmd='enable', input=passwd)
else:
self.enable = 'enable'
def send(self, commands, encoding='json'):
"""Send commands to the device.
"""
clist = to_list(commands)
if self.enable is not None:
clist.insert(0, self.enable)
data = self._get_body(clist, encoding)
data = self.module.jsonify(data)
headers = {'Content-Type': 'application/json-rpc'}
response, headers = fetch_url(self.module, self.url, data=data,
headers=headers, method='POST')
if headers['status'] != 200:
self.module.fail_json(**headers)
response = self.module.from_json(response.read())
if 'error' in response:
err = response['error']
self.module.fail_json(msg='json-rpc error', **err)
if self.enable:
response['result'].pop(0)
return response['result']
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
try:
self.shell.open(host, port=port, username=username, password=password)
except Exception, exc:
msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc))
self.module.fail_json(msg=msg)
def authorize(self):
passwd = self.module.params['auth_pass']
self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd))
def send(self, commands):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
params = super(NetworkModule, self)._load_params()
provider = params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
if not params.get(key) and value is not None:
params[key] = value
return params
def connect(self):
if self.params['transport'] == 'eapi':
self.connection = Eapi(self)
else:
self.connection = Cli(self)
try:
self.connection.connect()
self.execute('terminal length 0')
if self.params['authorize']:
self.connection.authorize()
except Exception, exc:
self.fail_json(msg=exc.message)
def configure(self, commands):
commands = to_list(commands)
commands.insert(0, 'configure terminal')
responses = self.execute(commands)
responses.pop(0)
return responses
def config_replace(self, commands):
if self.params['transport'] == 'cli':
self.fail_json(msg='config replace only supported over eapi')
cmd = 'configure replace terminal:'
commands = '\n'.join(to_list(commands))
command = dict(cmd=cmd, input=commands)
self.execute(command)
def execute(self, commands, **kwargs):
try:
return self.connection.send(commands, **kwargs)
except Exception, exc:
self.fail_json(msg=exc.message, commands=commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=3)
def get_config(self):
cmd = 'show running-config'
if self.params.get('include_defaults'):
cmd += ' all'
if self.params['transport'] == 'cli':
return self.execute(cmd)[0]
else:
resp = self.execute(cmd, encoding='text')
return resp[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
module = NetworkModule(**kwargs)
# HAS_PARAMIKO is set by module_utils/shell.py
if module.params['transport'] == 'cli' and not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
module.connect()
return module

View File

@ -51,19 +51,35 @@ def f5_argument_spec():
def f5_parse_arguments(module):
if not bigsuds_found:
module.fail_json(msg="the python bigsuds module is required")
if not module.params['validate_certs']:
disable_ssl_cert_validation()
if module.params['validate_certs']:
import ssl
if not hasattr(ssl, 'SSLContext'):
module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
return (module.params['server'],module.params['user'],module.params['password'],module.params['state'],module.params['partition'],module.params['validate_certs'])
def bigip_api(bigip, user, password):
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
return api
def bigip_api(bigip, user, password, validate_certs):
try:
# bigsuds >= 1.0.3
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password, verify=validate_certs)
except TypeError:
# bigsuds < 1.0.3, no verify param
if validate_certs:
# Note: verified we have SSLContext when we parsed params
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
else:
import ssl
if hasattr(ssl, 'SSLContext'):
# Really, you should never do this. It disables certificate
# verification *globally*. But since older bigip libraries
# don't give us a way to toggle verification we need to
# disable it at the global level.
# From https://www.python.org/dev/peps/pep-0476/#id29
ssl._create_default_https_context = ssl._create_unverified_context
api = bigsuds.BIGIP(hostname=bigip, username=user, password=password)
def disable_ssl_cert_validation():
# You probably only want to do this for testing and never in production.
# From https://www.python.org/dev/peps/pep-0476/#id29
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
return api
# Fully Qualified name (with the partition)
def fq_name(partition,name):

View File

@ -119,6 +119,7 @@ class Facts(object):
('/etc/gentoo-release', 'Gentoo'),
('/etc/os-release', 'Debian'),
('/etc/lsb-release', 'Mandriva'),
('/etc/altlinux-release', 'Altlinux'),
('/etc/os-release', 'NA'),
)
SELINUX_MODE_DICT = { 1: 'enforcing', 0: 'permissive', -1: 'disabled' }
@ -270,7 +271,7 @@ class Facts(object):
OracleLinux = 'RedHat', OVS = 'RedHat', OEL = 'RedHat', Amazon = 'RedHat',
XenServer = 'RedHat', Ubuntu = 'Debian', Debian = 'Debian', Raspbian = 'Debian', Slackware = 'Slackware', SLES = 'Suse',
SLED = 'Suse', openSUSE = 'Suse', SuSE = 'Suse', SLES_SAP = 'Suse', Gentoo = 'Gentoo', Funtoo = 'Gentoo',
Archlinux = 'Archlinux', Manjaro = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake',
Archlinux = 'Archlinux', Manjaro = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake', Altlinux = 'Altlinux',
Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris',
SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin',
FreeBSD = 'FreeBSD', HPUX = 'HP-UX'
@ -323,7 +324,7 @@ class Facts(object):
for (path, name) in Facts.OSDIST_LIST:
if os.path.exists(path):
if os.path.getsize(path) > 0:
if self.facts['distribution'] in ('Fedora', ):
if self.facts['distribution'] in ('Fedora', 'Altlinux', ):
# Once we determine the value is one of these distros
# we trust the values are always correct
break
@ -356,6 +357,13 @@ class Facts(object):
else:
self.facts['distribution'] = data.split()[0]
break
elif name == 'Altlinux':
data = get_file_content(path)
if 'ALT Linux' in data:
self.facts['distribution'] = name
else:
self.facts['distribution'] = data.split()[0]
break
elif name == 'OtherLinux':
data = get_file_content(path)
if 'Amazon' in data:
@ -430,9 +438,9 @@ class Facts(object):
release = re.search("^PRETTY_NAME=[^(]+ \(?([^)]+?)\)", line)
if release:
self.facts['distribution_release'] = release.groups()[0]
elif 'enterprise' in data.lower():
elif 'enterprise' in data.lower() and 'VERSION_ID' in line:
release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) # SLES doesn't got funny release names
if release:
if release.group(1):
release = release.group(1)
else:
release = "0" # no minor number, so it is the first release
@ -524,7 +532,12 @@ class Facts(object):
keytypes = ('dsa', 'rsa', 'ecdsa', 'ed25519')
if self.facts['system'] == 'Darwin':
keydir = '/etc'
if self.facts['distribution'] == 'MacOSX' and LooseVersion(self.facts['distribution_version']) >= LooseVersion('10.11') :
keydir = '/etc/ssh'
else:
keydir = '/etc'
if self.facts['distribution'] == 'Altlinux':
keydir = '/etc/openssh'
else:
keydir = '/etc/ssh'
@ -551,14 +564,16 @@ class Facts(object):
proc_1 = get_file_content('/proc/1/comm')
if proc_1 is None:
rc, proc_1, err = module.run_command("ps -p 1 -o comm|tail -n 1", use_unsafe_shell=True)
else:
proc_1 = os.path.basename(proc_1)
if proc_1 in ['init', '/sbin/init']:
# many systems return init, so this cannot be trusted
if proc_1 in ['init', '/sbin/init', 'bash']:
# many systems return init, so this cannot be trusted, bash is from docker
proc_1 = None
# if not init/None it should be an identifiable or custom init, so we are done!
if proc_1 is not None:
self.facts['service_mgr'] = proc_1
self.facts['service_mgr'] = proc_1.strip()
# start with the easy ones
elif self.facts['distribution'] == 'MacOSX':
@ -2971,14 +2986,19 @@ def get_file_content(path, default=None, strip=True):
data = default
if os.path.exists(path) and os.access(path, os.R_OK):
try:
datafile = open(path)
data = datafile.read()
if strip:
data = data.strip()
if len(data) == 0:
data = default
finally:
datafile.close()
try:
datafile = open(path)
data = datafile.read()
if strip:
data = data.strip()
if len(data) == 0:
data = default
finally:
datafile.close()
except:
# ignore errors as some jails/containers might have readable permissions but not allow reads to proc
# done in 2 blocks for 2.4 compat
pass
return data
def get_file_lines(path):

View File

@ -0,0 +1,146 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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/>.
#
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(default=22, type='int'),
username=dict(required=True),
password=dict(no_log=True),
authorize=dict(default=False, type='bool'),
auth_pass=dict(no_log=True),
provider=dict()
)
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
try:
self.shell.open(host, port=port, username=username, password=password)
except Exception, exc:
msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc))
self.module.fail_json(msg=msg)
def authorize(self):
passwd = self.module.params['auth_pass']
self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd))
def send(self, commands):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
params = super(NetworkModule, self)._load_params()
provider = params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
params[key] = value
return params
def connect(self):
try:
self.connection = Cli(self)
self.connection.connect()
self.execute('terminal length 0')
if self.params['authorize']:
self.connection.authorize()
except Exception, exc:
self.fail_json(msg=exc.message)
def configure(self, commands):
commands = to_list(commands)
commands.insert(0, 'configure terminal')
responses = self.execute(commands)
responses.pop(0)
return responses
def execute(self, commands, **kwargs):
try:
return self.connection.send(commands, **kwargs)
except Exception, exc:
self.fail_json(msg=exc.message, commands=commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=1)
def get_config(self):
cmd = 'show running-config'
if self.params.get('include_defaults'):
cmd += ' all'
return self.execute(cmd)[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
module = NetworkModule(**kwargs)
# HAS_PARAMIKO is set by module_utils/shell.py
if not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
module.connect()
return module

View File

@ -0,0 +1,127 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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/>.
#
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(default=22, type='int'),
username=dict(required=True),
password=dict(no_log=True),
provider=dict()
)
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
try:
self.shell.open(host, port=port, username=username, password=password)
except Exception, exc:
msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc))
self.module.fail_json(msg=msg)
def send(self, commands):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
params = super(NetworkModule, self)._load_params()
provider = params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
params[key] = value
return params
def connect(self):
try:
self.connection = Cli(self)
self.connection.connect()
self.execute('terminal length 0')
except Exception, exc:
self.fail_json(msg=exc.message)
def configure(self, commands):
commands = to_list(commands)
commands.insert(0, 'configure terminal')
commands.append('commit')
responses = self.execute(commands)
responses.pop(0)
responses.pop()
return responses
def execute(self, commands, **kwargs):
return self.connection.send(commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=1)
def get_config(self):
return self.execute('show running-config')[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
module = NetworkModule(**kwargs)
if not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
module.connect()
return module

View File

@ -0,0 +1,90 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is based on
# Lib/posixpath.py of cpython
# It is licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
#
# 1. This LICENSE AGREEMENT is between the Python Software Foundation
# ("PSF"), and the Individual or Organization ("Licensee") accessing and
# otherwise using this software ("Python") in source or binary form and
# its associated documentation.
#
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby
# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
# analyze, test, perform and/or display publicly, prepare derivative works,
# distribute, and otherwise use Python alone or in any derivative version,
# provided, however, that PSF's License Agreement and PSF's notice of copyright,
# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved"
# are retained in Python alone or in any derivative version prepared by Licensee.
#
# 3. In the event Licensee prepares a derivative work that is based on
# or incorporates Python or any part thereof, and wants to make
# the derivative work available to others as provided herein, then
# Licensee hereby agrees to include in any such work a brief summary of
# the changes made to Python.
#
# 4. PSF is making Python available to Licensee on an "AS IS"
# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
# INFRINGE ANY THIRD PARTY RIGHTS.
#
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
#
# 6. This License Agreement will automatically terminate upon a material
# breach of its terms and conditions.
#
# 7. Nothing in this License Agreement shall be deemed to create any
# relationship of agency, partnership, or joint venture between PSF and
# Licensee. This License Agreement does not grant permission to use PSF
# trademarks or trade name in a trademark sense to endorse or promote
# products or services of Licensee, or any third party.
#
# 8. By copying, installing or otherwise using Python, Licensee
# agrees to be bound by the terms and conditions of this License
# Agreement.
import os
def ismount(path):
"""Test whether a path is a mount point
clone of os.path.ismount (from cpython Lib/posixpath.py)
fixed to solve https://github.com/ansible/ansible-modules-core/issues/2186
and workaround non-fixed http://bugs.python.org/issue2466
this should be rewritten as soon as python issue 2466 is fixed
probably check for python version and use os.path.ismount if fixed
to remove replace in this file ismount( -> os.path.ismount( and remove this
function"""
try:
s1 = os.lstat(path)
except OSError:
# the OSError should be handled with more care
# it could be a "permission denied" but path is still a mount
return False
else:
# A symlink can never be a mount point
if os.path.stat.S_ISLNK(s1.st_mode):
return False
parent = os.path.join(path, os.path.pardir)
parent = os.path.realpath(parent)
try:
s2 = os.lstat(parent)
except OSError:
# one should handle the returned OSError with more care to figure
# out whether this is still a mount
return False
if s1.st_dev != s2.st_dev:
return True # path/.. on a different device as path
if s1.st_ino == s2.st_ino:
return True # path/.. is the same i-node as path, i.e. path=='/'
return False

View File

@ -0,0 +1,128 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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/>.
#
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(default=22, type='int'),
username=dict(required=True),
password=dict(no_log=True),
provider=dict()
)
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
try:
self.shell.open(host, port=port, username=username, password=password)
except Exception, exc:
msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc))
self.module.fail_json(msg=msg)
def send(self, commands):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
params = super(NetworkModule, self)._load_params()
provider = params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
params[key] = value
return params
def connect(self):
self.connection = Cli(self)
self.connection.connect()
if self.connection.shell._matched_prompt.strip().endswith('%'):
self.execute('cli')
self.execute('set cli screen-length 0')
def configure(self, commands):
commands = to_list(commands)
commands.insert(0, 'configure')
commands.append('commit and-quit')
responses = self.execute(commands)
responses.pop(0)
responses.pop()
return responses
def execute(self, commands, **kwargs):
return self.connection.send(commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=4)
def get_config(self):
cmd = 'show configuration'
return self.execute(cmd)[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
kwargs['check_invalid_arguments'] = False
module = NetworkModule(**kwargs)
# HAS_PARAMIKO is set by module_utils/shell.py
if not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
module.connect()
return module

View File

@ -28,7 +28,11 @@
import os
import hmac
import urlparse
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
try:
from hashlib import sha1
@ -74,12 +78,12 @@ def get_fqdn(repo_url):
if "@" in repo_url and "://" not in repo_url:
# most likely an user@host:path or user@host/path type URL
repo_url = repo_url.split("@", 1)[1]
if ":" in repo_url:
repo_url = repo_url.split(":")[0]
result = repo_url
if repo_url.startswith('['):
result = repo_url.split(']', 1)[0] + ']'
elif ":" in repo_url:
result = repo_url.split(":")[0]
elif "/" in repo_url:
repo_url = repo_url.split("/")[0]
result = repo_url
result = repo_url.split("/")[0]
elif "://" in repo_url:
# this should be something we can parse with urlparse
parts = urlparse.urlparse(repo_url)
@ -87,11 +91,13 @@ def get_fqdn(repo_url):
# ensure we actually have a parts[1] before continuing.
if parts[1] != '':
result = parts[1]
if ":" in result:
result = result.split(":")[0]
if "@" in result:
result = result.split("@", 1)[1]
if result[0].startswith('['):
result = result.split(']', 1)[0] + ']'
elif ":" in result:
result = result.split(":")[0]
return result
def check_hostkey(module, fqdn):
@ -113,6 +119,7 @@ def not_in_host_file(self, host):
host_file_list.append(user_host_file)
host_file_list.append("/etc/ssh/ssh_known_hosts")
host_file_list.append("/etc/ssh/ssh_known_hosts2")
host_file_list.append("/etc/openssh/ssh_known_hosts")
hfiles_not_found = 0
for hf in host_file_list:
@ -169,7 +176,7 @@ def add_host_key(module, fqdn, key_type="rsa", create_dir=False):
if not os.path.exists(user_ssh_dir):
if create_dir:
try:
os.makedirs(user_ssh_dir, 0700)
os.makedirs(user_ssh_dir, int('700', 8))
except:
module.fail_json(msg="failed to create host key directory: %s" % user_ssh_dir)
else:

View File

@ -0,0 +1,66 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c), Jonathan Mainguy <jon@soh.re>, 2015
# Most of this was originally added by Sven Schliesing @muffl0n in the mysql_user.py module
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
def mysql_connect(module, login_user=None, login_password=None, config_file='', ssl_cert=None, ssl_key=None, ssl_ca=None, db=None, cursor_class=None):
config = {
'host': module.params['login_host'],
'ssl': {
}
}
if module.params['login_unix_socket']:
config['unix_socket'] = module.params['login_unix_socket']
else:
config['port'] = module.params['login_port']
if os.path.exists(config_file):
config['read_default_file'] = config_file
# If login_user or login_password are given, they should override the
# config file
if login_user is not None:
config['user'] = login_user
if login_password is not None:
config['passwd'] = login_password
if ssl_cert is not None:
config['ssl']['cert'] = ssl_cert
if ssl_key is not None:
config['ssl']['key'] = ssl_key
if ssl_ca is not None:
config['ssl']['ca'] = ssl_ca
if db is not None:
config['db'] = db
db_connection = MySQLdb.connect(**config)
if cursor_class is not None:
return db_connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
else:
return db_connection.cursor()

View File

@ -0,0 +1,170 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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 re
import collections
class ConfigLine(object):
def __init__(self, text):
self.text = text
self.children = list()
self.parents = list()
self.raw = None
def __str__(self):
return self.raw
def __eq__(self, other):
if self.text == other.text:
return self.parents == other.parents
def __ne__(self, other):
return not self.__eq__(other)
def parse(lines, indent):
toplevel = re.compile(r'\S')
childline = re.compile(r'^\s*(.+)$')
repl = r'([{|}|;])'
ancestors = list()
config = list()
for line in str(lines).split('\n'):
text = str(re.sub(repl, '', line)).strip()
cfg = ConfigLine(text)
cfg.raw = line
if not text or text[0] in ['!', '#']:
continue
# handle top level commands
if toplevel.match(line):
ancestors = [cfg]
# handle sub level commands
else:
match = childline.match(line)
line_indent = match.start(1)
level = int(line_indent / indent)
parent_level = level - 1
cfg.parents = ancestors[:level]
if level > len(ancestors):
config.append(cfg)
continue
for i in range(level, len(ancestors)):
ancestors.pop()
ancestors.append(cfg)
ancestors[parent_level].children.append(cfg)
config.append(cfg)
return config
class Conditional(object):
'''
Used in command modules to evaluate waitfor conditions
'''
OPERATORS = {
'eq': ['eq', '=='],
'neq': ['neq', 'ne', '!='],
'gt': ['gt', '>'],
'ge': ['ge', '>='],
'lt': ['lt', '<'],
'le': ['le', '<='],
'contains': ['contains']
}
def __init__(self, conditional):
self.raw = conditional
key, op, val = shlex.split(conditional)
self.key = key
self.func = self.func(op)
self.value = self._cast_value(val)
def __call__(self, data):
try:
value = self.get_value(dict(result=data))
return self.func(value)
except Exception:
raise ValueError(self.key)
def _cast_value(self, value):
if value in BOOLEANS_TRUE:
return True
elif value in BOOLEANS_FALSE:
return False
elif re.match(r'^\d+\.d+$', value):
return float(value)
elif re.match(r'^\d+$', value):
return int(value)
else:
return unicode(value)
def func(self, oper):
for func, operators in self.OPERATORS.items():
if oper in operators:
return getattr(self, func)
raise AttributeError('unknown operator: %s' % oper)
def get_value(self, result):
for key in self.key.split('.'):
match = re.match(r'^(.+)\[(\d+)\]', key)
if match:
key, index = match.groups()
result = result[key][int(index)]
else:
result = result.get(key)
return result
def number(self, value):
if '.' in str(value):
return float(value)
else:
return int(value)
def eq(self, value):
return value == self.value
def neq(self, value):
return value != self.value
def gt(self, value):
return self.number(value) > self.value
def ge(self, value):
return self.number(value) >= self.value
def lt(self, value):
return self.number(value) < self.value
def le(self, value):
return self.number(value) <= self.value
def contains(self, value):
return self.value in value

View File

@ -1,130 +0,0 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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 module adds support for Cisco NXAPI to Ansible shared
module_utils. It builds on module_utils/urls.py to provide
NXAPI support over HTTP/S which is required for proper operation.
In order to use this module, include it as part of a custom
module as shown below.
** Note: The order of the import statements does matter. **
from ansible.module_utils.basic import *
from ansible.module_utils.urls import *
from ansible.module_utils.nxapi import *
The nxapi module provides the following common argument spec:
* host (str) - [Required] The IPv4 address or FQDN of the network device
* port (str) - Overrides the default port to use for the HTTP/S
connection. The default values are 80 for HTTP and
443 for HTTPS
* url_username (str) - [Required] The username to use to authenticate
the HTTP/S connection. Aliases: username
* url_password (str) - [Required] The password to use to authenticate
the HTTP/S connection. Aliases: password
* use_ssl (bool) - Specifies whether or not to use an encrypted (HTTPS)
connection or not. The default value is False.
* command_type (str) - The type of command to send to the remote
device. Valid values in `cli_show`, `cli_show_ascii`, 'cli_conf`
and `bash`. The default value is `cli_show_ascii`
In order to communicate with Cisco NXOS devices, the NXAPI feature
must be enabled and configured on the device.
"""
NXAPI_COMMAND_TYPES = ['cli_show', 'cli_show_ascii', 'cli_conf', 'bash']
def nxapi_argument_spec(spec=None):
"""Creates an argument spec for working with NXAPI
"""
arg_spec = url_argument_spec()
arg_spec.update(dict(
host=dict(required=True),
port=dict(),
url_username=dict(required=True, aliases=['username']),
url_password=dict(required=True, aliases=['password']),
use_ssl=dict(default=False, type='bool'),
command_type=dict(default='cli_show_ascii', choices=NXAPI_COMMAND_TYPES)
))
if spec:
arg_spec.update(spec)
return arg_spec
def nxapi_url(module):
"""Constructs a valid NXAPI url
"""
if module.params['use_ssl']:
proto = 'https'
else:
proto = 'http'
host = module.params['host']
url = '{}://{}'.format(proto, host)
port = module.params['port']
if module.params['port']:
url = '{}:{}'.format(url, module.params['port'])
url = '{}/ins'.format(url)
return url
def nxapi_body(commands, command_type, **kwargs):
"""Encodes a NXAPI JSON request message
"""
if isinstance(commands, (list, set, tuple)):
commands = ' ;'.join(commands)
msg = {
'version': kwargs.get('version') or '1.2',
'type': command_type,
'chunk': kwargs.get('chunk') or '0',
'sid': kwargs.get('sid'),
'input': commands,
'output_format': 'json'
}
return dict(ins_api=msg)
def nxapi_command(module, commands, command_type=None, **kwargs):
"""Sends the list of commands to the device over NXAPI
"""
url = nxapi_url(module)
command_type = command_type or module.params['command_type']
data = nxapi_body(commands, command_type)
data = module.jsonify(data)
headers = {'Content-Type': 'text/json'}
response, headers = fetch_url(module, url, data=data, headers=headers,
method='POST')
status = kwargs.get('status') or 200
if headers['status'] != status:
module.fail_json(**headers)
response = module.from_json(response.read())
return response, headers

View File

@ -0,0 +1,235 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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/>.
#
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(type='int'),
username=dict(required=True),
password=dict(no_log=True),
transport=dict(default='cli', choices=['cli', 'nxapi']),
use_ssl=dict(default=False, type='bool'),
provider=dict()
)
NXAPI_COMMAND_TYPES = ['cli_show', 'cli_show_ascii', 'cli_conf', 'bash']
NXAPI_ENCODINGS = ['json', 'xml']
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Nxapi(object):
def __init__(self, module):
self.module = module
# sets the module_utils/urls.py req parameters
self.module.params['url_username'] = module.params['username']
self.module.params['url_password'] = module.params['password']
self.url = None
self.enable = None
def _get_body(self, commands, command_type, encoding, version='1.2', chunk='0', sid=None):
"""Encodes a NXAPI JSON request message
"""
if isinstance(commands, (list, set, tuple)):
commands = ' ;'.join(commands)
if encoding not in NXAPI_ENCODINGS:
self.module.fail_json("Invalid encoding. Received %s. Expected one of %s" %
(encoding, ','.join(NXAPI_ENCODINGS)))
msg = {
'version': version,
'type': command_type,
'chunk': chunk,
'sid': sid,
'input': commands,
'output_format': encoding
}
return dict(ins_api=msg)
def connect(self):
host = self.module.params['host']
port = self.module.params['port']
if self.module.params['use_ssl']:
proto = 'https'
if not port:
port = 443
else:
proto = 'http'
if not port:
port = 80
self.url = '%s://%s:%s/ins' % (proto, host, port)
def send(self, commands, command_type='cli_show_ascii', encoding='json'):
"""Send commands to the device.
"""
clist = to_list(commands)
if command_type not in NXAPI_COMMAND_TYPES:
self.module.fail_json(msg="Invalid command_type. Received %s. Expected one of %s." %
(command_type, ','.join(NXAPI_COMMAND_TYPES)))
data = self._get_body(clist, command_type, encoding)
data = self.module.jsonify(data)
headers = {'Content-Type': 'application/json'}
response, headers = fetch_url(self.module, self.url, data=data,
headers=headers, method='POST')
if headers['status'] != 200:
self.module.fail_json(**headers)
response = self.module.from_json(response.read())
result = list()
try:
output = response['ins_api']['outputs']['output']
if isinstance(output, list):
for item in response['ins_api']['outputs']['output']:
if item['code'] != '200':
self.module.fail_json(msg=item['msg'], command=item['input'],
code=item['code'])
else:
result.append(item['body'])
elif output['code'] != '200':
self.module.fail_json(msg=item['msg'], command=item['input'],
code=item['code'])
else:
result.append(output['body'])
except Exception:
self.module.fail_json(**headers)
return result
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
try:
self.shell.open(host, port=port, username=username, password=password)
except Exception, exc:
msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc))
self.module.fail_json(msg=msg)
def send(self, commands, encoding='text'):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
params = super(NetworkModule, self)._load_params()
provider = params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
if not params.get(key) and value is not None:
params[key] = value
return params
def connect(self):
if self.params['transport'] == 'nxapi':
self.connection = Nxapi(self)
else:
self.connection = Cli(self)
self.connection.connect()
if self.params['transport'] == 'cli':
self.execute('terminal length 0')
def configure(self, commands):
commands = to_list(commands)
if self.params['transport'] == 'cli':
commands.insert(0, 'configure')
responses = self.execute(commands)
responses.pop(0)
else:
responses = self.execute(commands, command_type='cli_conf')
return responses
def execute(self, commands, **kwargs):
try:
return self.connection.send(commands, **kwargs)
except Exception, exc:
self.fail_json(msg=exc.message, commands=commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=2)
def get_config(self):
cmd = 'show running-config'
if self.params.get('include_defaults'):
cmd += ' all'
response = self.execute(cmd)
return response[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
module = NetworkModule(**kwargs)
# HAS_PARAMIKO is set by module_utils/shell.py
if module.params['transport'] == 'cli' and not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
module.connect()
return module

View File

@ -0,0 +1,246 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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
import json
try:
from runconfig import runconfig
from opsrest.settings import settings
from opsrest.manager import OvsdbConnectionManager
from opslib import restparser
HAS_OPS = True
except ImportError:
HAS_OPS = False
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(),
port=dict(type='int'),
username=dict(),
password=dict(no_log=True),
transport=dict(default='ssh', choices=['ssh', 'cli', 'rest']),
)
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
def get_idl():
manager = OvsdbConnectionManager(settings.get('ovs_remote'),
settings.get('ovs_schema'))
manager.start()
idl = manager.idl
init_seq_no = 0
while (init_seq_no == idl.change_seqno):
idl.run()
time.sleep(1)
return idl
def get_schema():
return restparser.parseSchema(settings.get('ext_schema'))
def get_runconfig():
idl = get_idl()
schema = get_schema()
return runconfig.RunConfigUtil(idl, schema)
class Response(object):
def __init__(self, resp, hdrs):
self.body = resp.read()
self.headers = hdrs
@property
def json(self):
try:
return json.loads(self.body)
except ValueError:
return None
class Rest(object):
def __init__(self, module):
self.module = module
self.baseurl = None
def connect(self):
host = self.module.params['host']
port = self.module.params['port']
if self.module.params['use_ssl']:
proto = 'https'
if not port:
port = 443
else:
proto = 'http'
if not port:
port = 80
self.baseurl = '%s://%s:%s/rest/v1' % (proto, host, port)
def _url_builder(self, path):
if path[0] == '/':
path = path[1:]
return '%s/%s' % (self.baseurl, path)
def send(self, method, path, data=None, headers=None):
url = self._url_builder(path)
data = self.module.jsonify(data)
if headers is None:
headers = dict()
headers.update({'Content-Type': 'application/json'})
resp, hdrs = fetch_url(self.module, url, data=data, headers=headers,
method=method)
return Response(resp, hdrs)
def get(self, path, data=None, headers=None):
return self.send('GET', path, data, headers)
def put(self, path, data=None, headers=None):
return self.send('PUT', path, data, headers)
def post(self, path, data=None, headers=None):
return self.send('POST', path, data, headers)
def delete(self, path, data=None, headers=None):
return self.send('DELETE', path, data, headers)
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
self.shell = Shell()
self.shell.open(host, port=port, username=username, password=password)
def send(self, commands, encoding='text'):
return self.shell.send(commands)
class OpsModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(OpsModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
self._runconfig = None
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def connect(self):
if self.params['transport'] == 'rest':
self.connection = Rest(self)
elif self.params['transport'] == 'cli':
self.connection = Cli(self)
try:
self.connection.connect()
except Exception, exc:
self.fail_json(msg=exc.message)
def configure(self, config):
if self.params['transport'] == 'cli':
commands = to_list(config)
commands.insert(0, 'configure terminal')
responses = self.execute(commands)
responses.pop(0)
return responses
elif self.params['transport'] == 'rest':
path = '/system/full-configuration'
return self.connection.put(path, data=config)
else:
if not self._runconfig:
self._runconfig = get_runconfig()
self._runconfig.write_config_to_db(config)
def execute(self, commands, **kwargs):
try:
return self.connection.send(commands, **kwargs)
except Exception, exc:
self.fail_json(msg=exc.message, commands=commands)
def disconnect(self):
self.connection.close()
def parse_config(self, cfg):
return parse(cfg, indent=4)
def get_config(self):
if self.params['transport'] == 'cli':
return self.execute('show running-config')[0]
elif self.params['transport'] == 'rest':
resp = self.connection.get('/system/full-configuration')
return resp.json
else:
if not self._runconfig:
self._runconfig = get_runconfig()
return self._runconfig.get_running_config()
def get_module(**kwargs):
"""Return instance of OpsModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
kwargs['check_invalid_arguments'] = False
module = OpsModule(**kwargs)
if not HAS_OPS and module.params['transport'] == 'ssh':
module.fail_json(msg='could not import ops library')
# HAS_PARAMIKO is set by module_utils/shell.py
if module.params['transport'] == 'cli' and not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
# copy in values from local action.
params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS))
for key, value in params.iteritems():
module.params[key] = value
if module.params['transport'] in ['cli', 'rest']:
module.connect()
return module

View File

@ -0,0 +1,199 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.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 re
import socket
from StringIO import StringIO
try:
import paramiko
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
ANSI_RE = re.compile(r'(\x1b\[\?1h\x1b=)')
CLI_PROMPTS_RE = [
re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*[>|#](?:\s*)$'),
re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*\(.+\)#(?:\s*)$')
]
CLI_ERRORS_RE = [
re.compile(r"% ?Error"),
re.compile(r"^% \w+", re.M),
re.compile(r"% ?Bad secret"),
re.compile(r"invalid input", re.I),
re.compile(r"(?:incomplete|ambiguous) command", re.I),
re.compile(r"connection timed out", re.I),
re.compile(r"[^\r\n]+ not found", re.I),
re.compile(r"'[^']' +returned error code: ?\d+"),
re.compile(r"syntax error"),
re.compile(r"unknown command")
]
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class ShellError(Exception):
def __init__(self, msg, command=None):
super(ShellError, self).__init__(msg)
self.message = msg
self.command = command
class Command(object):
def __init__(self, command, prompt=None, response=None):
self.command = command
self.prompt = prompt
self.response = response
def __str__(self):
return self.command
class Shell(object):
def __init__(self):
self.ssh = None
self.shell = None
self._matched_prompt = None
self.prompts = list()
self.prompts.extend(CLI_PROMPTS_RE)
self.errors = list()
self.errors.extend(CLI_ERRORS_RE)
def open(self, host, port=22, username=None, password=None,
timeout=10, key_filename=None, pkey=None, look_for_keys=None,
allow_agent=False):
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
use_keys = password is None
self.ssh.connect(host, port=port, username=username, password=password,
timeout=timeout, look_for_keys=look_for_keys, pkey=pkey,
key_filename=key_filename, allow_agent=allow_agent)
self.shell = self.ssh.invoke_shell()
self.shell.settimeout(10)
self.receive()
def strip(self, data):
return ANSI_RE.sub('', data)
def receive(self, cmd=None):
recv = StringIO()
while True:
data = self.shell.recv(200)
recv.write(data)
recv.seek(recv.tell() - 200)
window = self.strip(recv.read())
if isinstance(cmd, Command):
self.handle_input(window, prompt=cmd.prompt,
response=cmd.response)
try:
if self.read(window):
resp = self.strip(recv.getvalue())
return self.sanitize(cmd, resp)
except ShellError, exc:
exc.command = cmd
raise
def send(self, commands):
responses = list()
try:
for command in to_list(commands):
cmd = '%s\r' % str(command)
self.shell.sendall(cmd)
responses.append(self.receive(command))
except socket.timeout, exc:
raise ShellError("timeout trying to send command", cmd)
return responses
def close(self):
self.shell.close()
def handle_input(self, resp, prompt, response):
if not prompt or not response:
return
prompt = to_list(prompt)
response = to_list(response)
for pr, ans in zip(prompt, response):
match = pr.search(resp)
if match:
cmd = '%s\r' % ans
self.shell.sendall(cmd)
def sanitize(self, cmd, resp):
cleaned = []
for line in resp.splitlines():
if line.startswith(str(cmd)) or self.read(line):
continue
cleaned.append(line)
return "\n".join(cleaned)
def read(self, response):
for regex in self.errors:
if regex.search(response):
raise ShellError('%s' % response)
for regex in self.prompts:
match = regex.search(response)
if match:
self._matched_prompt = match.group()
return True
def get_cli_connection(module):
host = module.params['host']
port = module.params['port']
if not port:
port = 22
username = module.params['username']
password = module.params['password']
try:
cli = Cli()
cli.open(host, port=port, username=username, password=password)
except paramiko.ssh_exception.AuthenticationException, exc:
module.fail_json(msg=exc.message)
except socket.error, exc:
host = '%s:%s' % (host, port)
module.fail_json(msg=exc.strerror, errno=exc.errno, host=host)
except socket.timeout:
module.fail_json(msg='socket timed out')
return cli

View File

@ -310,36 +310,45 @@ class NoSSLError(SSLValidationError):
"""Needed to connect to an HTTPS url but no ssl library available to verify the certificate"""
pass
# Some environments (Google Compute Engine's CoreOS deploys) do not compile
# against openssl and thus do not have any HTTPS support.
CustomHTTPSConnection = CustomHTTPSHandler = None
if hasattr(httplib, 'HTTPSConnection') and hasattr(urllib2, 'HTTPSHandler'):
class CustomHTTPSConnection(httplib.HTTPSConnection):
def __init__(self, *args, **kwargs):
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
if HAS_SSLCONTEXT:
self.context = create_default_context()
if self.cert_file:
self.context.load_cert_chain(self.cert_file, self.key_file)
class CustomHTTPSConnection(httplib.HTTPSConnection):
def __init__(self, *args, **kwargs):
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
if HAS_SSLCONTEXT:
self.context = create_default_context()
if self.cert_file:
self.context.load_cert_chain(self.cert_file, self.key_file)
def connect(self):
"Connect to a host on a given (SSL) port."
def connect(self):
"Connect to a host on a given (SSL) port."
if hasattr(self, 'source_address'):
sock = socket.create_connection((self.host, self.port), self.timeout, self.source_address)
else:
sock = socket.create_connection((self.host, self.port), self.timeout)
if hasattr(self, 'source_address'):
sock = socket.create_connection((self.host, self.port), self.timeout, self.source_address)
else:
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
if HAS_SSLCONTEXT:
self.sock = self.context.wrap_socket(sock, server_hostname=self.host)
else:
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, certfile=self.cert_file, ssl_version=PROTOCOL)
server_hostname = self.host
# Note: self._tunnel_host is not available on py < 2.6 but this code
# isn't used on py < 2.6 (lack of create_connection)
if self._tunnel_host:
self.sock = sock
self._tunnel()
server_hostname = self._tunnel_host
class CustomHTTPSHandler(urllib2.HTTPSHandler):
if HAS_SSLCONTEXT:
self.sock = self.context.wrap_socket(sock, server_hostname=server_hostname)
else:
self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, certfile=self.cert_file, ssl_version=PROTOCOL)
def https_open(self, req):
return self.do_open(CustomHTTPSConnection, req)
class CustomHTTPSHandler(urllib2.HTTPSHandler):
https_request = urllib2.AbstractHTTPHandler.do_request_
def https_open(self, req):
return self.do_open(CustomHTTPSConnection, req)
https_request = urllib2.AbstractHTTPHandler.do_request_
def generic_urlparse(parts):
'''
@ -373,7 +382,10 @@ def generic_urlparse(parts):
# get the username, password, etc.
try:
netloc_re = re.compile(r'^((?:\w)+(?::(?:\w)+)?@)?([A-Za-z0-9.-]+)(:\d+)?$')
(auth, hostname, port) = netloc_re.match(parts[1])
match = netloc_re.match(parts[1])
auth = match.group(1)
hostname = match.group(2)
port = match.group(3)
if port:
# the capture group for the port will include the ':',
# so remove it and convert the port to an integer
@ -383,6 +395,8 @@ def generic_urlparse(parts):
# and then split it up based on the first ':' found
auth = auth[:-1]
username, password = auth.split(':', 1)
else:
username = password = None
generic_parts['username'] = username
generic_parts['password'] = password
generic_parts['hostname'] = hostname
@ -390,7 +404,7 @@ def generic_urlparse(parts):
except:
generic_parts['username'] = None
generic_parts['password'] = None
generic_parts['hostname'] = None
generic_parts['hostname'] = parts[1]
generic_parts['port'] = None
return generic_parts
@ -532,7 +546,8 @@ class SSLValidationHandler(urllib2.BaseHandler):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if https_proxy:
proxy_parts = generic_urlparse(urlparse.urlparse(https_proxy))
s.connect((proxy_parts.get('hostname'), proxy_parts.get('port')))
port = proxy_parts.get('port') or 443
s.connect((proxy_parts.get('hostname'), port))
if proxy_parts.get('scheme') == 'http':
s.sendall(self.CONNECT_COMMAND % (self.hostname, self.port))
if proxy_parts.get('username'):
@ -542,7 +557,7 @@ class SSLValidationHandler(urllib2.BaseHandler):
connect_result = s.recv(4096)
self.validate_proxy_response(connect_result)
if context:
ssl_s = context.wrap_socket(s, server_hostname=proxy_parts.get('hostname'))
ssl_s = context.wrap_socket(s, server_hostname=self.hostname)
else:
ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL)
match_hostname(ssl_s.getpeercert(), self.hostname)
@ -661,8 +676,9 @@ def open_url(url, data=None, headers=None, method=None, use_proxy=True,
handlers.append(proxyhandler)
# pre-2.6 versions of python cannot use the custom https
# handler, since the socket class is lacking this method
if hasattr(socket, 'create_connection'):
# handler, since the socket class is lacking create_connection.
# Some python builds lack HTTPS support.
if hasattr(socket, 'create_connection') and CustomHTTPSHandler:
handlers.append(CustomHTTPSHandler)
opener = urllib2.build_opener(*handlers)

View File

@ -1,344 +0,0 @@
# (c) 2012-2014, 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/>.
#############################################
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
import sys
from ansible import constants as C
from ansible.inventory.group import Group
from .host import Host
from ansible.plugins.inventory.aggregate import InventoryAggregateParser
from ansible import errors
class Inventory:
'''
Create hosts and groups from inventory
Retrieve the hosts and groups that ansible knows about from this class.
Retrieve raw variables (non-expanded) from the Group and Host classes
returned from here.
'''
def __init__(self, inventory_list=C.DEFAULT_HOST_LIST):
'''
:kwarg inventory_list: A list of inventory sources. This may be file
names which will be parsed as ini-like files, executable scripts
which return inventory data as json, directories of both of the above,
or hostnames. Files and directories are
:kwarg vault_password: Password to use if any of the inventory sources
are in an ansible vault
'''
self._restricted_to = None
self._filter_pattern = None
parser = InventoryAggregateParser(inventory_list)
parser.parse()
self._basedir = parser.basedir
self._hosts = parser.hosts
self._groups = parser.groups
def get_hosts(self):
'''
Return the list of hosts, after filtering based on any set pattern
and restricting the results based on the set host restrictions.
'''
if self._filter_pattern:
hosts = self._filter_hosts()
else:
hosts = self._hosts[:]
if self._restricted_to is not None:
# this will preserve the order of hosts after intersecting them
res_set = set(hosts).intersection(self._restricted_to)
return [h for h in hosts if h in res_set]
else:
return hosts[:]
def get_groups(self):
'''
Retrieve the Group objects known to the Inventory
'''
return self._groups[:]
def get_host(self, hostname):
'''
Retrieve the Host object for a hostname
'''
for host in self._hosts:
if host.name == hostname:
return host
return None
def get_group(self, groupname):
'''
Retrieve the Group object for a groupname
'''
for group in self._groups:
if group.name == groupname:
return group
return None
def add_group(self, group):
'''
Add a new group to the inventory
'''
if group not in self._groups:
self._groups.append(group)
def set_filter_pattern(self, pattern='all'):
'''
Sets a pattern upon which hosts/groups will be filtered.
This pattern can contain logical groupings such as unions,
intersections and negations using special syntax.
'''
self._filter_pattern = pattern
def set_host_restriction(self, restriction):
'''
Restrict operations to hosts in the given list
'''
assert isinstance(restriction, list)
self._restricted_to = restriction[:]
def remove_host_restriction(self):
'''
Remove the restriction on hosts, if any.
'''
self._restricted_to = None
def _filter_hosts(self):
"""
Limits inventory results to a subset of inventory that matches a given
list of patterns, such as to select a subset of a hosts selection that also
belongs to a certain geographic group or numeric slice.
Corresponds to --limit parameter to ansible-playbook
:arg patterns: The pattern to limit with. If this is None it
clears the subset. Multiple patterns may be specified as a comma,
semicolon, or colon separated string.
"""
hosts = []
pattern_regular = []
pattern_intersection = []
pattern_exclude = []
patterns = self._pattern.replace(";",":").split(":")
for p in patterns:
if p.startswith("!"):
pattern_exclude.append(p)
elif p.startswith("&"):
pattern_intersection.append(p)
elif p:
pattern_regular.append(p)
# if no regular pattern was given, hence only exclude and/or intersection
# make that magically work
if pattern_regular == []:
pattern_regular = ['all']
# when applying the host selectors, run those without the "&" or "!"
# first, then the &s, then the !s.
patterns = pattern_regular + pattern_intersection + pattern_exclude
for p in patterns:
intersect = False
negate = False
if p.startswith('&'):
intersect = True
elif p.startswith('!'):
p = p[1:]
negate = True
target = self._resolve_pattern(p)
if isinstance(target, Host):
if negate and target in hosts:
# remove it
hosts.remove(target)
elif target not in hosts:
# for both union and intersections, we just append it
hosts.append(target)
else:
if intersect:
hosts = [ h for h in hosts if h not in target ]
elif negate:
hosts = [ h for h in hosts if h in target ]
else:
to_append = [ h for h in target if h.name not in [ y.name for y in hosts ] ]
hosts.extend(to_append)
return hosts
def _resolve_pattern(self, pattern):
target = self.get_host(pattern)
if target:
return target
else:
(name, enumeration_details) = self._enumeration_info(pattern)
hpat = self._hosts_in_unenumerated_pattern(name)
result = self._apply_ranges(pattern, hpat)
return result
def _enumeration_info(self, pattern):
"""
returns (pattern, limits) taking a regular pattern and finding out
which parts of it correspond to start/stop offsets. limits is
a tuple of (start, stop) or None
"""
# Do not parse regexes for enumeration info
if pattern.startswith('~'):
return (pattern, None)
# The regex used to match on the range, which can be [x] or [x-y].
pattern_re = re.compile("^(.*)\[([-]?[0-9]+)(?:(?:-)([0-9]+))?\](.*)$")
m = pattern_re.match(pattern)
if m:
(target, first, last, rest) = m.groups()
first = int(first)
if last:
if first < 0:
raise errors.AnsibleError("invalid range: negative indices cannot be used as the first item in a range")
last = int(last)
else:
last = first
return (target, (first, last))
else:
return (pattern, None)
def _apply_ranges(self, pat, hosts):
"""
given a pattern like foo, that matches hosts, return all of hosts
given a pattern like foo[0:5], where foo matches hosts, return the first 6 hosts
"""
# If there are no hosts to select from, just return the
# empty set. This prevents trying to do selections on an empty set.
# issue#6258
if not hosts:
return hosts
(loose_pattern, limits) = self._enumeration_info(pat)
if not limits:
return hosts
(left, right) = limits
if left == '':
left = 0
if right == '':
right = 0
left=int(left)
right=int(right)
try:
if left != right:
return hosts[left:right]
else:
return [ hosts[left] ]
except IndexError:
raise errors.AnsibleError("no hosts matching the pattern '%s' were found" % pat)
def _hosts_in_unenumerated_pattern(self, pattern):
""" Get all host names matching the pattern """
results = []
hosts = []
hostnames = set()
# ignore any negative checks here, this is handled elsewhere
pattern = pattern.replace("!","").replace("&", "")
def __append_host_to_results(host):
if host not in results and host.name not in hostnames:
hostnames.add(host.name)
results.append(host)
groups = self.get_groups()
for group in groups:
if pattern == 'all':
for host in group.get_hosts():
__append_host_to_results(host)
else:
if self._match(group.name, pattern):
for host in group.get_hosts():
__append_host_to_results(host)
else:
matching_hosts = self._match_list(group.get_hosts(), 'name', pattern)
for host in matching_hosts:
__append_host_to_results(host)
if pattern in ["localhost", "127.0.0.1"] and len(results) == 0:
new_host = self._create_implicit_localhost(pattern)
results.append(new_host)
return results
def _create_implicit_localhost(self, pattern):
new_host = Host(pattern)
new_host._connection = 'local'
new_host.set_variable("ansible_python_interpreter", sys.executable)
ungrouped = self.get_group("ungrouped")
if ungrouped is None:
self.add_group(Group('ungrouped'))
ungrouped = self.get_group('ungrouped')
self.get_group('all').add_child_group(ungrouped)
ungrouped.add_host(new_host)
return new_host
def is_file(self):
'''
Did inventory come from a file?
:returns: True if the inventory is file based, False otherwise
'''
pass
def src(self):
'''
What's the complete path to the inventory file?
:returns: Complete path to the inventory file. None if inventory is
not file-based
'''
pass
def basedir(self):
'''
What directory from which the inventory was read.
'''
return self._basedir

View File

@ -1,51 +0,0 @@
# (c) 2012-2014, 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Host:
def __init__(self, name):
self._name = name
self._connection = None
self._ipv4_address = ''
self._ipv6_address = ''
self._port = 22
self._vars = dict()
def __repr__(self):
return self.get_name()
def get_name(self):
return self._name
def get_groups(self):
return []
def set_variable(self, name, value):
''' sets a variable for this host '''
self._vars[name] = value
def get_vars(self):
''' returns all variables for this host '''
all_vars = self._vars.copy()
all_vars.update(dict(inventory_hostname=self._name))
return all_vars

View File

@ -21,7 +21,7 @@ __metaclass__ = type
from ansible.compat.six import iteritems, string_types
from ansible.errors import AnsibleParserError
from ansible.errors import AnsibleParserError,AnsibleError
from ansible.plugins import module_loader
from ansible.parsing.splitter import parse_kv, split_args
from ansible.template import Templar
@ -137,7 +137,16 @@ class ModuleArgsParser:
# than those which may be parsed/normalized next
final_args = dict()
if additional_args:
final_args.update(additional_args)
if isinstance(additional_args, string_types):
templar = Templar(loader=None)
if templar._contains_vars(additional_args):
final_args['_variable_params'] = additional_args
else:
raise AnsibleParserError("Complex args containing variables cannot use bare variables, and must use the full variable style ('{{var_name}}')")
elif isinstance(additional_args, dict):
final_args.update(additional_args)
else:
raise AnsibleParserError('Complex args must be a dictionary or variable string ("{{var}}").')
# how we normalize depends if we figured out what the module name is
# yet. If we have already figured it out, it's an 'old style' invocation.
@ -155,6 +164,13 @@ class ModuleArgsParser:
tmp_args = parse_kv(tmp_args)
args.update(tmp_args)
# only internal variables can start with an underscore, so
# we don't allow users to set them directy in arguments
if args and action not in ('command', 'shell', 'script', 'raw'):
for arg in args:
if arg.startswith('_ansible_'):
raise AnsibleError("invalid parameter specified for action '%s': '%s'" % (action, arg))
# finally, update the args we're going to return with the ones
# which were normalized above
if args:
@ -206,18 +222,21 @@ class ModuleArgsParser:
action = None
args = None
actions_allowing_raw = ('command', 'shell', 'script', 'raw')
if isinstance(thing, dict):
# form is like: copy: { src: 'a', dest: 'b' } ... common for structured (aka "complex") args
thing = thing.copy()
if 'module' in thing:
action = thing['module']
action, module_args = self._split_module_string(thing['module'])
args = thing.copy()
check_raw = action in actions_allowing_raw
args.update(parse_kv(module_args, check_raw=check_raw))
del args['module']
elif isinstance(thing, string_types):
# form is like: copy: src=a dest=b ... common shorthand throughout ansible
(action, args) = self._split_module_string(thing)
check_raw = action in ('command', 'shell', 'script', 'raw')
check_raw = action in actions_allowing_raw
args = parse_kv(args, check_raw=check_raw)
else:
@ -268,7 +287,7 @@ class ModuleArgsParser:
if item in module_loader or item == 'meta' or item == 'include':
# finding more than one module name is a problem
if action is not None:
raise AnsibleParserError("conflicting action statements", obj=self._task_ds)
raise AnsibleParserError("conflicting action statements (%s, %s)" % (action, item), obj=self._task_ds)
action = item
thing = value
action, args = self._normalize_parameters(value, action=action, additional_args=additional_args)
@ -282,7 +301,7 @@ class ModuleArgsParser:
obj=self._task_ds)
else:
raise AnsibleParserError("no action detected in task", obj=self._task_ds)
raise AnsibleParserError("no action detected in task. This often indicates a misspelled module name, or incorrect module path.", obj=self._task_ds)
elif args.get('_raw_params', '') != '' and action not in RAW_PARAM_MODULES:
templar = Templar(loader=None)
raw_params = args.pop('_raw_params')

View File

@ -65,8 +65,8 @@ def parse_kv(args, check_raw=False):
raise
raw_params = []
for x in vargs:
x = _decode_escapes(x)
for orig_x in vargs:
x = _decode_escapes(orig_x)
if "=" in x:
pos = 0
try:
@ -83,19 +83,14 @@ def parse_kv(args, check_raw=False):
k = x[:pos]
v = x[pos + 1:]
# only internal variables can start with an underscore, so
# we don't allow users to set them directy in arguments
if k.startswith('_'):
raise AnsibleError("invalid parameter specified: '%s'" % k)
# FIXME: make the retrieval of this list of shell/command
# options a function, so the list is centralized
if check_raw and k not in ('creates', 'removes', 'chdir', 'executable', 'warn'):
raw_params.append(x)
raw_params.append(orig_x)
else:
options[k.strip()] = unquote(v.strip())
else:
raw_params.append(x)
raw_params.append(orig_x)
# recombine the free-form params, if any were found, and assign
# them to a special option for use later by the shell/command module
@ -261,6 +256,6 @@ def split_args(args):
# If we're done and things are not at zero depth or we're still inside quotes,
# raise an error to indicate that the args were unbalanced
if print_depth or block_depth or comment_depth or inside_quotes:
raise Exception("error while splitting arguments, either an unbalanced jinja2 block or quotes")
raise AnsibleError("error while splitting arguments, either an unbalanced jinja2 block or quotes")
return params

View File

@ -20,6 +20,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
from ansible.errors import AnsibleParserError, AnsibleError
# Components that match a numeric or alphanumeric begin:end or begin:end:step
# range expression inside square brackets.
@ -162,6 +163,7 @@ patterns = {
$
'''.format(label=label), re.X|re.I|re.UNICODE
),
}
def parse_address(address, allow_ranges=False):
@ -183,8 +185,8 @@ def parse_address(address, allow_ranges=False):
# First, we extract the port number if one is specified.
port = None
for type in ['bracketed_hostport', 'hostport']:
m = patterns[type].match(address)
for matching in ['bracketed_hostport', 'hostport']:
m = patterns[matching].match(address)
if m:
(address, port) = m.groups()
port = int(port)
@ -194,22 +196,20 @@ def parse_address(address, allow_ranges=False):
# numeric ranges, or a hostname with alphanumeric ranges.
host = None
for type in ['ipv4', 'ipv6', 'hostname']:
m = patterns[type].match(address)
for matching in ['ipv4', 'ipv6', 'hostname']:
m = patterns[matching].match(address)
if m:
host = address
continue
# If it isn't any of the above, we don't understand it.
if not host:
return (None, None)
# If we get to this point, we know that any included ranges are valid. If
# the caller is prepared to handle them, all is well. Otherwise we treat
# it as a parse failure.
raise AnsibleError("Not a valid network hostname: %s" % address)
# If we get to this point, we know that any included ranges are valid.
# If the caller is prepared to handle them, all is well.
# Otherwise we treat it as a parse failure.
if not allow_ranges and '[' in host:
return (None, None)
raise AnsibleParserError("Detected range in host but was asked to ignore ranges")
return (host, port)

View File

@ -22,6 +22,7 @@ import shlex
import shutil
import sys
import tempfile
import random
from io import BytesIO
from subprocess import call
from ansible.errors import AnsibleError
@ -70,7 +71,7 @@ try:
except ImportError:
pass
from ansible.compat.six import PY3, byte2int
from ansible.compat.six import PY3
from ansible.utils.unicode import to_unicode, to_bytes
HAS_ANY_PBKDF2HMAC = HAS_PBKDF2 or HAS_PBKDF2HMAC
@ -220,21 +221,91 @@ class VaultEditor:
def __init__(self, password):
self.vault = VaultLib(password)
def _shred_file_custom(self, tmp_path):
""""Destroy a file, when shred (core-utils) is not available
Unix `shred' destroys files "so that they can be recovered only with great difficulty with
specialised hardware, if at all". It is based on the method from the paper
"Secure Deletion of Data from Magnetic and Solid-State Memory",
Proceedings of the Sixth USENIX Security Symposium (San Jose, California, July 22-25, 1996).
We do not go to that length to re-implement shred in Python; instead, overwriting with a block
of random data should suffice.
See https://github.com/ansible/ansible/pull/13700 .
"""
file_len = os.path.getsize(tmp_path)
if file_len > 0: # avoid work when file was empty
max_chunk_len = min(1024*1024*2, file_len)
passes = 3
with open(tmp_path, "wb") as fh:
for _ in range(passes):
fh.seek(0, 0)
# get a random chunk of data, each pass with other length
chunk_len = random.randint(max_chunk_len//2, max_chunk_len)
data = os.urandom(chunk_len)
for _ in range(0, file_len // chunk_len):
fh.write(data)
fh.write(data[:file_len % chunk_len])
assert(fh.tell() == file_len) # FIXME remove this assert once we have unittests to check its accuracy
os.fsync(fh)
def _shred_file(self, tmp_path):
"""Securely destroy a decrypted file
Note standard limitations of GNU shred apply (For flash, overwriting would have no effect
due to wear leveling; for other storage systems, the async kernel->filesystem->disk calls never
guarantee data hits the disk; etc). Furthermore, if your tmp dirs is on tmpfs (ramdisks),
it is a non-issue.
Nevertheless, some form of overwriting the data (instead of just removing the fs index entry) is
a good idea. If shred is not available (e.g. on windows, or no core-utils installed), fall back on
a custom shredding method.
"""
if not os.path.isfile(tmp_path):
# file is already gone
return
try:
r = call(['shred', tmp_path])
except OSError:
# shred is not available on this system, or some other error occured.
r = 1
if r != 0:
# we could not successfully execute unix shred; therefore, do custom shred.
self._shred_file_custom(tmp_path)
os.remove(tmp_path)
def _edit_file_helper(self, filename, existing_data=None, force_save=False):
# Create a tempfile
_, tmp_path = tempfile.mkstemp()
if existing_data:
self.write_data(existing_data, tmp_path)
self.write_data(existing_data, tmp_path, shred=False)
# drop the user into an editor on the tmp file
call(self._editor_shell_command(tmp_path))
try:
call(self._editor_shell_command(tmp_path))
except:
# whatever happens, destroy the decrypted file
self._shred_file(tmp_path)
raise
tmpdata = self.read_data(tmp_path)
# Do nothing if the content has not changed
if existing_data == tmpdata and not force_save:
os.remove(tmp_path)
self._shred_file(tmp_path)
return
# encrypt new data and write out to tmp
@ -258,7 +329,7 @@ class VaultEditor:
ciphertext = self.read_data(filename)
plaintext = self.vault.decrypt(ciphertext)
self.write_data(plaintext, output_file or filename)
self.write_data(plaintext, output_file or filename, shred=False)
def create_file(self, filename):
""" create a new encrypted file """
@ -323,13 +394,21 @@ class VaultEditor:
return data
def write_data(self, data, filename):
def write_data(self, data, filename, shred=True):
"""write data to given path
if shred==True, make sure that the original data is first shredded so
that is cannot be recovered
"""
bytes = to_bytes(data, errors='strict')
if filename == '-':
sys.stdout.write(bytes)
else:
if os.path.isfile(filename):
os.remove(filename)
if shred:
self._shred_file(filename)
else:
os.remove(filename)
with open(filename, "wb") as fh:
fh.write(bytes)
@ -338,6 +417,7 @@ class VaultEditor:
# overwrite dest with src
if os.path.isfile(dest):
prev = os.stat(dest)
# old file 'dest' was encrypted, no need to _shred_file
os.remove(dest)
shutil.move(src, dest)

View File

@ -22,7 +22,7 @@ __metaclass__ = type
import yaml
from ansible.compat.six import PY3
from ansible.parsing.yaml.objects import AnsibleUnicode
from ansible.parsing.yaml.objects import AnsibleUnicode, AnsibleSequence, AnsibleMapping
from ansible.vars.hostvars import HostVars
class AnsibleDumper(yaml.SafeDumper):
@ -50,3 +50,13 @@ AnsibleDumper.add_representer(
represent_hostvars,
)
AnsibleDumper.add_representer(
AnsibleSequence,
yaml.representer.SafeRepresenter.represent_list,
)
AnsibleDumper.add_representer(
AnsibleMapping,
yaml.representer.SafeRepresenter.represent_dict,
)

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