Compare commits

...

372 Commits

Author SHA1 Message Date
29f2f26278 New release v2.1.2.0-1 2016-09-29 10:01:35 -05:00
5f23d09e7c Removing mount from CHANGELOG due to reversion 2016-09-29 09:53:34 -05:00
b989004a0b Pick up core reversion of mount changes 2016-09-29 07:19:23 -07:00
fd256a79a1 Updating CHANGELOG for 2.1 2016-09-29 08:51:49 -05:00
c3257027b9 New release v2.1.2.0-0.5.rc5 2016-09-27 10:41:46 -05:00
4c845b4ef0 Update submodule ref for fix to mount backport 2016-09-27 07:28:05 -07:00
3d8dac2084 New release v2.1.2.0-0.4.rc4 2016-09-26 14:45:29 -05:00
2b7761cd37 Update core to pick up more mount changes 2016-09-26 12:10:21 -07:00
60a6c83abc Add ini_file whitespace change to the CHANGELOG 2016-09-26 07:05:13 -07:00
ee4ba3ceca Update submodule refs 2016-09-26 07:04:24 -07:00
66ffe199e5 Disable mount tests for now. Mount is buggy on too many platforms 2016-09-23 16:37:49 -07:00
5635670191 Update submodule refs for mount fixes 2016-09-23 14:53:31 -07:00
a4611ff509 Add tests for the mount module (#17718)
* Add tests for the mount module

* Switch from unmounted to absent...

the code for mounting always modifies fstab so we need to always modify
fstab to cleanup as well.

* Fix comments and copyright
2016-09-23 12:54:09 -07:00
cc71765d9e Use loop_control.loop_var directly
6eefc11c converted task.loop_control into an object, but while the other
callers were updated to use .loop_var instead of .get('loop_var'), this
site was overlooked.

This can be reproduced by including with loop_control a file that does
set_fact; a simple regression test along these lines is included.

(cherry picked from commit 950cc26aab)
2016-09-23 10:39:06 -05:00
b5b8a756fc fixes issue with net_template failing unless src argument provided. (#17726)
This fixes an issue where the net_template action will fail if a non
required argument (src) is not provided.

fixes ansible/ansible-modules-core#4978
2016-09-23 09:24:30 -04:00
4c9ed1f4fb bump extras submodule ref for win_chocolatey fix 2016-09-22 20:09:06 -07:00
e917a0bd7a Fix for cherry-pick e8dddc3679,
The cherry-pick allows module parameters to convert int to float, fixing
bugs where a float type is required.  However, it used types from the
six library for python3 compatibility as well.  Since we don't have
six in 2.1.0 this was buggy.  Merging the unittests for the problem
here: adc158a499 made this buggy behaviour
apparent.
2016-09-21 21:45:03 -07:00
9de21e1eaa Update submodule refs 2016-09-21 20:41:52 -07:00
eaba2152f3 Remove _load_hosts() from Play initialization as it's no longer needed and it breaks using extra_vars defining a list for hosts (#17699)
Thanks to @jimi-c for the solution

Fixes #16583
2016-09-21 20:41:13 -07:00
adc158a499 Add a test for int/float parameter type checking (#16741)
A parameter of type int should accept int and string, but not float.
A parameter of type float should accept float, int, and string.

Also reset the arguments in another test so that it runs cleanly.  This
agrees with what all the other tests are doing.
2016-09-21 20:39:02 -07:00
01439aafaf Create a raw lookup for hostvars that does not template the data
When using hostvars to get extra connection-specific vars for connection
plugins, use this raw lookup to avoid prematurely templating all of the
hostvar data (triggering unnecessary lookups).

Fixes #17024

(cherry picked from commit ac5ddf4aa092e12f9e1c85c6b74aa30b7ef0a382)
2016-09-21 13:16:58 -05:00
b7168d2ac8 Remove Travis config/badge and add Shippable. (#17680) 2016-09-20 17:35:57 -07:00
c004ae578d Take ITERATING_ALWAYS into account when setting failed state in PlayIterator
Fixes #15963

(cherry picked from commit 4dc2bf4815)
2016-09-20 11:31:23 -05:00
efe5bb122e Rework the way params are assigned to TaskIncludes when they're dynamic
Copying the TaskInclude task (which is the parent) before loading the blocks
makes the code much more simple and clean, and fixes a bug introduced during
the performance improvement changes (and specifically the change which moved
things to a single-parent model).

Fixes #17064

(cherry picked from commit f4237b2151)
2016-09-19 14:29:08 -05:00
27c621950c cloudstack: fix has_changed dict values comparsion (#17632)
In some rare situations, the CloudStack API returns string for numbers
when we expected int.

With this fix, we ensure we compare the types expected.
2016-09-19 14:08:21 +02:00
7bfa36eb8b Pass the absolute path to dirname when assigning basedir (#17457)
* Pass the absolute path to dirname when assigning basedir

If no path is specified when calling the playbook, os.path.dirname(playbook_path) returns ''
This will cause failure when creating the retry file.

Fixes #17456

* Updated to use os.pathdirname(os.path.abspath())
2016-09-16 08:20:47 -07:00
fb71a9dfd9 New release v2.1.2.0-0.3.rc3 2016-09-14 16:51:18 -05:00
Jay
d86b2ec225 Fix issue with proxy setting leaving temp files (#17073)
When using the no_proxy option, the temp ca files that are created for
setting up an ssl context were not being deleted. 

This small patch fixes that issue by removing the temp file.
2016-09-13 16:15:48 -04:00
7ea342d8ec bump core submodule ref 2016-09-13 10:57:53 -04:00
6ba8926889 Update submodule refs 2016-09-12 09:02:44 -07:00
2e06f0b427 add win_user regression tests
Ensure https://github.com/ansible/ansible-modules-core/issues/4369 doesn't occur again, also adds tests for password set when expired.

(cherry picked from commit 69880f1640)
2016-09-11 20:47:40 -07:00
a900a0b4e3 bump core submodule ref 2016-09-11 20:47:31 -07:00
c536ca399a Fix galaxy import command for 2.1 (#17417)
The g_connect decorator is required on create_import_task or else
self.baseurl is None. This is fixed in devel already.
2016-09-09 18:04:09 -04:00
2587d2aaf9 Add partially backwards compatible version of _fixup_perms. (#17427)
Also added a deprecation notice for _fixup_perms.

Resolves issue #17352 (assumes custom actions use recursive=False).

(cherry picked from commit 94a0d2afb4)
2016-09-06 16:56:47 -07:00
e83840c3fd New release v2.1.2.0-0.2.rc2 2016-08-31 12:57:52 -05:00
e8dddc3679 For module parameter checking, allow conversion of int to float (#17325)
Fixes https://github.com/ansible/ansible-modules-core/issues/4084
2016-08-31 09:01:03 -07:00
da3fd2d588 Several fixes for includes
* when including statically, make sure that all parents were also included
  statically (issue #16990)
* properly resolve nested static include paths
* print a message when a file is statically included

Fixes #16990

(cherry picked from commit 1c7e0c73c9)
2016-08-31 00:09:33 -05:00
c9b212c5bd task_result _check_key should handle empty results (#16766)
When a task result has an empty results list, the
list should be ignored when determining the results
of `_check_key`. Here the empty list is treated the
same as a non-existent list.

This fixes a bug that manifests itself with squashed
items - namely the task result contains the correct
value for the key, but an empty results list. The
empty results list was treated as zero failures
when deciding which handler to call - so the task
show as a success in the output, but is deemed to
have failed when deciding whether to continue.

This also demonstrates a mismatch between task
result processing and play iteration.

A test is also added for this case, but it would not
have caught the bug - because the bug is really in
the display, and not the success/failure of the
task (visually the test is more accurate).

Fixes ansible/ansible-modules-core#4214
(cherry picked from commit eb2a3a91a8)
2016-08-29 08:40:38 -07:00
478283f571 Do not convert Nonetype to "None" (#17261)
If someone use a task with a empty name like this:

  - name:
    command: true

This will result in displaying 'None' as a task name instead of
'command'.
2016-08-26 09:53:29 -07:00
5035b8a8bf Properly template task names in free strategy
* Fixes #16295
2016-08-26 09:53:20 -07:00
559fcbe531 Add fetch fix to changelog 2016-08-25 21:54:10 -07:00
0cce86cac9 Fix fetch idempotence (#17255)
Fetch always follows symlinks when downloading so it needs to always
follow symlinks when getting the checksum of the file as well.
2016-08-25 21:53:20 -07:00
f80c981ef6 Clean up PlaybookExecutor logic for batches and errors
The calculation for max_fail_percentage was moved into the linear
strategy a while back, and works better there in the stategy layer
rather than at the PBE layer. This patch removes it from the PBE layer
and tweaks the logic controlling whether or not the next batch is run.

Fixes #15954

(cherry picked from commit 890e096b2b)
2016-08-25 12:28:08 -05:00
fc3efdb057 Changelog entry for https://github.com/ansible/ansible/pull/17225 2016-08-24 11:04:10 -07:00
7d6df50e0e The former match for a section header included hosts that began with a range. (#17225)
Checking that the line ends with "]" narrows that window somewhat,

Fixes #15331
2016-08-24 10:59:32 -07:00
b2fe1b39df New release v2.1.2.0-0.1.rc1 2016-08-22 16:15:56 -05:00
719c73afa2 Fix tmpfile misspelled as tmplfile (#17183) 2016-08-22 11:32:40 -07:00
bf929ac532 Backport of f722d41 to the stable-2.1 branch
Related to #15915
2016-08-22 10:21:45 -05:00
9478e41394 bump extras submodule ref for another win_chocolatey fix 2016-08-17 16:41:00 -07:00
29dd1635fe bump submodule refs 2016-08-17 15:51:09 -07:00
ea7a038f6f Don't use an unset playbook basedir when searching for hostgroup vars
The flag new_pb_basedir is not being utilized in Inventory._get_hostgroup_vars,
leading to the situation where an inventory with no playbook basedir set will
read host/group vars from the $CWD, regardless of the inventory and/or playbook
relative location. This patch corrects that by not using the playbook basedir
if it is unset (None).

This patch also corrects a bug in which the VariableManager would accumulate
host/group vars files, which could lead to incorrect vars files being used when
playbooks are run from different directories containing their own group/host vars
directories.

Fixes #16953

(cherry picked from commit b617d62203)
2016-08-17 16:29:13 -05:00
cf0eb42ad5 fixed tests to accoutn for new parameter
(cherry picked from commit b1410fa278)
2016-08-17 10:58:49 -04:00
77fe1ac7af Catch a missing filename in include_vars
Fixes ansible/ansible-modules-core#4445
2016-08-17 09:30:14 -05:00
8312df1512 Relicense netcfg.py to BSD (#17110)
As with #17025. The caveat regarding machilde's (now obsoleted) commit has been addressed, so netcfg.py is able to be easily relicensed.
2016-08-16 16:35:07 -04:00
2de8d2ece7 make parsed param private and explicit (#17104)
* make parsed param private and explicit

* fixed missed parsed

(cherry picked from commit 5fe9d3c3d5)
2016-08-16 11:59:47 -04:00
d35377dac7 Fix async logic when parsing fails (#17091)
We want to NOT consider the async task as failed if the result is
not parsed, which was the intent of:

  https://github.com/ansible/ansible/pull/16458

However, the logic doesn't actually do that because we default
the 'parsed' value to True. It should default to False so that
we continue waiting, as intended.
(cherry picked from commit bf8c871801)
2016-08-15 13:37:26 -04:00
616a51ac00 updated submodule refs 2016-08-12 10:48:24 -04:00
3749d44cd5 Increase local version for unofficial rpms (#17045) 2016-08-11 17:36:37 -07:00
1601e24593 bump core submodule ref to pick up reverted unarchive change 2016-08-11 12:29:30 -07:00
69d66727ca update core submodule ref 2016-08-11 11:29:01 -07:00
6fe13bbb47 Relicense low-hanging fruit to BSD (#17025) 2016-08-10 10:49:27 -07:00
e71cce7776 Add sync: poll fix to changelog 2016-08-08 11:16:59 -07:00
f695dd6892 Fix from @jimi-c to use task_vars when polling async tasks. (#17003)
This is needed so that async_status can use ansible_python_interpreter
for the host being contacted.

Fixes #14101
(cherry picked from commit 38ccd11cce)
2016-08-08 11:07:32 -07:00
9255a618e3 set cwd to task's basedir (#16805)
* switch cwd to basedir of task

This restores previous behaviour in pre 2.0 and allows for 'local type' plugins
and actions to have a more predictable relative path.

fixes #14489

* removed FIXME since prev commit 'fixes' this

* fix tests, now they need a loader (thanks jimi!)

(cherry picked from commit e2f17f8d9b)
2016-08-08 13:14:00 -04:00
cf9ef724e9 Use file list, not recursion, in _fixup_perms. (#16924)
Run setfacl/chown/chmod on each temp dir and file.

This fixes temp file permissions handling on platforms such as FreeBSD
which always return success when using find -exec. This is done by
eliminating the use of find when setting up temp files and directories.

(cherry picked from commit 72cca01cd4)
2016-08-05 18:50:31 -07:00
f956ff9619 Tweak the way the debug strategy imports the linear strategy parent
Due to the way we load plugins, internally to Python there can be issues when
the debug strategy is loaded after the linear strategy. To work around this,
we're changing the import line for the linear strategy to avoid the problem.

Related to #16825

(cherry picked from commit 1714279b5e)
2016-08-05 10:08:38 -05:00
0000b76a0a YAML treats some unquoted strings as booleans. For instance, (#16961)
uri:
    follow_redirects: no

Will lead yaml to set follow_redirects=False.  This is problematic when
the module parameter is not a boolean value but a string.  For instance:

  follow_redirects = dict(required=False, default='safe', choices=['all', 'safe', 'none', 'yes', 'no']),

Our parameter validation code ends up getting follow_redirects="False"
instead of "no".  The 100% fix is for the user to quote their strings in
playbooks like:
  uri:
    follow_redirects: "no"

But we can fix quite a few common cases by trying to switch "False" back
into the string that it was specified as.  We only do this if there is
only one correct choices value that could have been specified.  In the
follow_redirects example, a value of "True" only maps back to "yes" and
a value of "False" only maps back to "no" so we can do this.  If choices
also contained "on" and "off" then we couldn't map back safely and would
need to force the module author to change the module to handle this
case.

Fixes parts of the following PRs:

* https://github.com/ansible/ansible-modules-core/pull/4220
* https://github.com/ansible/ansible-modules-extras/pull/2593
(cherry picked from commit 6db6edfc4f)
2016-08-05 06:51:18 -07:00
7bd9128848 * Fix race in creating temp directories pre-fork (#16965)
* These can still race when multiple ansible processes are created at
    the same time.
* Reverse order of expanduser and expandvars in unfrakpath(). So that
  tildes in environment variables will be handled.
(cherry picked from commit 1ecf51d87e)
2016-08-04 16:43:22 -07:00
9f645cdbdb Add the PID of the Ansible process to local_tmp directory. (#16589)
This aids in associating a leftover cachedir with a previous run of
Ansible.  Came about because of #16489
(cherry picked from commit 3f4027f7b4)
2016-08-04 16:40:40 -07:00
baaa1d3013 fix for unspecified retries on until + test (#16963)
fixes #16907
(cherry picked from commit 746ea64d30)
2016-08-04 19:31:49 -04:00
e464237894 Fix for issue @synchronize doesn't substitute variables properly #16347 (#16349)
* Fix for issue @synchronize doesn't substitute variables properly #16347
2016-08-04 11:30:43 -07:00
08ae111757 raise AnsibleError in hashi_vault lookup plugin when hvac module is not installed (#16859)
(cherry picked from commit 14901b65d9)
2016-08-04 10:06:42 -07:00
df16e37ad7 Implicit localhost ondemand (#16900)
* Revert "There can be only one localhost"

This reverts commit 5f1bbb4fcd.
this broke several usages of localhost, see #16882, #16898 and #16886

* ensure there is only 1 localhost

fixes #16886, #16882 and #16898

- make sure localhost exists before returning it
- optimzed host caching
- ensure we always return a host object

(cherry picked from commit f7570f1dc4)
2016-08-02 10:39:37 -04:00
db4e661fef (re)allow ansible_python_interpreter to contain more than 1 arg (#16247) 2016-07-28 13:55:02 -07:00
167a12003d Fix quoting of args for old-style modules
This removes the extra layer of quotes around values in the 'args' file.
These quotes were there before the pipes.quote() call was added, but
were not removed, resulting in too much quoting.
2016-07-28 13:55:02 -07:00
3b6b4f6ce4 Unprivileged become on HP-UX/UNIX (Fixes #16249) (#16275)
Problem: When setting the file permissions on the remote server for
unprivileged users ansible expects that a chown will fail for unprivileged
users. For some systems (e.g. HP-UX) this is not the case.

Solution: Change the order how ansible sets the remote permissions.
* If the remote_user sudo's to an unprivileged user then we attempt to
  grant the unprivileged user access via file system acls.
* If granting file system acls fails we try to change the owner of the
  file with chown which only works in case the remote_user is privileged
  or the remote systems allows chown calls by unprivileged users (e.g.
  HP-UX)
* If the chown fails we can set the file to be world readable so that
  the second unprivileged user can read the file. Since this could allow
  other users to get access to private information we only do this
  ansible is configured with "allow_world_readable_tmpfiles" in the
  ansible.cfg
2016-07-28 13:55:02 -07:00
780c363482 New release v2.1.1.0-1 2016-07-28 10:32:34 -07:00
32b6114bef Updating CHANGELOG for 2.1.1 2016-07-28 10:17:27 -07:00
02389d6c51 New release v2.1.1.0-0.5.rc5 2016-07-24 18:06:49 -05:00
044547034d Make returning results after loading vars optional
Reinstates some functionality removed by commit 0ba9a6a but
makes the expensive operation optional (and default to off)

(cherry picked from commit 7dc09adaf4)
2016-07-24 11:51:57 -05:00
6ca9b406ec New release v2.1.1.0-0.4.rc4 2016-07-23 16:59:25 -04:00
3ec6e95e47 bump core submodule ref 2016-07-22 10:00:02 -07:00
53c348c89d Only show the traceback for importing cryptography when in Ansible Debug. (#16795) 2016-07-22 05:41:45 -07:00
3e964dbfae Update submodules 2016-07-21 11:11:44 -07:00
505a1de605 Fix exceptions thrown from cryptography import (#16723)
A simple import of cryptography can throw several types of errors. For example,
if `setuptools` is less than cryptography's minimum requirement of 11.3, then
this import of cryptography will throw a VersionConflict here. An earlier case
threw a DistributionNotFound exception.

An optional dependency should not stop ansible. If the error is more than
an ImportError, log a warning, so that errors can be fixed in ansible or
elsewhere.
2016-07-21 05:30:42 -07:00
ed959d72f1 Fix bug where getting role vars does not follow the dep chain
This bug was introduced in 3ced6d3, where getting vars from a role
did not follow the dep chain. This was originally hidden by the fact
that we got vars twice (from the block and from the roles directly).

Fixes #16729

(cherry picked from commit d8a3feb976)
2016-07-19 17:02:01 -04:00
35da6ba9d1 New release v2.1.1.0-0.3.rc3 2016-07-18 16:12:07 -04:00
cf6f46683a Update submodule ref 2016-07-15 06:48:15 -07:00
b7479a1dc6 Add a function to check for killed processes in all strategies (#16684)
* Add a function to check for killed processes so that if any
threads are sigkilled or sigtermed, the entire playbook execution is aborted.

(cherry picked from commit 238c6461f6)
2016-07-14 17:31:09 -04:00
fb6e58e888 Update submodule refs 2016-07-14 15:20:54 -04:00
87dcec9fc5 Update submodule refs 2016-07-13 10:34:52 -07:00
137385059c Fixing type in 293723f (mock_handler -> mock_handler_task) 2016-07-12 16:59:30 -05:00
293723f4f6 Fix unit test for base strategy in regards to handler changes 2016-07-12 16:49:23 -05:00
bac0028350 Fix unreachable host/any_errors_fatal bug in linear strategy
2e003adb added the ability for tasks using any_errors_fatal to fail
when there were unreachable hosts. However that patch used the running
unreachable hosts data rather than the results from the current task,
which causes failures when any run_once or BYPASS_HOST_LOOP task is hit
after an unreachable host causes a failure. This patch corrects that by
using the current set of results to determine if any hosts were
unreachable during the last task only.

Fixes ansible/ansible-modules-core#4160

(cherry picked from commit 245ce9461d)
2016-07-12 03:06:38 -05:00
ad3128e661 Revert "tkuratomi bugfix for BOMs in powershell modules"
This reverts commit 2398ca917f.
2016-07-11 13:23:17 -07:00
e7690b0dd1 Revert "update action unit test for powershell shebang behavior"
This reverts commit 637f6f23e1.
2016-07-11 13:23:04 -07:00
637f6f23e1 update action unit test for powershell shebang behavior
(cherry picked from commit de549ad675)
2016-07-11 12:38:53 -07:00
2398ca917f tkuratomi bugfix for BOMs in powershell modules
fixes #15998

(cherry picked from commit 31e963dd2a)
2016-07-11 11:52:15 -07:00
628a67563f Fix typo on -K/--ask-become-pass option in 'ansible' man page (#16667) 2016-07-11 00:18:02 +02:00
909d629c73 Upgrade to azure 2.0.0rc5 and add locations parameter.
(cherry picked from commit 7964a2fca3)
2016-07-09 01:59:23 -04:00
=
c6faf106f8 fix win_msi tests by setting wait: true on all win_msi tasks.
I suspect this problem was masked previously as older versions of pywinrm
where significantly slower, allowing more time for the windows installer service to complete.
2016-07-08 13:36:06 -07:00
eff49c968a New release v2.1.1.0-0.2.rc2 2016-07-07 15:25:28 -05:00
4b86191a24 Updating integration tests for async testing
(cherry picked from commit cd4412016a)
2016-07-07 14:18:26 -05:00
1f74df5c91 Updating submodule refs 2016-07-07 13:50:47 -05:00
9eb32357de bump extras submodule ref 2016-07-07 10:58:40 -07:00
56737da097 Updating submodule refs 2016-07-07 11:32:12 -05:00
11eefdc486 correctly set become value in console
fixes #16614

(cherry picked from commit 906dc99c64)
2016-07-07 10:43:19 -04:00
5b9f795140 cloudstack: handle unicode API results in has_changed (#16601)
* cloudstack: handle unicode API results in has_changed

* cloudstack: add more case sensitve keys
2016-07-07 11:01:30 +02:00
8a2f54bcee Force remote_user to be the currently user for connection=local
Fixes ansible-modules-core#4092

(cherry picked from commit 9248cde239)
2016-07-06 23:10:47 -05:00
4dfc0edbc1 Update submodule for unarchive fixes 2016-07-06 16:24:04 -07:00
ef5a504b8b Update submodule refs 2016-07-06 14:43:29 -07:00
68232d10cf Fix unit tests for handler stuff in base strategy test 2016-07-06 16:40:13 -05:00
e5235e151d Don't treat parsing problems as async task timeout (#16458)
* Don't treat parsing problems as async task timeout

If there is a problem reading/writing the status file that manifests as
not being able to parse the data, that doesn't mean the task timed out,
it means there was what was likely a tempoarary problem. Move on and
keep polling for success. The only things that should cause the async
status to not be parseable are bugs in the async_runner.

* Add comment explaining not bailing out of loop

* Return different error when result is unparseable

* Remove extraneous else
2016-07-06 14:35:18 -07:00
feebe73ede Fix the way handlers are compiled and found/notified
* Instead of rebuilding the handler list all over the place, we now
  compile the handlers at the point the play is post-validated so that
  the view of the play in the PlayIterator contains the definitive list
* Assign the dep_chain to the handlers as they're compiling, just as we
  do for regular tasks

(cherry picked from commit 930d090507)
2016-07-06 15:46:43 -05:00
ff601f4161 Port azure_rm_common.py to py3 syntax (#15880)
Since the rest of the file already use a non 2.4 syntax
(such as format), I didn't bother using the 2.4 syntax for
exceptions.
(cherry picked from commit eb52dc9af0)
2016-07-05 16:59:34 -07:00
6b84306f70 Fix authentication via params and env vars. Update guide to RC5.
(cherry picked from commit c71a939b08)
2016-07-05 16:53:23 -07:00
5e04dcf026 Update and pin to azure-2.0.0rc5
(cherry picked from commit a076612b63)
2016-07-05 16:52:39 -07:00
16c3fc5cec prevent spurious pywinrm arg warnings for non-pywinrm connection args
(cherry picked from commit c5e0d3d17b)
2016-07-05 16:05:42 -07:00
75a3526680 Ignore limits and restrictions when parsing inventory.
We want to update host vars for all hosts (even those that might
have failed), and the in case of a refresh_inventory, the code has
a stale restrictions list at this point anyway.

(cherry picked from commit 0c4effb8a6)
2016-07-05 16:02:13 -05:00
5982a0632f Fix git shallow update (#16224)
* add git shallow fetch test

covers https://github.com/ansible/ansible-modules-core/issues/3782

updating a repo with depth=1 fails silently if version==HEAD

* raise git version support supporting depth to 1.9.1

(cherry picked from commit d0ccedc617)
2016-07-05 17:00:20 -04:00
18ea3f1178 update core submodule reference 2016-07-05 16:20:43 -04:00
fbf8f5bccb Determine failed hosts with _check_failed_state() (#16566)
(cherry picked from commit 8bff6154a6)
2016-07-05 10:08:48 -05:00
6bcdb575e8 Fix the way pull localhosts out of inventory for delegate_to
This patch corrects the way we look in the inventory hosts list for
implicit localhost entries when localhost aliases are used.

Fixes #16568

(cherry picked from commit 83e4a4048b)
2016-07-04 11:26:11 -05:00
8659f255df Fix test_async. (#16552)
* Conditionally run test_async in docker containers.
* Revise test_async test.

(cherry picked from commit 4e369a31db)
2016-07-01 18:01:16 -07:00
6755e9c848 Run test_async after test_connection. 2016-07-01 16:06:13 -07:00
75fa80f73c Parse async response in async action. (#16534)
* Parse async response in async action.
* Add async test for non-JSON data before module output.
* Fix existing async unit test.

Resolves #16156

(cherry picked from commit 292785ff2b)
2016-07-01 15:24:41 -07:00
a598f26006 Update shippable shared dir for COPY_SOURCE. (#16511)
Tests now use '/shared' instead of '/tmp/shared-dir' when using
COPY_SOURCE. This avoids issues with containers purging '/tmp'.

(cherry picked from commit fbfadc47c7)
2016-06-29 15:11:32 -07:00
3fd94769dc Save and restore the loader's basedir when loading playbooks
Fixes #15930
2016-06-29 12:52:52 -04:00
9e5fc8f08f In some cornercases, sys.executable is empty. Work around that by choosing a sensible default path to python and emitting a warning (#16487)
Fixes #13585

Fix grammar of warning method
2016-06-29 08:51:45 -07:00
e826d3c7d7 Merge pull request #16496 from privateip/stable-2.1
fixes minor issue with expanding blocks in netcfg
2016-06-29 06:48:33 -07:00
b70d83fe1d fixes minor issue with expanding blocks in netcfg
This fixes a minor bug where blocks in netcfg where not being expanded
when replace=block was specified.
2016-06-29 06:30:00 -07:00
3489dcfd94 Update shippable notifications to match devel. 2016-06-28 16:32:07 -07:00
ba97d6069a Merge pull request #16483 from privateip/bugfix-netcfg
fixes dependency on OrderedDict in netcfg
2016-06-28 16:05:49 -07:00
c80d8b97e8 fixes dependency on OrderedDict in netcfg
This removes the need for OrderedDict which will make the update
capable with Python version < 2.7

fixes ansible/ansible-modules-core#3979
2016-06-28 15:36:41 -07:00
dd15a15edc Don't modify the original task ds when preprocessing data
In the case of using YAML anchors/aliases, YAML actually uses references
to the duplicated object so any modifications to the original impacts
later uses of the object.

Fixes #13575

(cherry picked from commit af249b83e6)
2016-06-28 03:09:36 -05:00
57eb60757c Lookup password omit salt (#16361)
* Lookup unencrypted password must not include salt
* Integration test lookup: remove previous directory
* Test that lookup password doesn't return salt
* Lookup password: test behavior with empty encrypt parameter

Closes #16189

(cherry picked from commit b361bf90d7)
2016-06-27 10:47:33 -07:00
25c5314f2e Update submodule refs 2016-06-24 09:02:38 -07:00
084a3e13b0 Fix off-by-one error with retries
Fixes #16408

(cherry picked from commit 948682dbe2)
2016-06-23 18:08:00 -05:00
eeba5e2c3e Remove unnecessary role initialization and lookup stuff
* Remove unnecessary copying of values from parents to role deps, as
  this can cause problems when roles have multiple parents (or the same
  parents with different params speficied through deps)
* Since we're already checking the dep chain in the block for role
  things (which every task in a role should have), it is not necessary
  to check the role directly in case it improperly grabs something

Fixes #14438

(cherry picked from commit 3e4755f7e4)
2016-06-23 17:02:14 -05:00
e877ea7f2b Update intro_configuration.rst (#16416)
Fix callback plugin path (per lib/ansible/constants.py)
2016-06-23 11:07:10 -04:00
7da2265e10 Properly wrap objects using json default encoder
Our custom encoder for the to_json filter was simply returning the
object if it was not a HostVars object, leading in some cases to a
TypeError when the data contained an undefined variable. This lead
to an odd error message being propagated up, so we now properly catch
this as an undefined variable error.

Fixes #15610

(cherry picked from commit c24c0f5f6b)
2016-06-23 09:20:35 -05:00
cb520bd86a Fixed bug in find_mount_point function
The find_mount_point function does not resolve the mount point of paths with a soft-link correctly and returns the wrong mount-point.

I have mounted an NFS filesystem on /nfs-mount. This directory contains a directory called "directory". I also created a soft-link to this last directory: /soft-link-to-directory -> /nfs-mount/directory. I created the following task to copy a file into /soft-link-to-directory:

    - name: copy file to nfs-mount
      copy:
        src: "file"
        dest: "/soft-link-to-directory/file"

This throws an exception:

invalid selinux context: [Errno 95] Operation not supported

This is caused by the find_mount_point function to return '/' as the mount point for '/soft-link-to-directory/file'. This should have been /nfs-mount. Because the find_mount_point returns the wrong mount-point, the is_special_selinux_path function does not recognise the file is on an NFS mount and tries to set the default SELinux context (system_u:object_r:default_t:s0), which fails. The context should have been: system_u:object_r:nfs_t:s0

Full Ansible output:

TASK [copy file to nfs-mount] **************************************************
fatal: [hostname]: FAILED! => {"changed": false, "checksum": "f34b60930a5d6d689cf49a4c16bd7f9806be608c", "cur_context": ["system_u", "object_r", "nfs_t", "s0"], "failed": true, "gid": 24170, "group": "foundation", "input_was": ["system_u", "object_r", "default_t", "s0"], "mode": "0644", "msg": "invalid selinux context: [Errno 95] Operation not supported", "new_context": ["system_u", "object_r", "default_t", "s0"], "owner": "root", "path": "/soft-link-to-directory/.ansible_tmpWCT6Z4file", "secontext": "system_u:object_r:nfs_t:s0", "size": 37, "state": "file", "uid": 0}
(cherry picked from commit 23349911f1)
2016-06-22 22:39:30 -05:00
62b3909ef5 bump extras submodule ref 2016-06-22 17:20:30 -07:00
5f1d50d53d bump core submodule ref 2016-06-22 11:52:53 -07:00
a7199abb74 Take previously failed/unreachable hosts into account when checking the batch
Again, as we're carrying failed/unreachable hosts forward from play to play via
internal structures, we need to remember which ones had previously failed so that
unrelated host failures don't inflate the numbers for a given serial batch in the
PlaybookExecutor causing a premature exit.

Fixes #16364

(cherry picked from commit 4c1601e9f2)
2016-06-22 08:33:08 -05:00
a7f93be2b6 Use find -exec + so that failures will be visible in find's exit code (#16389)
Fixes #16385
2016-06-22 05:44:35 -07:00
05536b3be5 bump extras submodule ref 2016-06-20 13:04:01 -07:00
4d4bbcbb33 New release v2.1.1.0-0.1.rc1 2016-06-17 14:33:25 -05:00
bb43d4d00b fix win_setup integration test to match fact name
(cherry picked from commit a45e842ba1c030d9022bf2e23ca7ad5002a2019c)
2016-06-17 12:29:02 -07:00
d6fef6f9a6 Update ansible-modules-core ref
Includes cherry-pick of [ansible-modules-core#3347](https://github.com/ansible/ansible-modules-core/pull/3347)
2016-06-17 15:15:22 -04:00
7083d0b5ea Add setfacl on BSD fix to CHANGELOG 2016-06-17 11:51:33 -07:00
cd348abaa6 Update core submodule ref for wait_for fix 2016-06-17 10:17:21 -07:00
9d91d2c6b8 Use find instead of -R for recursive setfacl (#16340)
(-R is not present on Solaris or freebsd setfacl)

Fixes #16322
2016-06-17 09:40:53 -07:00
552172b141 Allowing changed colour to be set properly from ansible.cfg (#16338)
(cherry picked from commit 2b0d0d0323)
2016-06-17 12:28:05 -04:00
55f2829e0c bump core submodule ref 2016-06-16 13:16:01 -07:00
8ec11e9d6c bump extras submodule ref 2016-06-16 12:38:12 -07:00
=
5d03a65ee8 added further tests for win_regedit to cover the changes made
under https://github.com/ansible/ansible-modules-extras/pull/2436
2016-06-16 12:37:14 -07:00
8223b83758 bump submodule refs 2016-06-15 14:50:02 -07:00
877347ad20 allow spaces before the filter string (#16312)
(cherry picked from commit 5a2b34e159)
2016-06-15 14:17:56 -04:00
8fc5a6a6f5 Update core submodule ref 2016-06-15 10:34:34 -07:00
9a00b2d4af Check that required arguments src and dest are present.
Fixes #16301
2016-06-15 09:58:25 -07:00
d0cf725ef8 updated submodule refs 2016-06-15 11:26:32 -04:00
d88264ee3b add executable if none exists when become
this is needed as become adds commands that require shell

(cherry picked from commit 4acac55d23)
2016-06-15 11:26:32 -04:00
969c6a4443 zone plugin updates for newer api (#16289)
Fixes errors on import.
2016-06-15 07:39:33 -07:00
7248314233 Make sure we don't end up with an empty PYTHONPATH (#16240)
When the PYTHONPATH is an empty string python will treat it as though
the cwd is in the PYTHONPATH.  This can be undesirable.  So make sure we
delete PYTHONPATH from the environment altgether in this case.

Fixes #16195
2016-06-15 07:07:11 -07:00
73a3a5839b Force an include to be a static task if no vars or loops are being used. (#16192)
Fixes #15735
2016-06-15 00:54:09 -05:00
43d1ea0cfc Track notified handlers by object rather than simply their name
Due to the fact that roles may be instantiated with different sets of
params (multiple inclusions of the same role or via role dependencies),
simply tracking notified handlers by name does not work. This patch
changes the way we track handler notifications by using the handler
object itself instead of just the name, allowing for multiple internal
instances. Normally this would be bad, but we also modify the way we
search for handlers by first looking at the notifying tasks dependency
chain (ensuring that roles find their own handlers first) and then at
the main list of handlers, using the first match it finds.

This patch also modifies the way we setup the internal list of handlers,
which should allow us to correctly identify if a notified handler exists
more easily.

Fixes #15084
2016-06-14 22:34:51 -05:00
f6727aff39 Add further commentary about synchronize process_remote. (#16292)
Addresses #16284
(cherry picked from commit dc9b53a6aa)
2016-06-14 22:30:14 -05:00
304493e795 Fix synchronize module incorrect remote host processing. (#15993)
Fixes  #15539
(cherry picked from commit 9753cac530)
2016-06-14 22:29:59 -05:00
75beca22a5 Be more lenient of symlinked /bin/sh inside the chroot (#16239)
Symlinks inside of the chroot were failng because we weren't able to
    determine if they were pointing to a real file or not.  We could write
    some complicated code to walk the symlink path taking into account where
    the root of the tree is but that could be fragile.  Since this is just
    a sanity check, instead we just assume that the chroot is fine if we
    find that /bin/sh in the chroot is a symlink.  Can revisit if it turns
    out that many chroots have a /bin/sh that's a broken symlink.

    Fixes #16097
2016-06-14 12:05:31 -07:00
fc8d8b5cde bump core submodule ref for windows setup fix 2016-06-14 11:35:57 -07:00
97a9eaa299 ignore closing file error when file not open
fixes #16268

(cherry picked from commit 07846f7bec)
2016-06-14 09:34:57 -04:00
6e2651ce07 fixes using ssh keyfile with junos network module
The junos network module will now properly use the ssh key file if its
passed from the playbook to authenticate to the remote device.  Prior
to this commit, the ssh keyfile was ignored.
2016-06-13 22:50:49 -04:00
9cbb1a196b Workaround bad interaction with .pth files. (#16238)
When setuptools installs a python module (as is done via python setup.py
install)  It puts the module into a subdirectory of site-packages and
then creates an entry in easy-install.pth to load that directory.  This
makes it difficult for Ansiballz to function correctly as the .pth file
overrides the sys.path that the wrapper constructs.  Using
sitecustomize.py fixes this because sitecustomize overrides the
directories handled in .pth files.

Fixes #16187
2016-06-13 08:00:59 -07:00
4c59c0b31d Fix docker unit test. Not deprecated in 2.1. 2016-06-11 09:14:54 -07:00
467ef193fd call base _connect() from winrm._connect()
without it, we don't get the base's free become method error check

(cherry picked from commit 445a88d3e8)
2016-06-10 14:41:11 -07:00
2982b99b8e better handling of retry files
(cherry picked from commit 734bbcb1d3)
2016-06-10 14:03:53 -04:00
6b286ee0c8 fix for psuedo-connection hostvars not propagating to connection
(mostly done by jimi-c, tested working)

(cherry picked from commit 9f998dbfc49ab5b7a587db6a7099096a414f5841)
2016-06-10 10:17:56 -07:00
efed4e577c raw should not use default executable (#16085)
also removed unused cruft in script
(cherry picked from commit a529a60478)
2016-06-10 11:41:49 -04:00
9bb754c0a7 Fix: create retry_files_save_path if it doesn't exist (#15659)
* Fix: create retry_files_save_path if it doesn't exist

Ansible documentation states that retry_files_save_path directory will be
created if it does not already exist. It currently doesn't, so this patch
fixes it :)

* Use makedirs_safe to ensure thread-safe dir creation

@bcoca suggested to use the makedirs_safe helper function :)

(cherry picked from commit 828b73dd2d)
2016-06-10 09:41:30 -04:00
d65e2aec81 Update submodule ref 2016-06-09 15:52:23 -07:00
e971a63c33 avoid processing bad mtab entries (#16206)
fixes #16174
(cherry picked from commit c376954ecf)
2016-06-09 15:14:26 -04:00
553ceefe81 Update submodule refs 2016-06-09 08:58:54 -07:00
c9b125d21a Fix hostvars lookup of locahost based on 5f1bbb4
(cherry picked from commit b51d0dd69b)
2016-06-08 14:15:02 -05:00
00cf1cbac1 There can be only one localhost
The changes to exclude implicit localhosts from group patterns exposed
the bug that we sometimes create multiple implicit localhosts, which
caused some bugs with things like includes, where the host was used as
an entry into a dict, so having multiple meant that the incorrect host
(with a different uuid) was found and includes were not executed for
implicit localhosts.
2016-06-08 13:01:47 -05:00
2e003adbc8 Expand return code values returned by TQM and strategies
This allows the PlaybookExecutor to receive more information regarding
what happened internal to the TaskQueueManager and strategy, to determine
things like whether or not the play iteration should stop.

Fixes #15523

(cherry picked from commit fbec2d9692)
2016-06-08 10:44:59 -05:00
d4c78b84f0 fixes issue with ssh keyfile and nxos authentication
The nxos cli provider would not properly handle ssh key files passed
from the playbook task.   The ssh_keyfile argument is now properly
passed to the ssh authentication method

This fix address the bug reported in #3862
2016-06-07 21:28:31 -04:00
405f636cc7 bump submodule refs 2016-06-07 17:13:53 -07:00
a2c1247dff Revert "don't tempalte register"
This reverts commit 7ba790bbaf.

Fixes #15700
2016-06-07 13:40:36 -05:00
3ced6d3e90 Further tweaks to variable precedence to make it match our docs
Also removes looking at role variables from the Block, as those are
merged in separately via VariableManager
2016-06-07 12:09:05 -05:00
a9c9cd773f Fix variable precedence issue where set facts beat role params
Also updates doc on variable precedence, as it was incorrect for the
order of play vars/vars_prompt/vars_files in relation to set_fact and
registered variables.

Fixes #14702
Fixes #14826
2016-06-07 11:25:26 -05:00
313d94cc71 Create state in PlayIterator for unknown hosts rather than raise errors
Since we now use the PlayIterator to carry forward failures from previous
play executions, in the event that some hosts which had previously failed
are not in the current inventory we now create a stub state instead of
raising an error.
2016-06-07 09:45:22 -05:00
aa66551352 fix: playbook_on_task_start expects name and not task object (#16168)
(cherry picked from commit 721da46842)
2016-06-07 09:43:39 -04:00
66d54dec58 set remote_user to default if none is found when using delegate_to (#16138)
(cherry picked from commit ef9238ab85)
2016-06-07 09:37:14 -04:00
6fc2660194 fixes issues with authenticating using ssh-agent for ios devices
Exception was raised when trying to use ssh-agent for authentication to
ios devices.   This fix enables ssh-agent and enable use of password
protected ssh keys.  There is one additional fix to capture authentication
exceptions nicely.
2016-06-07 06:33:35 -04:00
8fd7e970a1 Remove opensuseleap from shippable tests in 2.1.
These tests were not part of CI at the time 2.1 was released.

They are not currently passing in stable-2.1, as changes necessary
for them to pass were made after the release.
2016-06-06 23:49:46 -07:00
263e51095f use userdir module as example instead of alias (#15540)
* alias module is very basic and removing it leads to the suse default
  config failing
* future improvements might test different modules and the effect of
  them being removed
(cherry picked from commit cf62a62b83)
2016-06-06 18:54:26 -07:00
e1765c9d0d tests, postgresql: add ubuntu 16.04 support
(cherry picked from commit 5583027f99)
2016-06-06 18:08:36 -07:00
d14b29efc2 tests, apt_repository: disable Ubuntu 16.04 as there is no package yet
(cherry picked from commit 39e4caafb2)
2016-06-06 17:30:14 -07:00
8b3ce600d0 Detect use of Travis tests on Shippable.
This can occur when building pre-Shippable branches or PRs.

(cherry picked from commit 03597143d0)
2016-06-06 16:48:17 -07:00
945fb3411e Run tests on ubuntu1604 and opensuseleap (#15936)
* reduce async sleep time in test
* make zypper test less destructive (don't break following uses of zypper)
* fix ca cert on suse
* fix/enable postgres/mysql on opensuseleap
* fix mysql test for mysql versions 5.7.6 and newer
* skip sni_host check on ubuntu1604
* add HTTPTESTER flag for test_uri

ubuntu 16.04 uses dash which drops env variables containing a dot
we work around this by adding an explicit env variable to enable httptester

(cherry picked from commit c06884eff0)
2016-06-06 16:48:17 -07:00
e2ebae5522 tests: fix tests on Debian 8
(cherry picked from commit c20d1fced7)
2016-06-06 16:48:17 -07:00
6f0bc4dd82 Corrected reference to httptester container.
(cherry picked from commit b755bcd875)
2016-06-06 16:36:54 -07:00
247d4ebb8d Add httptester docker container files and update run_tests.sh to use ansible/httptester
(cherry picked from commit 164f247ec8)
2016-06-06 16:36:54 -07:00
86516eae05 Do not test vca and vmware.py for py2.4 (#15887)
Since both of them depend on libraries not
working on python 2.4, we shouldn't restrict
ourself on 2.4, cf https://github.com/ansible/ansible/pull/15870
(cherry picked from commit cc61531a74)
2016-06-06 16:36:54 -07:00
df33ff6c65 Use httptester docker image for http tests (#15811)
* Use httptester docker image for http tests

* When not running with an httptester linked container, use public test sites

(cherry picked from commit accf40d8a8)
2016-06-06 16:36:54 -07:00
73b250ecf7 fix default for removing images
(cherry picked from commit 2af8e3b9d8)
2016-06-06 16:36:54 -07:00
d77ff116c2 added ability to also subset make tests
(cherry picked from commit 27a1ae4732)
2016-06-06 16:36:54 -07:00
6c80be47a2 another var 'defaulted' in run_tests
(cherry picked from commit 47d58c30e4)
2016-06-06 16:36:54 -07:00
77dc6a36fd added defaults for 'optional' vars
(cherry picked from commit 3669ab2456)
2016-06-06 16:36:54 -07:00
217f8fd824 fine tuned shell switches for run_tests.sh
(cherry picked from commit 52a714143f)
2016-06-06 16:36:54 -07:00
dee38ceb95 Update how shippable scripts are called.
(cherry picked from commit 6d74f43eff)
2016-06-06 15:55:42 -07:00
66f8da7258 Add full support for Shippable CI.
(cherry picked from commit b0e1efbd62)
2016-06-06 15:55:42 -07:00
c1fe7aa322 Fixes #15915: New HandlerTaskInclude Class which can run TaskIncludes inside Handlers 2016-06-06 16:02:27 -05:00
4cabc94f09 Force relative role paths to be absolute and correct errors about missing path(s). (#16088)
Addresses #10811
2016-06-06 15:46:34 -05:00
7725c58315 Make sure we add host/group vars files when parsing inventory
Also fixes a bug where add_host was not adding the vars files

Fixes #16117
2016-06-06 15:45:55 -05:00
9936d7355c Fix recursive call of _load_inventory_file() in VariableManager
Fixes #16128
2016-06-06 14:57:59 -05:00
0ba9a6a875 Fix the way host and group vars are loaded
* In the VariableManager, we were not properly tracking if a file
  had already been loaded, so we continuously append data to the end
  of the list there for host and group vars, meaning large sets of data
  are duplicated multiple times
* In the inventory, we were merging the host/group vars with the vars
  local to the host needlessly, as the VariableManager already handles that.
  This leads to needless duplication of the data and makes combining the
  vars in VariableManager take even longer.
2016-06-06 14:57:59 -05:00
7287effb5c Solve performance issue with a large number of groups (#13957)
Ansible excessively checks the file system for the potential presence of
`group_vars` and `host_vars` files.

For large numbers of groups this leads to combinatorial performance
issues.

This commit generates a set of group_vars and host_vars filenames using
`os.listdir()` in every possible location and then checks against the sets
before making a stat of the file system.

Also included in this commit is caching of the base directory lookup
for the inventory.
2016-06-06 14:57:59 -05:00
b950f75489 Use the python executable specified by the environment.
(cherry picked from commit 18831c72f9)
2016-06-05 20:09:05 -04:00
535a436703 Fix whitespace.
(cherry picked from commit 1fdb7613f1)
2016-06-05 20:09:05 -04:00
41dde7259b Mark implicitly hosts as such and exclude them from the all group
Fixes #16059
2016-06-04 18:54:50 -05:00
31f6e26009 bump extras submodule ref for azure fixes 2016-06-04 16:18:01 -07:00
6e6dd98b86 update azure minimum doc fragment 2016-06-04 16:06:29 -07:00
a538b222dc updates for Azure SDK 2.0.0rc4 2016-06-04 15:08:52 -07:00
55d40b50e4 Removing __version__ from __init__.py since it belongs in release.py 2016-06-04 11:00:08 -05:00
6f5d1456bc Fix the copy action to not send the content as a param. (#16127)
Has already been transferred as a tempfile.

This fixes the error in https://github.com/ansible/ansible/issues/16125
but there may be higher level issues that should be fixed as well (other
modules might be able to cause status fields like failed and changed to
return a censored string instead of a bool).  So leaving 16125 open for
now.
2016-06-03 12:34:20 -07:00
7ddf6dbbd4 bump core submodule ref 2016-06-03 10:48:30 -07:00
9f32dcdd28 bump core submodule ref 2016-06-03 10:22:38 -07:00
1b5ec51988 Add validate_certs to vmware doc fragment (#16096) 2016-06-02 10:22:40 -04:00
71350e022a fixed confusing reporting when no hosts are matched
it was hitting 'empty inventory' warning.
2016-06-01 08:35:30 -04:00
ff9641a6a1 fix version/help when missing action
also fixed issues with galaxy cli options
2016-06-01 08:35:30 -04:00
a59478f4f4 treat roles_path as the list it is for galaxy list 2016-06-01 08:35:30 -04:00
2452bd2135 moved validate info from vvv to debug 2016-05-27 09:44:42 -04:00
0de111be00 Fix missing ignore_certs (#16018) 2016-05-27 09:43:41 -04:00
2399dd32a8 Update submodule refs -- get them back onto the stable-2.1 branch instead of devel 2016-05-26 16:29:41 -07:00
ccbcb4b5e9 Update grep for six to not falsely trigger when six is only a substring of a different library 2016-05-26 16:16:21 -07:00
4304574c00 Fix unarchive failures when the destination is a symlink to a directory (#15999)
Also add integration tests for this problem and unicode filenames inside
a tarball.

Fixes #3725
2016-05-26 15:02:10 -07:00
a44743d142 Use get_dep_chain() instead of directly using a blocks _dep_chain
Child blocks (whether nested or via includes) don't get a copy of the
dependency chain, so the above method should be used to ensure the block
looks at its parents dep chain.

Fixes #15996
2016-05-26 15:48:53 -04:00
4bf9cf6e41 mark azure_rm_X_facts modules as TECH PREVIEW 2016-05-25 16:02:39 -07:00
f3c9672fc0 add missing azure_rm modules to changelog 2016-05-25 14:25:03 -07:00
52c9e2ffcf Add strings 'True' and 'False' as booleans as python bools converted to strings will look that way.
Workaround for custom modules which are using choices=BOOLEANS instead
of type='bool'.
2016-05-25 10:38:35 -07:00
b3ca832025 Add ChangeLog for pkg_util bug fix. 2016-05-25 12:27:26 -04:00
09fa05373b When adding nested blocks, don't pass in any parent includes
We previously changed block behavior to prefer includes over parent
blocks, which broke inheritence in nested blocks.

Fixes #15926
2016-05-25 06:59:37 -07:00
a6bff1e49c Add Changelog for optional pycrypto. 2016-05-25 06:59:37 -07:00
faf85ec57c Catch DistributionNotFound when pycrypto is absent (#15731)
* Catch DistributionNotFound when pycrypto is absent

On Solaris 11, module `pkg_resources` throws `DistributionNotFound` on import if `cryptography` is installed but `pycrypto` is not.  This change causes that situation to be handled gracefully.

I'm not using Paramiko or Vault, so I my understanding is that I don't
need `pycrpto`.  I could install `pycrypto` to make the error go away, but:
- The latest released version of `pycrypto` doesn't build cleanly on Solaris (https://github.com/dlitz/pycrypto/issues/184).
- Solaris includes an old version of GMP that triggers warnings every time Ansible runs (https://github.com/ansible/ansible/issues/6941).  I notice that I can silence these warnings with `system_warnings` in `ansible.cfg`, but not installing `pycrypto` seems like a safer solution.

* Ignore only `pkg_resources.DistributionNotFound`, not other exceptions.
2016-05-25 06:59:37 -07:00
d3367dd722 Updating RELEASES.txt 2016-05-25 08:52:51 -04:00
1867adfabc Update submodule ref to include parameter change to junos module. 2016-05-24 11:48:32 -07:00
9e622dcf31 New release v2.1.0.0-0.4.rc4 2016-05-22 16:44:41 -04:00
68807c461b Add support for shippable.com CI tests.
This will allow offloading sanity tests from Travis to Shippable.

(cherry picked from commit 0e8930640a)
2016-05-21 16:22:05 -07:00
3bef107872 Fix the error handling for loop squashing to restore the name argument into the task args (#15949) 2016-05-21 07:34:36 -07:00
6b964a1594 Update test off make_become for ssh pty race mitigation (#15931) 2016-05-19 15:20:32 -07:00
36f2312071 Update core submodule to pull in docker documentation fix 2016-05-19 14:42:33 -07:00
b970e2ca80 Add work-around for ssh pty race condition.
This should minimize loss of stdout when using
a pty and connecting with ssh or paramiko_ssh.

(cherry picked from commit bad293ae35)
2016-05-19 10:34:09 -07:00
036547b4dd New release v2.1.0.0-0.3.rc3 2016-05-18 15:48:24 -04:00
eae26891ea Fix typo.
(cherry picked from commit 0bc23a4408)
2016-05-18 15:12:42 -04:00
b5ccbf2986 Update ansible-container links.
(cherry picked from commit 22de8a9a21)
2016-05-18 15:12:25 -04:00
7bfc3df933 Fix italics and version directives.
(cherry picked from commit 0c80b71d71)
2016-05-18 15:12:09 -04:00
1aa39ae78b Add docker_service and mention ansible-container.
(cherry picked from commit 10c5d9e749)
2016-05-18 15:09:14 -04:00
a6150dbc14 More playbook executor compatibility fixes
With some earlier changes, continuing to forward failed hosts on
to the iterator with each TQM run() call was causing plays with
max_fail_pct set to fail, as hosts which failed in previous plays
were counting those old failures against the % calculation.

Also changed the linear strategy's calculation to use the internal
failed list, rather than the iterator, as this now represents the
hosts failed during the current run only.
2016-05-18 14:09:04 -04:00
92e4f25066 Make load_params into a function that custom modules can access (#15913) 2016-05-18 10:55:46 -07:00
b3676392a8 Fix small typo in ansible.cfg (#15912) 2016-05-18 12:29:30 -04:00
41f45336a9 Fixed issue with parents havings depth of 3+ on add method of NetworkConfig 2016-05-18 09:31:54 -04:00
710a96956e In TQM run() mark any entries in _failed_hosts as failed in the PlayIterator
As noted in the comment, the TQM may be used for more than one play. As such,
after creating the new PlayIterator object it is necessary to mark any failed
hosts from previous calls to run() as failed in the iterator, so they are
properly skipped during any future calls to run().
2016-05-18 08:17:53 -04:00
7855612805 Re-remove checking for failed state on hosts when building list of hosts
This was re-added by 63471cd (and modified by me to use iterator again),
it simply needs to be removed.

Fixes #15395
2016-05-18 08:17:53 -04:00
b0259a8631 Make sure that serial failures are handled correctly. 2016-05-18 08:17:53 -04:00
5e9b13cb94 Don't let max_fail_percentage affect future plays. 2016-05-18 08:17:53 -04:00
46e9e4c4da Handle max_fail_percentage per task. 2016-05-18 08:17:53 -04:00
9602e43952 Don't stop executing plays after failure.
https://github.com/ansible/ansible/pull/13750/files
2016-05-18 08:17:53 -04:00
c901c9144c Backward compatibility execution failures with 1.9 2016-05-18 08:17:53 -04:00
76cd7cadfe Allow ssh agent usage for junos_netconf
By default the `Shell` class disables ssh agents. The `junos_netconf`
module uses this class, but doesn't re-enable agents.
Here it's explicitly enabled again, so an ssh agent can be used to
connect to and configure Junos devices.
2016-05-18 07:55:31 -04:00
94e4e4105d openswitch.py: Fix the OpenSwitch REST authentication
It's a cookie based authentication, that we get it
through /login endpoint, called by connect() method
and save the cookie for the rest of the call.
2016-05-18 07:41:04 -04:00
309aba128c handle name resolution errors more gracefully from shell.py
This change will catch socket.gaierror exceptions from shell.py and
return a more friendly message to the user
2016-05-18 06:55:17 -04:00
ff346a199c fix windows integration tests to run under kerberos users 2016-05-17 15:32:32 -07:00
2259b90827 bugfix for issue with trying to eval contains with non-string
fixes ansible/ansible-modules-core#3502
2016-05-17 06:46:03 -04:00
cd2991c02d made format more flexible and allow for non dict entries 2016-05-16 14:40:11 -04:00
d9fa5a3b80 fixes #15496
changed to using OrderedDict to preserve order of lines
2016-05-16 14:05:09 -04:00
e0112a3db3 Fix pkgin detection on NetBSD 6 and 7 (#15834)
Since this is now the default package manager, it got moved
to another location on Netbsd :

  netbsd# type pkgin
  pkgin is a tracked alias for /usr/pkg/bin/pkgin
  netbsd# uname -a
  NetBSD netbsd.example.org 6.1.4 NetBSD 6.1.4 (GENERIC) amd64

But since the package manager is also used outside of NetBSD, we
have to keep the /opt/local path too.
2016-05-16 10:14:46 -04:00
8f4243c3ec Check for jxmlease when using netconf on JUNOS. (#15835) 2016-05-16 10:10:26 -04:00
956829f0f0 net_template.py: Fix jinja2 template file search path (#15134)
The change is needed to support the multiple include statements
inside the jinja2 template file, as in '{% include ['another.j2'] %}'.
statement.  I need this capability, as OpenSwitch `switch` role needs
to handle multiple *.j2 files and supporting the include statement
inside jinja2 file is essential, otherwise I need to combine multiple
template files into a single file, which easily causes conflicts
between developers working on different parts of the teamplate, ports
and interface.
2016-05-16 10:10:04 -04:00
af5195e336 vCloud module utils error handling bug fixes (#15859)
* Fix AttributeError that hides login errors

* Typo fixes for vca error messages
2016-05-13 23:59:47 -07:00
03e7f54071 Don't use 'from ansible.module_utils import foo' style here as it breaks (#15756)
py.test"
2016-05-13 23:59:39 -07:00
0ad0b3d83b New release v2.1.0.0-0.2.rc2 2016-05-13 17:07:03 -04:00
b40948acd9 Adding accelerated mode deprecation to the CHANGELOG 2016-05-13 17:00:36 -04:00
cc347dcaa9 Adding a deprecation message for accelerated mode 2016-05-13 17:00:06 -04:00
4c5dddb25a Reworking retry/until logic to fix bugs
Prior to this patch, the retry/until logic would fail any task that
succeeded if it took all of the alloted retries to succeed. This patch
reworks the retry/until logic to make things more simple and clear.

Fixes #15697
2016-05-13 16:09:17 -04:00
b181fe0bc2 Disable sftp batch mode if sshpass (#15829)
Make use of the -oBatchMode=no option to force password prompts from sftp

Addresses #13401
2016-05-13 13:52:30 -04:00
71c7476199 Update submodule refs 2016-05-13 10:15:52 -07:00
9121ca2f8e Use .code instead of .getcode() as py24 does not have .getcode(). Fixes https://github.com/ansible/ansible-modules-core/issues/3608 2016-05-13 12:11:15 -05:00
cae6240e5e Ship constants to the modules via internal module params rather than a secondary dict. 2016-05-13 10:09:28 -07:00
2f302e26f4 Make sure setting facts with run_once makes copies of the data
When using run_once, there is only one dict of facts so passing that
to the VariableManager results in the fact cache containing the same
dictionary reference for all hosts in inventory. This patch fixes that
by making sure we pass a copy of the facts dict to VariableManager.

Fixes #14279
2016-05-13 10:05:50 -04:00
1968bc5952 Cascade ssh_*args configurations in synchronize instead of limiting to just ssh_args. See https://github.com/ansible/ansible-modules-core/issues/3370 2016-05-12 19:01:09 -05:00
047d62cce3 Guard against a shell profile printing extraneous data 2016-05-12 18:33:54 -05:00
1c2f0ae8f7 Simply being in an always block shouldn't mean a host did not fail
Previously the changed code was necessary, however it is now problematic
as we've started using the is_failed() method in other places in the code.
Additional changes at the strategy layer should make this safe to remove
now.

Fixes #15625
2016-05-12 17:12:07 -04:00
3a052654f3 Do not include params when getting role vars in certain situations
In VariableManager, we fetch the params specifically in the next step,
so including them in the prior step is unnecessary and could lead to things
being overridden in an improper order.

In Block, we should not be getting the params for the role as they are
included earlier via the VariableManager.

Fixes #14411
2016-05-12 17:12:00 -04:00
2a512affde Fixes #15745 playbook include: Conditional scoping
Fixes #15745
Applies conditional forwarding to all tasks/roles within the included playbook.
The existing line only applies forwarded conditionals to the main Task block, and misses pre_, post_, and roles.

Typo ::

Made a selection mistake when I copied over the one line change
2016-05-12 15:02:46 -04:00
90fb1fb3fa If we can't squash for any reason, then simply do not optimize the items loop.
Also add more squashing testcases

Fixes #15649
2016-05-12 11:11:05 -07:00
8cd0c432e7 Fixed importing the libcloud modules to give a nice error rather than a traceback. 2016-05-12 09:14:51 -07:00
151df71464 Update GCE module to use JSON credentials (#13623)
* Update GCE module to use JSON credentials

* Ensure minimum libcloud version when using JSON crednetials for GCE

* Relax langauge around libcloud requirements
2016-05-12 09:14:51 -07:00
d22898a27c Clear blocked hosts when a role duplicate task is found in free strategy
In the free strategy, we mark a host as blocked when it has work to do
(the PlayIterator returns a task) to prevent multiple tasks from being sent
to the host. However, we check for role duplicates after setting the blocked
flag, but were not clearing that when the task was skipped leading to an
infinite loop. This patch corrects that by clearing the blocked flag when
the task is skipped.

Fixes #15681
2016-05-12 10:42:51 -04:00
22b86bbb63 do not erroneously set gathered_facts=True
In `lib/ansible/executor/play_iterator.py`, ansible sets a host's
`_gathered_facts` property to `True` without checking to see if there
are any tasks to be executed.  In the event that the entire play is
skipped, `_gathered_facts` will be `True` even though the `setup`
module was never run.

This patch modifies the logic to only set `_gathered_facts` to `True`
when there are tasks to execute.

Closes #15744.
2016-05-12 10:23:06 -04:00
00b04ab794 Apply inventory host restrictions by host name rather than UUID.
Issue #15633 observes that a meta: inventory_refresh task causes the playbook
to exit. An inventory refresh flushes all caches and rebuilds all host
objects, assigning new UUIDs to each. These new host UUIDs currently fail to
match those on host objects stored for restrictions in the inventory, causing
the playbook to exit for having no hosts to run further tasks against.

This changeset attempts to address this issue by storing host restrictions
by name, and comparing inventory host names against these names when applying
restrictions in get_hosts.
2016-05-12 09:52:36 -04:00
af257c20da Change error about loop variable in use to a warning 2016-05-11 21:50:51 -04:00
0040c6a4d2 Add fix for 15601 to the ChangeLog 2016-05-11 17:56:48 -07:00
8a84ef80e2 Strip junk after JSON return. (#15822)
Fixes #15601
2016-05-11 17:55:20 -07:00
e9406bcfd3 Backport some python3 fixes for facts 2016-05-10 21:47:19 -07:00
1c21baa706 restore old jsonfile behaviour on key expiration
fixes #14456, now it won't expire keys in middle of a play when they
were 'valid' at 'gather time'.
2016-05-10 15:55:46 -04:00
8a72972360 fix bad assignment, method modifies by ref already
fixes #15694
2016-05-10 12:08:22 -04:00
b7cab0533e Remove reload from arg related tests. Changes to how ziploader passes args mean we don't need reload anymore. (#15782) 2016-05-10 08:14:46 -07:00
6e8b12690b Add s3_bucket fix 2016-05-10 08:14:46 -07:00
e840816da2 Add s3_bucket fix to CHANGELOG 2016-05-10 08:14:46 -07:00
fc4edcbedc add repr for hostvars (#15793)
* allows passing full hostvars to a module
2016-05-10 10:24:06 -04:00
31f17d1191 Update submodule ref 2016-05-10 07:12:56 -07:00
d2e8e8d6a7 Switch to a different url for testing SNI right now. (#15798) 2016-05-10 07:10:28 -07:00
6f99f40f37 fixes-#15685-tools-that-paginate-show-spurious-less-output: less --version outputs to standard out not to standard error so this changes the redirect from 2> to > (#15720)
fixes-#15685-tools-that-paginate-show-spurious-less-output: Updated redirect to include stderr as well as stdout to not show any errors on screen
2016-05-09 16:50:25 -04:00
be28443943 Strip leading and trailing whitespace for json arg types 2016-05-09 08:15:23 -07:00
9620eadc03 bump extras submodule ref 2016-05-06 09:48:25 -07:00
73dd183394 Update submodule refs 2016-05-06 08:15:05 -07:00
506e6255b2 Fixing bugs in strategies
* Don't filter hosts remaining based on their failed state. Instead rely
  on the PlayIterator to return None/ITERATING_COMPLETE when the host is
  failed.
* In the free strategy, make sure we wait outside the host loop for all
  pending results to be processed.
* Use the internal _set_failed_state() instead of manually setting things
  when a failed child state is hit

Fixes #15623
2016-05-06 09:21:20 -04:00
4574a0e82d bump submodule refs 2016-05-05 15:45:39 -07:00
761cdc794e add jimi-c's unit test for squashed skip results, tweaked is_skipped() logic to pass 2016-05-05 15:30:58 -07:00
da1e62a6d9 Don't assume a task with non-dict loop results has been skipped.
This changeset addresses the issue reported here:

  ansible/ansible-modules-core#1765

The yum module (at least) includes its task results as strings, rather than
dicts, and the code this changeset replaces assumed that in that instance the
task was skipped. The updated behaviour assumes that the task has been
skipped only if:

* results exist, and
* all results are dicts that include a truthy skipped value
2016-05-05 15:30:50 -07:00
198f57b61e New inventory_ip_type option in gce inventory tool 2016-05-05 10:22:46 -07:00
90fef85143 check that variable first
before using string methods to check for magic interpreter var
2016-05-05 11:15:36 -04:00
a355ebcbb8 Fix logging into vCloud Director and expose verify_certs argument (#15533) 2016-05-05 09:52:27 -04:00
2c8715178f error message cleanup 2016-05-04 09:44:36 -07:00
7662b06d5b actionable.py: Do not print next task banner in handler callback (#15698)
Fix actionable callback plugin to not print the banner of the previous
task.

When a handler is executed there is no task banner, so in case it is run,
it will reference the banner from the preceding task.

**Author:** @hvhaugwitz

Test case:

      ---

      - name: actionable filter
        hosts: all
        handlers:
          - name: handler
            command: "true"
        tasks:
          - name: task 1
            file: path=/tmp/test state=touch
            notify: handler
          - name: task 2
            file: path=/tmp/test state=absent
          - name: task 3
            file: path=/tmp/test state=absent
          - name: task 4
            file: path=/tmp/test state=absent
          - name: task 5
            file: path=/tmp/test state=absent
          - name: task 6
            file: path=/tmp/test state=absent

Example output:

BEFORE
------

      PLAY [actionable filter] *******************************************************

      TASK [task 1] ******************************************************************
      changed: [localhost]

      TASK [task 2] ******************************************************************
      changed: [localhost]

      RUNNING HANDLER [handler] ******************************************************

      TASK [task 6] ******************************************************************
      changed: [localhost]

      PLAY RECAP *********************************************************************
      localhost                  : ok=8    changed=3    unreachable=0    failed=0

AFTER
-----

      PLAY [actionable filter] *******************************************************

      TASK [task 1] ******************************************************************
      changed: [localhost]

      TASK [task 2] ******************************************************************
      changed: [localhost]

      RUNNING HANDLER [handler] ******************************************************
      changed: [localhost]

      PLAY RECAP *********************************************************************
      localhost                  : ok=8    changed=3    unreachable=0    failed=0
2016-05-03 11:26:37 -04:00
e52caba801 Update submodule ref to fix ansible-doc for azure modules 2016-05-03 07:53:26 -07:00
efbcd8bda0 Add a jsonarg type to arg spec (#15701)
This makes sure that if we get a list or dict that it is turned into
a jsonified string.
2016-05-03 10:21:23 -04:00
86768fdcc8 Treat "static: yes/no" with higher priority than "task_includes_static" in ansible.cfg 2016-05-03 09:19:05 -04:00
e1b7d4cb4d Restore Ansible 2.0 compatibility for includes 2016-05-03 09:18:59 -04:00
641b4cc952 Add unarchive fixes to changelog 2016-05-02 11:32:19 -07:00
7cc7684abd Update submodule refs 2016-05-02 11:31:20 -07:00
16c1f10e18 Remove the ziploader provided pythonpaths from the env inside run_com… (#15674)
Remove the ziploader provided pythonpaths from the env inside run_command.

Fixes #15655
2016-05-02 11:24:31 -04:00
4abadead76 make vi the default editor if no EDITOR
fixes #15577
2016-05-02 08:45:01 -04:00
c7cae3b08c Don't fail hosts when using any_errors_fatal when ignoring errors
Fixes #15431
2016-05-01 12:40:49 -04:00
40c0f34c41 When var name is the same as var content, try to template it before reporting that var is not defined. (#13629)
* When var name is the same as var content, try to template it before reporting that var is not defined.
Add asserts in test_var_blending to check this special corner case.

* Fix integration tests when using debug with list or dict.
2016-05-01 06:48:54 -07:00
48fa4f842e Get rid of logentries.to_unicode.
It wasn't doing anything that a literal couldn't do and used
unicode_escape which only understands latin1 (The author of the code
seems to have thought it took an encoding argument but it looks like
that was silently ignored.)
2016-04-30 07:49:51 -07:00
145d8626d0 Fix inventory on python3 2016-04-30 07:29:45 -07:00
e27b22162a bytes when passing to os.path.* and opening the file, text at other times
Fixes #15644
2016-04-29 22:24:47 -07:00
92c299cfd5 Update submodule refs 2016-04-29 22:00:33 -07:00
5909a4473d Fix the mapping of module_name to Locks
This was reinitialized every time we forked before so we weren't sharing
the same Locks.  It also was not accounting for modules which were
directly invoked by an action plugin instead of going through the
strategy plguins.
2016-04-29 21:58:14 -07:00
d5585220a4 Fix the version number 2016-04-29 14:01:14 -07:00
1e5708514b Fix ziploader for the cornercase of ansible invoking ansible.
* Make ziploader's ansible and ansible.module_utils libraries into
  namespace packages.
* Move __version__ and __author__ from ansible/__init__ to
  ansible/release.py.  This is because namespace packages only load one
  __init__.py.  If that is not the __init__.py with the author and
  version info then those won't be available.
* In ziploader, move the version ito ANSIBLE_CONSTANTS.
* Change PluginLoader to properly construct the path to the plugins even
  when namespace packages are present.
2016-04-29 11:15:24 -07:00
a582664bc6 fix typo 2016-04-29 13:50:52 -04:00
08ce27edfb fixed method signature as per #15668 2016-04-29 13:13:36 -04:00
bea94efd73 Add aliases 2016-04-29 01:29:04 -04:00
92f058b1f5 Updated doc fragment to be consistent with the getting started guide. 2016-04-29 01:28:53 -04:00
7ed6270808 Fixed docker_host definition. 2016-04-29 01:26:13 -04:00
6db3d4887e Change command line parameters to parameters. 2016-04-29 01:25:28 -04:00
5e7e55830f Adding getting started guide for Docker 2016-04-29 01:25:06 -04:00
8f31634977 Update submodules 2016-04-29 01:15:26 -04:00
688a7d3668 Updating submodules 2016-04-29 01:05:11 -04:00
8bef5ccae9 Updating submodules 2016-04-29 00:59:41 -04:00
74b4d7a2ae API now connects to server lazily (#15632)
This should fix most issues with offline operation.

Fixes #14486
Fixes #13991

Alternate to #15363 and #15593
2016-04-28 23:28:49 -04:00
5a9e826647 Rename function bool() as it is a Python built-in function (#15651) 2016-04-28 14:42:21 -04:00
3c42724d96 Update extras submodules 2016-04-28 08:31:11 -07:00
069e032dcd Add git_config module to 2.1 changelog 2016-04-28 08:30:48 -07:00
9535feb90a Update submodule ref 2016-04-28 08:15:29 -07:00
0b28f708c4 Update and fix typos in docker inventory docstrings. (#15616) 2016-04-28 07:27:06 -07:00
10c3472699 Handle "/usr/bin/env python" style ansible_*interpreter settings.
Fixes #15635
2016-04-28 06:52:39 -07:00
b6c59f89d2 added common azure tags fragment 2016-04-27 17:10:37 -04:00
8222a83bcd Make -q flag totally quiet for env-setup.fish
The first echo and setup.py steps are not quiet and result in output that is annoying if your Fish is configured to source the file.
2016-04-27 12:33:45 -07:00
430bef907a Fix for unittests on python2.6 or less 2016-04-27 10:46:35 -07:00
d3de771d2e Update submodule ref 2016-04-27 10:42:25 -07:00
459c4ec124 Remove the duplicate modstyle parameter 2016-04-27 10:38:41 -07:00
1c447016c3 extend zypper integration tests (#15596)
* extend zypper integration tests

* fix caching issue of local RPMS on openSUSE 42.1 Leap
* add tests for simultaneous install/remove via prefixes +-
* test fail cases (rm patch or URL)
* test patch install (succes, unchanged second run, fail on wrong name)

* add test for pattern install
2016-04-27 10:38:34 -07:00
41f5ce80fa Some algorithmic unittests for the apt and docker modules 2016-04-27 10:38:13 -07:00
07e0fceb61 Some beginning python3 porting for urls.py 2016-04-27 10:37:16 -07:00
caa8fffa01 Fix traceback in fetch_urls when status code is in info as well as the error. 2016-04-27 10:37:05 -07:00
5954a82dd6 New release v2.1.0.0-0.1.rc1 2016-04-26 16:23:34 -04:00
7f7e010a32 Submodule update for stable-2.1 branch 2016-04-26 16:18:29 -04:00
200 changed files with 4634 additions and 1694 deletions

1
.gitignore vendored
View File

@ -61,3 +61,4 @@ venv
Vagrantfile
.vagrant
ansible.egg-info/
/shippable/

View File

@ -1,40 +0,0 @@
dist: trusty
sudo: required
services:
- docker
language: python
matrix:
include:
- env: TARGET=centos6
- env: TARGET=centos7 TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
- env: TARGET=fedora23 TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
- env: TARGET=fedora-rawhide TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
- env: TARGET=ubuntu1204
- env: TARGET=ubuntu1404
- env: TARGET=sanity TOXENV=py26
python: 2.6
- env: TARGET=sanity TOXENV=py27
python: 2.7
- env: TARGET=sanity TOXENV=py34
python: 3.4
- env: TARGET=sanity TOXENV=py35
python: 3.5
- env: TARGET=sanity TOXENV=py24
addons:
apt:
sources:
- deadsnakes
packages:
- python2.4
install:
- pip install tox
script:
- ./test/utils/run_tests.sh
notifications:
irc:
channels:
- "chat.freenode.net#ansible-notices"
on_success: change
on_failure: always
skip_join: true
nick: ansibletravis

View File

@ -1,7 +1,103 @@
Ansible Changes By Release
==========================
## 2.1 "The Song Remains the Same" - ACTIVE DEVELOPMENT
## 2.1.2 "The Song Remains the Same" - 09-29-2016
###Minor Changes:
* Fixed a bug related to creation of retry files (#17456)
* Fixed a bug in the way include params are used when an include task is dynamic (#17064)
* Fixed a bug related to including blocks in an include task (#15963)
* Fixed a bug related to the use of hostvars internally when creating the connection plugin. This prevents things like variables using lookups from being evaluated unnecessarily (#17024)
* Fixed a bug where using a variable containing a list for the `hosts` of a play resulted in an list of lists (#16583)
* Fixed a bug where integer values would cause an error if a module param was of type `float` (no issue)
* Fixed a bug with net_template failing if src was not specified (#17726)
* Fixed a bug in "ansible-galaxy import" (#17417)
* Fixed a bug in which INI files incorrectly treated a hosts range as a section header (#15331)
* Fixed a bug in which the max_fail_percentage calculation erroneously caused a series of plays to stop executing (#15954)
* Fixed a bug in which the task names were not properly templated (#16295)
* Fixed a bug causing "squashed" loops (ie. yum, apt) to incorrectly report results (ansible-modules-core#4214)
* Fixed several bugs related to includes:
- when including statically, make sure that all parents were also included statically (issue #16990)
- properly resolve nested static include paths
- print a message when a file is statically included
* Fixed a bug in which module params expected to be float types were not converted from integers (only strings) (#17325)
* Fixed a bug introduced by static includes in 2.1, which prevented notifications from going to the "top level" handler name.
* Fixed a bug where a group_vars or host_vars directory in the current working directory would be used (and would take precedence) over those in the inventory and/or playbook directory.
* Fixed a bug which could occur when the result of an async task did not parse as valid JSON.
* (re)-allowed the use of ansible_python_interpreter lines with more than one argument.
* Fixed several bugs related to the creation of the implicit localhost in inventory.
* Fixed a bug related to an unspecified number of retries when using until.
* Fixed a race-condition bug when creating temp directories before the worker process is forked.
* Fix a bug with async's poll keyword not making use of ansible_python_interpreter to run (and thus breaking when /usr/bin/python is not present on the remote machine.)
* Fix a bug where hosts that started with a range in inventory were being treated as an invalid section header.
Module fixes:
* Fixed a bug where the temporary CA files created by the module helper code were not being deleted properly in some situations (#17073)
* Fixed many bugs in the unarchive module
* Fixes for module ec2:
- Fixed a bug related to source_dest_check when used with non-vpc instances (core#3243)
- Fixed a bug in ec2 where instances were not powering of when referenced via tags only (core#4765)
- Fixed a bug where instances with multiple interfaces were not powering up/down correctly (core#3234)
* Fixes for module get_url:
- Fixed a bug in get_url module to force a download if there is a checksum mismatch regardless of the last modified time (core#4262)
- Fixed a bug in get_url module to properly process FTP results (core#3661 and core#4601)
* Fixed a bug in win_user related to users with disabled accounts/expired passwords (core#4369)
* ini_file:
- Fixed a bug where option lines are now inserted before blank lines.
- Fixed a bug where leading whitespace prevented matches on options.
* Fixed a bug in iam_cert when dup_ok is used as a string.
* Fixed a bug in postgresql_db related to the changed logic when state=absent.
* Fixed a bug where single_transaction and quick were not passed into db_dump for the mysql_db module.
* Fixed a bug where the fetch module was not idempotent when retrieving the target of a symlink.
* Many minor fixes for bugs in extras modules.
###Deprecations:
* Deprecated the use of `_fixup_perms`. Use `_fixup_perms2` instead.
This change only impacts custom action plugins using `_fixup_perms`.
###Incompatible Changes:
* Use of `_fixup_perms` with `recursive=True` (the default) is no longer supported.
Custom action plugins using `_fixup_perms` will require changes unless they already use `recursive=False`.
Use `_fixup_perms2` if support for previous releases is not required.
Otherwise use `_fixup_perms` with `recursive=False`.
## 2.1.1 "The Song Remains the Same" - 07-28-2016
###Minor Changes:
* If the user is not using paramiko or vault, allow Ansible to run if pycrypto is not installed.
* Fixed a bug in pkg_util module that caused "update_catalog must be one of" error if 'update_catalog' arg was used.
* Fixed a bug where psuedo-connection vars (eg, ansible_winrm_transport) defined in group_vars or host_vars were not getting passed to the connection.
* Fixed a bug where temp file permissions on BSDs were not using filesystem acls when available.
* Fixed some bugs in variable dependency resolution. These were mainly related to includes and roles, to bringe the VariableManager code in-line with our documentation.
* Fixed a bug in unarchive, when the destination was a symlinked directory.
* Fixed a bug related to performance when loading a large number of groups.
* Fixed bugs related to the way host and group vars are loaded, which (for large sets of inventory variables) can reduce CPU and memory usage by 50%.
* Fixed a bug where includes were not being implicitly evaluated as static when no loop or variables were being used.
* Fixed several more bugs in relation to the way play execution continues or halts when hosts fail, to bringe the behavior more in line with 1.9.x.
* Fixed bugs related to the use of the underlying shell executable with the script and raw modules.
* Fixed several bugs in relation to the way ssh keys were used with various networking modules.
* Fixed a bug related to the way handlers are tracked internally, which could cause bugs when roles are reused within the same play (allow_duplicates: yes) or when the role dependencies formed a "diamond" pattern.
* Fixed a bug related to setfacl on platforms which do not support the -R option for recursive changes.
* Several fixes to the way async works to prevent race conditions and other bugs
* More fixes to the way failed and unreachable hosts affect future plays
* Fixed a bug in the way the to_json filter encoded some objects
* Fixed a bug in the way roles and dependencies are loaded, and how they inherit params from parent roles.
* Fixed a bug in which the number of retries in a do/until loop was off by one
* Fixed a bug in the way the passwd lookup deals with salts
* When using the local connection, internally the remote_user value is now forced to be the current user even if remote_user is specified, to prevent issues with become settings
* Fix for Azure modules to work with most recent Azure python library (2.0.0rc5)
* Fix for bug related to unreachable hosts and any_errors_fatal in the linear strategy
* Fix for error handling in relation to killed/dead worker processes. If workers are killed via SIGKILL or SIGTERM, this will halt execution of the playbook.
* Fixed a regression in the way we handle variables from dependent roles.
* Added better handling for certain errors thrown from the cryptography.
* Fixed a typo in the azure_rm_storageaccount module.
* Fixed some minor bugs in the os_user_role and cs_volume modules.
* Fixed a bug related to the return value of a low-level inventory API call related to getting variables for hosts and groups.
## 2.1 "The Song Remains the Same" - 05-25-2016
###Major Changes:
@ -38,13 +134,21 @@ Ansible Changes By Release
- azure:
* azure_rm_deployment
* azure_rm_networkinterface
* azure_rm_networkinterface_facts (TECH PREVIEW)
* azure_rm_publicipaddress
* azure_rm_publicipaddress_facts (TECH PREVIEW)
* azure_rm_resourcegroup
* azure_rm_resourcegroup_facts (TECH PREVIEW)
* azure_rm_securitygroup
* azure_rm_securitygroup_facts (TECH PREVIEW)
* azure_rm_storageaccount
* azure_rm_storageaccount_facts (TECH PREVIEW)
* azure_rm_storageblob
* azure_rm_subnet
* azure_rm_virtualmachine
* azure_rm_virtualmachineimage_facts (TECH PREVIEW)
* azure_rm_virtualnetwork
* azure_rm_virtualnetwork_facts (TECH PREVIEW)
- cloudflare_dns
- cloudstack
* cs_cluster
@ -70,6 +174,7 @@ Ansible Changes By Release
* eos_config
* eos_eapi
* eos_template
- git_config
- gitlab
* gitlab_group
* gitlab_project
@ -160,7 +265,7 @@ Ansible Changes By Release
###Minor Changes:
* Added support for pipelining mode to more connection plugins, which helps prevent
* Added support for pipelining mode to more connection plugins, which helps prevent
module data from being written to disk.
* Added a new '!unsafe' YAML decorator, which can be used in playbooks to ensure a
string is not templated. For example: `foo: !unsafe "Don't template {{me}}"`.
@ -172,12 +277,16 @@ Ansible Changes By Release
two custom callback plugins to run in a certain order you can name them
10-first-callback.py and 20-second-callback.py.
* Added (alpha) Centirfy's dzdo as another become meethod (privilege escalation)
* Fixes for unarchive when filenames contain non-ascii characters
* Fixes for s3_bucket when setting an s3_url.
* Fix for connections which return extra data after the module's done sending its information.
###Deprecations:
* Deprecated the use of "bare" variables in loops (ie. `with_items: foo`, where `foo` is a variable).
The full jinja2 variable syntax of `{{foo}}` should always be used instead. This warning will be removed
completely in 2.3, after which time it will be an error.
* Deprecated accelerated mode.
## 2.0.2 "Over the Hills and Far Away"

View File

@ -67,7 +67,7 @@ ifeq ($(OFFICIAL),yes)
DEBUILD_OPTS += -k$(DEBSIGN_KEYID)
endif
else
DEB_RELEASE = 0.git$(DATE)$(GITINFO)
DEB_RELEASE = 100.git$(DATE)$(GITINFO)
# Do not sign unofficial builds
DEBUILD_OPTS += -uc -us
DPUT_OPTS += -u
@ -83,7 +83,7 @@ RPMSPEC = $(RPMSPECDIR)/ansible.spec
RPMDIST = $(shell rpm --eval '%{?dist}')
RPMRELEASE = $(RELEASE)
ifneq ($(OFFICIAL),yes)
RPMRELEASE = 0.git$(DATE)$(GITINFO)
RPMRELEASE = 100.git$(DATE)$(GITINFO)
endif
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"

View File

@ -1,7 +1,6 @@
[![PyPI version](https://img.shields.io/pypi/v/ansible.svg)](https://pypi.python.org/pypi/ansible)
[![PyPI downloads](https://img.shields.io/pypi/dm/ansible.svg)](https://pypi.python.org/pypi/ansible)
[![Build Status](https://travis-ci.org/ansible/ansible.svg?branch=devel)](https://travis-ci.org/ansible/ansible)
[![Build Status](https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=stable-2.1)](https://app.shippable.com/projects/573f79d02a8192902e20e34b)
Ansible
=======

View File

@ -1,73 +1,73 @@
Ansible Releases at a Glance
============================
Active Development
++++++++++++++++++
2.1 "The Song Remains the Same" - in progress
Released
++++++++
2.0.2 "Over the Hills and Far Away" 04-19-2015
2.0.1 "Over the Hills and Far Away" 02-24-2016
2.0.0 "Over the Hills and Far Away" 01-12-2016
1.9.6 "Dancing In the Streets" 04-15-2016
1.9.5 "Dancing In the Streets" 03-21-2016
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
1.9.1 "Dancing In the Streets" 04-27-2015
1.9.0 "Dancing In the Streets" 03-25-2015
1.8.4 "You Really Got Me" ---- 02-19-2015
1.8.3 "You Really Got Me" ---- 02-17-2015
1.8.2 "You Really Got Me" ---- 12-04-2014
1.8.1 "You Really Got Me" ---- 11-26-2014
1.7.2 "Summer Nights" -------- 09-24-2014
1.7.1 "Summer Nights" -------- 08-14-2014
1.7 "Summer Nights" -------- 08-06-2014
1.6.10 "The Cradle Will Rock" - 07-25-2014
1.6.9 "The Cradle Will Rock" - 07-24-2014
1.6.8 "The Cradle Will Rock" - 07-22-2014
1.6.7 "The Cradle Will Rock" - 07-21-2014
1.6.6 "The Cradle Will Rock" - 07-01-2014
1.6.5 "The Cradle Will Rock" - 06-25-2014
1.6.4 "The Cradle Will Rock" - 06-25-2014
1.6.3 "The Cradle Will Rock" - 06-09-2014
1.6.2 "The Cradle Will Rock" - 05-23-2014
1.6.1 "The Cradle Will Rock" - 05-07-2014
1.6 "The Cradle Will Rock" - 05-05-2014
1.5.5 "Love Walks In" -------- 04-18-2014
1.5.4 "Love Walks In" -------- 04-01-2014
1.5.3 "Love Walks In" -------- 03-13-2014
1.5.2 "Love Walks In" -------- 03-11-2014
1.5.1 "Love Walks In" -------- 03-10-2014
1.5 "Love Walks In" -------- 02-28-2014
1.4.5 "Could This Be Magic?" - 02-12-2014
1.4.4 "Could This Be Magic?" - 01-06-2014
1.4.3 "Could This Be Magic?" - 12-20-2013
1.4.2 "Could This Be Magic?" - 12-18-2013
1.4.1 "Could This Be Magic?" - 11-27-2013
1.4 "Could This Be Magic?" - 11-21-2013
1.3.4 "Top of the World" ----- 10-29-2013
1.3.3 "Top of the World" ----- 10-09-2013
1.3.2 "Top of the World" ----- 09-19-2013
1.3.1 "Top of the World" ----- 09-16-2013
1.3 "Top of the World" ----- 09-13-2013
1.2.3 "Hear About It Later" -- 08-21-2013
1.2.2 "Hear About It Later" -- 07-05-2013
1.2.1 "Hear About It Later" -- 07-04-2013
1.2 "Right Now" ------------ 06-10-2013
1.1 "Mean Street" ---------- 04-02-2013
1.0 "Eruption" ------------- 02-01-2013
0.9 "Dreams" --------------- 11-30-2012
0.8 "Cathedral" ------------ 10-19-2012
0.7 "Panama" --------------- 09-06-2012
0.6 "Cabo" ----------------- 08-06-2012
0.5 "Amsterdam" ------------ 07-04-2012
0.4 "Unchained" ------------ 05-23-2012
0.3 "Baluchitherium" ------- 04-23-2012
0.0.2 Untitled
0.0.1 Untitled
VERSION RELEASE CODE NAME
++++++++++++++++++++++++++++++
2.2.0 TBD "The Battle of Evermore"
2.1.2 TBD "The Song Remains the Same"
2.1.1 07-28-2016 "The Song Remains the Same"
2.1.0 05-25-2016 "The Song Remains the Same"
2.0.2 04-19-2016 "Over the Hills and Far Away"
2.0.1 02-24-2016 "Over the Hills and Far Away"
2.0.0 01-12-2016 "Over the Hills and Far Away"
1.9.6 04-15-2016 "Dancing In the Streets"
1.9.5 03-21-2016 "Dancing In the Streets"
1.9.4 10-09-2015 "Dancing In the Streets"
1.9.3 09-03-2015 "Dancing In the Streets"
1.9.2 06-24-2015 "Dancing In the Streets"
1.9.1 04-27-2015 "Dancing In the Streets"
1.9.0 03-25-2015 "Dancing In the Streets"
1.8.4 02-19-2015 "You Really Got Me"
1.8.3 02-17-2015 "You Really Got Me"
1.8.2 12-04-2014 "You Really Got Me"
1.8.1 11-26-2014 "You Really Got Me"
1.8.0 ? "You Really Got Me"
1.7.2 09-24-2014 "Summer Nights"
1.7.1 08-14-2014 "Summer Nights"
1.7.0 08-06-2014 "Summer Nights"
1.6.10 07-25-2014 "The Cradle Will Rock"
1.6.9 07-24-2014 "The Cradle Will Rock"
1.6.8 07-22-2014 "The Cradle Will Rock"
1.6.7 07-21-2014 "The Cradle Will Rock"
1.6.6 07-01-2014 "The Cradle Will Rock"
1.6.5 06-25-2014 "The Cradle Will Rock"
1.6.4 06-25-2014 "The Cradle Will Rock"
1.6.3 06-09-2014 "The Cradle Will Rock"
1.6.2 05-23-2014 "The Cradle Will Rock"
1.6.1 05-07-2014 "The Cradle Will Rock"
1.6.0 05-05-2014 "The Cradle Will Rock"
1.5.5 04-18-2014 "Love Walks In"
1.5.4 04-01-2014 "Love Walks In"
1.5.3 03-13-2014 "Love Walks In"
1.5.2 03-11-2014 "Love Walks In"
1.5.1 03-10-2014 "Love Walks In"
1.5.0 02-28-2014 "Love Walks In"
1.4.5 02-12-2014 "Could This Be Magic?"
1.4.4 01-06-2014 "Could This Be Magic?"
1.4.3 12-20-2013 "Could This Be Magic?"
1.4.2 12-18-2013 "Could This Be Magic?"
1.4.1 11-27-2013 "Could This Be Magic?"
1.4.0 11-21-2013 "Could This Be Magic?"
1.3.4 10-29-2013 "Top of the World"
1.3.3 10-09-2013 "Top of the World"
1.3.2 09-19-2013 "Top of the World"
1.3.1 09-16-2013 "Top of the World"
1.3.0 09-13-2013 "Top of the World"
1.2.3 08-21-2013 "Right Now"
1.2.2 07-05-2013 "Right Now"
1.2.1 07-04-2013 "Right Now"
1.2.0 06-10-2013 "Right Now"
1.1.0 04-02-2013 "Mean Street"
1.0.0 02-01-2013 "Eruption"
0.9.0 11-30-2012 "Dreams"
0.8.0 10-19-2012 "Cathedral"
0.7.0 09-06-2012 "Panama"
0.6.0 08-06-2012 "Cabo"
0.5.0 07-04-2012 "Amsterdam"
0.4.0 05-23-2012 "Unchained"
0.3.0 04-23-2012 "Baluchitherium"
0.2.0 ? "Untitled"
0.1.0 ? "Untitled"
0.0.2 ? "Untitled"
0.0.1 ? "Untitled"

View File

@ -1 +1 @@
2.1.0
2.1.2.0 1

View File

@ -9,6 +9,9 @@
# Control which tags are included. Set tags to a comma separated list of keys or key:value pairs
#tags=
# Control which locations are included. Set locations to a comma separated list (e.g. eastus,eastus2,westus)
#locations=
# Include powerstate. If you don't need powerstate information, turning it off improves runtime performance.
include_powerstate=yes

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python
#
# Copyright (c) 2016 Matt Davis, <mdavis@ansible.com>
# Chris Houseknecht, <house@redhat.com>
@ -130,6 +130,10 @@ Select hosts for specific tag key by assigning a comma separated list of tag key
AZURE_TAGS=key1,key2,key3
Select hosts for specific locations:
AZURE_LOCATIONS=eastus,westus,eastus2
Or, select hosts for specific tag key:value pairs by assigning a comma separated list key:value pairs to:
AZURE_TAGS=key1:value1,key2:value2
@ -152,13 +156,13 @@ up. If the value is anything other than 'running', the machine is down, and will
Examples:
---------
Execute /bin/uname on all instances in the galaxy-qa resource group
$ ansible -i azure_rm_inventory.py galaxy-qa -m shell -a "/bin/uname -a"
$ ansible -i azure_rm.py galaxy-qa -m shell -a "/bin/uname -a"
Use the inventory script to print instance specific information
$ contrib/inventory/azure_rm_inventory.py --host my_instance_host_name --pretty
$ contrib/inventory/azure_rm.py --host my_instance_host_name --pretty
Use with a playbook
$ ansible-playbook -i contrib/inventory/azure_rm_inventory.py my_playbook.yml --limit galaxy-qa
$ ansible-playbook -i contrib/inventory/azure_rm.py my_playbook.yml --limit galaxy-qa
Insecure Platform Warning
@ -180,11 +184,13 @@ Version: 1.0.0
import argparse
import ConfigParser
import json
import json
import os
import re
import sys
from distutils.version import LooseVersion
from os.path import expanduser
HAS_AZURE = True
@ -195,12 +201,9 @@ try:
from azure.mgmt.compute import __version__ as azure_compute_version
from azure.common import AzureMissingResourceHttpError, AzureHttpError
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
from azure.mgmt.network.network_management_client import NetworkManagementClient,\
NetworkManagementClientConfiguration
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient,\
ResourceManagementClientConfiguration
from azure.mgmt.compute.compute_management_client import ComputeManagementClient,\
ComputeManagementClientConfiguration
from azure.mgmt.network.network_management_client import NetworkManagementClient
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient
from azure.mgmt.compute.compute_management_client import ComputeManagementClient
except ImportError as exc:
HAS_AZURE_EXC = exc
HAS_AZURE = False
@ -219,6 +222,7 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict(
AZURE_CONFIG_SETTINGS = dict(
resource_groups='AZURE_RESOURCE_GROUPS',
tags='AZURE_TAGS',
locations='AZURE_LOCATIONS',
include_powerstate='AZURE_INCLUDE_POWERSTATE',
group_by_resource_group='AZURE_GROUP_BY_RESOURCE_GROUP',
group_by_location='AZURE_GROUP_BY_LOCATION',
@ -226,7 +230,7 @@ AZURE_CONFIG_SETTINGS = dict(
group_by_tag='AZURE_GROUP_BY_TAG'
)
AZURE_MIN_VERSION = "2016-03-30"
AZURE_MIN_VERSION = "0.30.0rc5"
def azure_id_to_dict(id):
@ -362,8 +366,7 @@ class AzureRM(object):
def network_client(self):
self.log('Getting network client')
if not self._network_client:
self._network_client = NetworkManagementClient(
NetworkManagementClientConfiguration(self.azure_credentials, self.subscription_id))
self._network_client = NetworkManagementClient(self.azure_credentials, self.subscription_id)
self._register('Microsoft.Network')
return self._network_client
@ -371,16 +374,14 @@ class AzureRM(object):
def rm_client(self):
self.log('Getting resource manager client')
if not self._resource_client:
self._resource_client = ResourceManagementClient(
ResourceManagementClientConfiguration(self.azure_credentials, self.subscription_id))
self._resource_client = ResourceManagementClient(self.azure_credentials, self.subscription_id)
return self._resource_client
@property
def compute_client(self):
self.log('Getting compute client')
if not self._compute_client:
self._compute_client = ComputeManagementClient(
ComputeManagementClientConfiguration(self.azure_credentials, self.subscription_id))
self._compute_client = ComputeManagementClient(self.azure_credentials, self.subscription_id)
self._register('Microsoft.Compute')
return self._compute_client
@ -403,6 +404,7 @@ class AzureInventory(object):
self.resource_groups = []
self.tags = None
self.locations = None
self.replace_dash_in_groups = False
self.group_by_resource_group = True
self.group_by_location = True
@ -425,6 +427,9 @@ class AzureInventory(object):
if self._args.tags:
self.tags = self._args.tags.split(',')
if self._args.locations:
self.locations = self._args.locations.split(',')
if self._args.no_powerstate:
self.include_powerstate = False
@ -462,6 +467,8 @@ class AzureInventory(object):
help='Return inventory for comma separated list of resource group names')
parser.add_argument('--tags', action='store',
help='Return inventory for comma separated list of tag key:value pairs')
parser.add_argument('--locations', action='store',
help='Return inventory for comma separated list of locations')
parser.add_argument('--no-powerstate', action='store_true', default=False,
help='Do not include the power state of each virtual host')
return parser.parse_args()
@ -487,7 +494,7 @@ class AzureInventory(object):
except Exception as exc:
sys.exit("Error: fetching virtual machines - {0}".format(str(exc)))
if self._args.host or self.tags > 0:
if self._args.host or self.tags or self.locations:
selected_machines = self._selected_machines(virtual_machines)
self._load_machines(selected_machines)
else:
@ -524,7 +531,7 @@ class AzureInventory(object):
resource_group=resource_group,
mac_address=None,
plan=(machine.plan.name if machine.plan else None),
virtual_machine_size=machine.hardware_profile.vm_size.value,
virtual_machine_size=machine.hardware_profile.vm_size,
computer_name=machine.os_profile.computer_name,
provisioning_state=machine.provisioning_state,
)
@ -576,7 +583,7 @@ class AzureInventory(object):
host_vars['mac_address'] = network_interface.mac_address
for ip_config in network_interface.ip_configurations:
host_vars['private_ip'] = ip_config.private_ip_address
host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method.value
host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method
if ip_config.public_ip_address:
public_ip_reference = self._parse_ref_id(ip_config.public_ip_address.id)
public_ip_address = self._network_client.public_ip_addresses.get(
@ -585,7 +592,7 @@ class AzureInventory(object):
host_vars['ansible_host'] = public_ip_address.ip_address
host_vars['public_ip'] = public_ip_address.ip_address
host_vars['public_ip_name'] = public_ip_address.name
host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method.value
host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method
host_vars['public_ip_id'] = public_ip_address.id
if public_ip_address.dns_settings:
host_vars['fqdn'] = public_ip_address.dns_settings.fqdn
@ -599,6 +606,8 @@ class AzureInventory(object):
selected_machines.append(machine)
if self.tags and self._tags_match(machine.tags, self.tags):
selected_machines.append(machine)
if self.locations and machine.location in self.locations:
selected_machines.append(machine)
return selected_machines
def _get_security_groups(self, resource_group):
@ -676,17 +685,17 @@ class AzureInventory(object):
file_settings = self._load_settings()
if file_settings:
for key in AZURE_CONFIG_SETTINGS:
if key in ('resource_groups', 'tags') and file_settings.get(key, None) is not None:
if key in ('resource_groups', 'tags', 'locations') and file_settings.get(key):
values = file_settings.get(key).split(',')
if len(values) > 0:
setattr(self, key, values)
elif file_settings.get(key, None) is not None:
elif file_settings.get(key):
val = self._to_boolean(file_settings[key])
setattr(self, key, val)
else:
env_settings = self._get_env_settings()
for key in AZURE_CONFIG_SETTINGS:
if key in('resource_groups', 'tags') and env_settings.get(key, None) is not None:
if key in('resource_groups', 'tags', 'locations') and env_settings.get(key):
values = env_settings.get(key).split(',')
if len(values) > 0:
setattr(self, key, values)
@ -774,11 +783,11 @@ class AzureInventory(object):
def main():
if not HAS_AZURE:
sys.exit("The Azure python sdk is not installed (try 'pip install azure') - {0}".format(HAS_AZURE_EXC))
sys.exit("The Azure python sdk is not installed (try 'pip install azure==2.0.0rc5') - {0}".format(HAS_AZURE_EXC))
if azure_compute_version < AZURE_MIN_VERSION:
sys.exit("Expecting azure.mgmt.compute.__version__ to be >= {0}. Found version {1} "
"Do you have Azure >= 2.0.0rc2 installed?".format(AZURE_MIN_VERSION, azure_compute_version))
if LooseVersion(azure_compute_version) != LooseVersion(AZURE_MIN_VERSION):
sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} "
"Do you have Azure == 2.0.0rc5 installed?".format(AZURE_MIN_VERSION, azure_compute_version))
AzureInventory()

View File

@ -24,9 +24,16 @@ DOCUMENTATION = '''
Docker Inventory Script
=======================
Generates dynamic inventory by making API requests to one or more Docker daemons. Communicates with the API
by way of docker-py (https://docker-py.readthedocs.org/en/stable/). So before running the script, you will need to
install docker-py:
The inventory script generates dynamic inventory by making API requests to one or more Docker APIs. It's dynamic
because the inventory is generated at run-time rather than being read from a static file. The script generates the
inventory by connecting to one or many Docker APIs and inspecting the containers it finds at each API. Which APIs the
script contacts can be defined using environment variables or a configuration file.
Requirements
------------
Using the docker modules requires having docker-py <https://docker-py.readthedocs.org/en/stable/>
installed on the host running Ansible. To install docker-py:
pip install docker-py
@ -197,126 +204,123 @@ When run in --list mode (the default), container instances are grouped by:
Configuration:
--------------
You can control the behavior of the inventory script by passing arguments, defining environment variables, or
creating a docker.yml file (sample provided in ansible/contrib/inventory). The order of precedence is command
line args, then the docker.yml file and finally environment variables.
creating a configuration file named docker.yml (sample provided in ansible/contrib/inventory). The order of precedence
is command line args, then the docker.yml file and finally environment variables.
Environment variables:
;;;;;;;;;;;;;;;;;;;;;;
......................
DOCKER_CONFIG_FILE
description: path to docker inventory configuration file.
default: ./docker.yml
To connect to a single Docker API the following variables can be defined in the environment to control the connection
options. These are the same environment variables used by the Docker modules.
DOCKER_HOST
description: Docker daemon URL or Unix socket path.
default: unix://var/run/docker.sock
DOCKER_HOST
The URL or Unix socket path used to connect to the Docker API. Defaults to unix://var/run/docker.sock.
DOCKER_TLS_HOSTNAME:
description: When DOCKER_TLS_VERIFY is true, provide the expected name of the host.
default: localhost
DOCKER_API_VERSION:
The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
by docker-py.
DOCKER_API_VERSION:
description: Version of the Docker API the client will use.
default: DEFAULT_DOCKER_API_VERSION as defined in docker-py
DOCKER_TIMEOUT:
The maximum amount of time in seconds to wait on a response fromm the API. Defaults to 60 seconds.
DOCKER_CERT_PATH:
description: Path to the directory containing the client certificate and key files.
default: None
DOCKER_TLS:
Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
Defaults to False.
DOCKER_SSL_VERSION:
description: Version of TLS supported by Docker daemon.
default: None
DOCKER_TLS_VERIFY:
Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
Default is False
DOCKER_TLS:
description: Use TLS when sending requests to Docker daemon. Set to 1, 0, true, false, True, False, yes, no.
default: False
DOCKER_TLS_HOSTNAME:
When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
to localhost.
DOCKER_TLS_VERIFY:
description: Verify hostname found in TLS certs. Set to 1, 0, true, false, True, False, yes, no.
default: False
DOCKER_CERT_PATH:
Path to the directory containing the client certificate, client key and CA certificate.
DOCKER_TIMEOUT:
description: Docker request timeout in seconds.
default: Value of DOCKER_TIMEOUT as defined in docker-py
DOCKER_SSL_VERSION:
Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
was 1.0
DOCKER_PRIVATE_SSH_PORT:
description: The private port (container port) on which SSH is listening for connections
default: 22
In addition to the connection variables there are a couple variables used to control the execution and output of the
script:
DOCKER_DEFAULT_IP:
description: This environment variable overrides the container SSH connection
IP address (aka, 'ansible_ssh_host').
DOCKER_CONFIG_FILE
Path to the configuration file. Defaults to ./docker.yml.
This option allows one to override the ansible_ssh_host whenever Docker has exercised its default behavior of
binding private ports to all interfaces of the Docker host. This behavior, when dealing with remote Docker hosts,
does not allow Ansible to determine a proper host IP address on which to connect via SSH to containers. By
default, this inventory module assumes all 0.0.0.0-exposed ports to be bound to localhost:<port>. To override
this behavior, for example, to bind a container's SSH port to the public interface of its host, one must
manually set this IP.
DOCKER_PRIVATE_SSH_PORT:
The private port (container port) on which SSH is listening for connections. Defaults to 22.
It is preferable to begin to launch Docker containers with ports exposed on publicly accessible IP addresses,
particularly if the containers are to be targeted by Ansible for remote configuration, not accessible via
localhost SSH connections. Docker containers can be explicitly exposed on IP addresses by
a) starting the daemon with the --ip argument
b) running containers with the -P/--publish ip::containerPort
argument
default: 127.0.0.1 if port exposed on 0.0.0.0
DOCKER_DEFAULT_IP:
The IP address to assign to ansible_host when the container's SSH port is mapped to interface '0.0.0.0'.
docker.yml
;;;;;;;;;;;;;;;;;;;;
Configuration File
..................
A sample docker.yml file is included in the ansible/contrib/inventory. Using this file is not required. If
the file is not found, environment variables will be used.
Using a configuration file provides a means for defining a set of Docker APIs from which to build an inventory.
The default name of the file is derived from the name of the inventory script. By default the script will look for
basename of the script (i.e. docker) with an extension of '.yml'. You can override the default name by passing a
command line argument or setting DOCKER_CONFIG_FILE in the environment.
basename of the script (i.e. docker) with an extension of '.yml'.
You can also override the default name of the script by defining DOCKER_CONFIG_FILE in the environment.
Here's what you can define in docker_inventory.yml:
* defaults: Defines a default connnection. Defaults will be taken from this and applied to any values not provided
for a host defined in the hosts list.
defaults
Defines a default connection. Defaults will be taken from this and applied to any values not provided
for a host defined in the hosts list.
* hosts: If you wish to get inventory from more than one Docker daemon hosts, define a hosts list.
hosts
If you wish to get inventory from more than one Docker host, define a hosts list.
For a host defined in defaults or hosts, you can provided the following attributes. The only required attribute is host.
For the default host and each host in the hosts list define the following attributes:
host:
description: The URL or Unix socket path for the host.
description: The URL or Unix socket path used to connect to the Docker API.
required: yes
tls:
description: Connect using https://
description: Connect using TLS without verifying the authenticity of the Docker host server.
default: false
required: false
tls_verify:
description: Connect using https:// and verify the host name matches the host name found in the certificate.
description: Connect using TLS without verifying the authenticity of the Docker host server.
default: false
required: false
cert_path:
description: Path to the host's certificate .pem file.
description: Path to the client's TLS certificate file.
default: null
required: false
cacert_path:
description: Path to the host's Certificate Authority .pem file.
description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
default: null
required: false
key_path:
description: Path to the host's encryption key .pem file
description: Path to the client's TLS key file.
default: null
required: false
version:
description: The API version.
description: The Docker API version.
required: false
default: will be supplied by the docker-py module.
timeout:
description: The amount of time in seconds to wait on an API response.
required: false
default: will be supplied by the docker-py module.
default: 60
default_ip:
description: The IP address to assign to ansilbe_host when the container's SSH port is mappped to 0.0.0.0
description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
'0.0.0.0'.
required: false
default: 1267.0.0.1
default: 127.0.0.1
private_ssh_port:
description: The port containers use for SSH
required: false
@ -324,28 +328,25 @@ For a host defined in defaults or hosts, you can provided the following attribut
Examples
--------
# Run the script with Env vars (for when you have Docker toolbox installed)
./docker_inventory.py --pretty
# Connect to docker instance on localhost port 4243
DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty
# Connect to the Docker API on localhost port 4243 and format the JSON output
DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty
# Any container's ssh port exposed on 0.0.0.0 will mapped to
#another IP address (where Ansible will attempt to connect via SSH)
DOCKER_DEFAULT_IP=1.2.3.4 ./docker.py --pretty
# Any container's ssh port exposed on 0.0.0.0 will be mapped to
# another IP address (where Ansible will attempt to connect via SSH)
DOCKER_DEFAULT_IP=1.2.3.4 ./docker.py --pretty
# Run as input to a playbook:
ansible-playbook -i ~/projects/ansible/contrib/inventory/docker_inventory.py docker_inventory_test.yml
# Run as input to a playbook:
ansible-playbook -i ~/projects/ansible/contrib/inventory/docker.py docker_inventory_test.yml
# Simple playbook to invoke with the above example:
# Simple playbook to invoke with the above example:
- name: Test docker_inventory
hosts: all
connection: local
gather_facts: no
tasks:
- debug: msg="Container - {{ inventory_hostname }}"
- debug: msg="Container - {{ inventory_hostname }}"
'''

View File

@ -1,52 +1,65 @@
# This is the configuration file for the Docker inventory script: docker_inventory.py.
#
# defaults: Defines a default connnection. Defaults will be taken from this and applied to any values not provided
# for a host defined in the hosts list.
# You can define the following in this file:
#
# hosts: If you wish to get inventory from more than one Docker daemon hosts, define a hosts list.
#
# For a host defined in defaults or hosts, you can provided the following attributes. The only required attribute is host.
# defaults
# Defines a default connection. Defaults will be taken from this and applied to any values not provided
# for a host defined in the hosts list.
#
# host:
# description: The URL or Unix socket path for the host.
# required: yes
# tls:
# description: Connect using https://
# default: false
# required: false
# tls_verify:
# description: Connect using https:// and verify the host name matches the host name found in the certificate.
# default: false
# required: false
# cert_path:
# description: Path to the client's certificate .pem file.
# default: null
# required: false
# cacert_path:
# description: Path to the client's Certificate Authority .pem file.
# default: null
# required: false
# key_path:
# description: Path to the client's encryption key .pem file
# default: null
# required: false
# version:
# description: The API version the client will use.
# required: false
# default: will be supplied by the docker-py module.
# timeout:
# description: The amount of time in seconds to wait on an API response.
# required: false
# default: will be supplied by the docker-py module.
# default_ip:
# description: The IP address to assign to ansilbe_host when the container's SSH port is mappped to 0.0.0.0
# required: false
# default: 1267.0.0.1
# private_ssh_port:
# description: The port containers use for SSH
# required: false
# default: 22
#
# hosts
# If you wish to get inventory from more than one Docker host, define a hosts list.
#
# For the default host and each host in the hosts list define the following attributes:
#
# host:
# description: The URL or Unix socket path used to connect to the Docker API.
# required: yes
#
# tls:
# description: Connect using TLS without verifying the authenticity of the Docker host server.
# default: false
# required: false
#
# tls_verify:
# description: Connect using TLS without verifying the authenticity of the Docker host server.
# default: false
# required: false
#
# cert_path:
# description: Path to the client's TLS certificate file.
# default: null
# required: false
#
# cacert_path:
# description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
# default: null
# required: false
#
# key_path:
# description: Path to the client's TLS key file.
# default: null
# required: false
#
# version:
# description: The Docker API version.
# required: false
# default: will be supplied by the docker-py module.
#
# timeout:
# description: The amount of time in seconds to wait on an API response.
# required: false
# default: 60
#
# default_ip:
# description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
# '0.0.0.0'.
# required: false
# default: 127.0.0.1
#
# private_ssh_port:
# description: The port containers use for SSH
# required: false
# default: 22
#defaults:
# host: unix:///var/run/docker.sock

View File

@ -45,3 +45,11 @@ gce_service_account_email_address =
gce_service_account_pem_file_path =
gce_project_id =
[inventory]
# The 'inventory_ip_type' parameter specifies whether 'ansible_ssh_host' should
# contain the instance internal or external address. Values may be either
# 'internal' or 'external'. If 'external' is specified but no external instance
# address exists, the internal address will be used.
# The INVENTORY_IP_TYPE environment variable will override this value.
inventory_ip_type =

View File

@ -69,7 +69,8 @@ Examples:
$ contrib/inventory/gce.py --host my_instance
Author: Eric Johnson <erjohnso@google.com>
Version: 0.0.1
Contributors: Matt Hite <mhite@hotmail.com>
Version: 0.0.2
'''
__requires__ = ['pycrypto>=2.6']
@ -83,7 +84,7 @@ except ImportError:
pass
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
USER_AGENT_VERSION="v1"
USER_AGENT_VERSION="v2"
import sys
import os
@ -111,7 +112,11 @@ class GceInventory(object):
def __init__(self):
# Read settings and parse CLI arguments
self.parse_cli_args()
self.config = self.get_config()
self.driver = self.get_gce_driver()
self.ip_type = self.get_inventory_options()
if self.ip_type:
self.ip_type = self.ip_type.lower()
# Just display data for specific host
if self.args.host:
@ -125,9 +130,13 @@ class GceInventory(object):
pretty=self.args.pretty))
sys.exit(0)
def get_gce_driver(self):
"""Determine the GCE authorization settings and return a
libcloud driver.
def get_config(self):
"""
Populates a SafeConfigParser object with defaults and
attempts to read an .ini-style configuration from the filename
specified in GCE_INI_PATH. If the environment variable is
not present, the filename defaults to gce.ini in the current
working directory.
"""
gce_ini_default_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "gce.ini")
@ -142,14 +151,32 @@ class GceInventory(object):
'gce_service_account_pem_file_path': '',
'gce_project_id': '',
'libcloud_secrets': '',
'inventory_ip_type': '',
})
if 'gce' not in config.sections():
config.add_section('gce')
config.read(gce_ini_path)
if 'inventory' not in config.sections():
config.add_section('inventory')
config.read(gce_ini_path)
return config
def get_inventory_options(self):
"""Determine inventory options. Environment variables always
take precedence over configuration files."""
ip_type = self.config.get('inventory', 'inventory_ip_type')
# If the appropriate environment variables are set, they override
# other configuration
ip_type = os.environ.get('INVENTORY_IP_TYPE', ip_type)
return ip_type
def get_gce_driver(self):
"""Determine the GCE authorization settings and return a
libcloud driver.
"""
# Attempt to get GCE params from a configuration file, if one
# exists.
secrets_path = config.get('gce', 'libcloud_secrets')
secrets_path = self.config.get('gce', 'libcloud_secrets')
secrets_found = False
try:
import secrets
@ -175,10 +202,10 @@ class GceInventory(object):
pass
if not secrets_found:
args = [
config.get('gce','gce_service_account_email_address'),
config.get('gce','gce_service_account_pem_file_path')
self.config.get('gce','gce_service_account_email_address'),
self.config.get('gce','gce_service_account_pem_file_path')
]
kwargs = {'project': config.get('gce', 'gce_project_id')}
kwargs = {'project': self.config.get('gce', 'gce_project_id')}
# If the appropriate environment variables are set, they override
# other configuration; process those into our args and kwargs.
@ -218,6 +245,12 @@ class GceInventory(object):
md[entry['key']] = entry['value']
net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
# default to exernal IP unless user has specified they prefer internal
if self.ip_type == 'internal':
ssh_host = inst.private_ips[0]
else:
ssh_host = inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]
return {
'gce_uuid': inst.uuid,
'gce_id': inst.id,
@ -233,7 +266,7 @@ class GceInventory(object):
'gce_metadata': md,
'gce_network': net,
# Hosts don't have a public name, so we add an IP
'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]
'ansible_ssh_host': ssh_host
}
def get_instance(self, instance_name):

View File

@ -63,7 +63,7 @@ CREATE
The *create* sub-command is used to initialize a new encrypted file.
After providing a password, the tool will launch whatever editor you have defined
with $EDITOR, and defaults to vim. Once you are done with the editor session, the
with $EDITOR, and defaults to vi. Once you are done with the editor session, the
file will be saved as encrypted data.
The default cipher is AES (which is shared-secret based).

View File

@ -42,7 +42,7 @@ The 'ARGUMENTS' to pass to the module.
Use privilege escalation (specific one depends on become_method),
this does not imply prompting for passwords.
*K*, *--ask-become-pass*::
*-K*, *--ask-become-pass*::
Ask for privilege escalation password.

View File

@ -8,12 +8,12 @@ Requirements
------------
Using the Azure Resource Manager modules requires having `Azure Python SDK <https://github.com/Azure/azure-sdk-for-python>`_
installed on the host running Ansible. You will need to have >= v2.0.0RC2 installed. The simplest way to install the
installed on the host running Ansible. You will need to have == v2.0.0RC5 installed. The simplest way to install the
SDK is via pip:
.. code-block:: bash
$ pip install azure==2.0.0rc2
$ pip install "azure==2.0.0rc5"
Authenticating with Azure
@ -320,6 +320,10 @@ Select hosts for specific tag key by assigning a comma separated list of tag key
* AZURE_TAGS=key1,key2,key3
Select hosts for specific locations by assigning a comma separated list of locations to:
* AZURE_LOCATIONS=eastus,eastus2,westus
Or, select hosts for specific tag key:value pairs by assigning a comma separated list key:value pairs to:
* AZURE_TAGS=key1:value1,key2:value2
@ -340,6 +344,9 @@ file will contain the following:
# Control which tags are included. Set tags to a comma separated list of keys or key:value pairs
#tags=
# Control which locations are included. Set locations to a comma separated list of locations.
#locations=
# Include powerstate. If you don't need powerstate information, turning it off improves runtime performance.
# Valid values: yes, no, true, false, True, False, 0, 1.
include_powerstate=yes

View File

@ -0,0 +1,317 @@
Getting Started with Docker
===========================
Ansible offers the following modules for orchestrating Docker containers:
docker_service
Use your existing Docker compose files to orchestrate containers on a single Docker daemon or on
Swarm. Supports compose versions 1 and 2.
docker_container
Manages the container lifecycle by providing the ability to create, update, stop, start and destroy a
container.
docker_image
Provides full control over images, including: build, pull, push, tag and remove.
docker_image_facts
Inspects one or more images in the Docker host's image cache, providing the information as facts for making
decision or assertions in a playbook.
docker_login
Authenticates with Docker Hub or any Docker registry and updates the Docker Engine config file, which
in turn provides password-free pushing and pulling of images to and from the registry.
docker (dynamic inventory)
Dynamically builds an inventory of all the available containers from a set of one or more Docker hosts.
Ansible 2.1.0 includes major updates to the Docker modules, marking the start of a project to create a complete and
integrated set of tools for orchestrating containers. In addition to the above modules, we are also working on the
following:
Still using Dockerfile to build images? Check out `ansible-container <https://github.com/ansible/ansible-container>`_,
and start building images from your Ansible playbooks.
Use the *shipit* command in `ansible-container <https://github.com/ansible/ansible-container>`_
to launch your docker-compose file on `OpenShift <https://www.openshift.org/>`_. Go from an app on your laptop to a fully
scalable app in the cloud in just a few moments.
There's more planned. See the latest ideas and thinking at the `Ansible proposal repo <https://github.com/ansible/proposals/tree/master/docker>`_.
Requirements
------------
Using the docker modules requires having `docker-py <https://docker-py.readthedocs.org/en/stable/>`_
installed on the host running Ansible. You will need to have >= 1.7.0 installed.
.. code-block:: bash
$ pip install 'docker-py>=1.7.0'
The docker_service module also requires `docker-compose <https://github.com/docker/compose>`_
.. code-block:: bash
$ pip install 'docker-compose>=1.7.0'
Connecting to the Docker API
----------------------------
You can connect to a local or remote API using parameters passed to each task or by setting environment variables.
The order of precedence is command line parameters and then environment variables. If neither a command line
option or an environment variable is found, a default value will be used. The default values are provided under
`Parameters`_
Parameters
..........
Control how modules connect to the Docker API by passing the following parameters:
docker_host
The URL or Unix socket path used to connect to the Docker API. Defaults to ``unix://var/run/docker.sock``.
To connect to a remote host, provide the TCP connection string. For example: ``tcp://192.168.99.100:2376``. If
TLS is used to encrypt the connection to the API, then the module will automatically replace 'tcp' in the
connection URL with 'https'.
api_version
The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
by docker-py.
timeout
The maximum amount of time in seconds to wait on a response from the API. Defaults to 60 seconds.
tls
Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
Defaults to False.
tls_verify
Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
Default is False.
cacert_path
Use a CA certificate when performing server verification by providing the path to a CA certificate file.
cert_path
Path to the client's TLS certificate file.
key_path
Path to the client's TLS key file.
tls_hostname
When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
to 'localhost'.
ssl_version
Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
was 1.0
Environment Variables
.....................
Control how the modules connect to the Docker API by setting the following variables in the environment of the host
running Ansible:
DOCKER_HOST
The URL or Unix socket path used to connect to the Docker API.
DOCKER_API_VERSION
The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
by docker-py.
DOCKER_TIMEOUT
The maximum amount of time in seconds to wait on a response from the API.
DOCKER_CERT_PATH
Path to the directory containing the client certificate, client key and CA certificate.
DOCKER_SSL_VERSION
Provide a valid SSL version number.
DOCKER_TLS
Secure the connection to the API by using TLS without verifying the authenticity of the Docker Host.
DOCKER_TLS_VERIFY
Secure the connection to the API by using TLS and verify the authenticity of the Docker Host.
Dynamic Inventory Script
------------------------
The inventory script generates dynamic inventory by making API requests to one or more Docker APIs. It's dynamic
because the inventory is generated at run-time rather than being read from a static file. The script generates the
inventory by connecting to one or many Docker APIs and inspecting the containers it finds at each API. Which APIs the
script contacts can be defined using environment variables or a configuration file.
Groups
......
The script will create the following host groups:
- container id
- container name
- container short id
- image_name (image_<image name>)
- docker_host
- running
- stopped
Examples
........
You can run the script interactively from the command line or pass it as the inventory to a playbook. Here are few
examples to get you started:
.. code-block:: bash
# Connect to the Docker API on localhost port 4243 and format the JSON output
DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty
# Any container's ssh port exposed on 0.0.0.0 will be mapped to
# another IP address (where Ansible will attempt to connect via SSH)
DOCKER_DEFAULT_IP=1.2.3.4 ./docker.py --pretty
# Run as input to a playbook:
ansible-playbook -i ~/projects/ansible/contrib/inventory/docker.py docker_inventory_test.yml
# Simple playbook to invoke with the above example:
- name: Test docker_inventory
hosts: all
connection: local
gather_facts: no
tasks:
- debug: msg="Container - {{ inventory_hostname }}"
Configuration
.............
You can control the behavior of the inventory script by defining environment variables, or
creating a docker.yml file (sample provided in ansible/contrib/inventory). The order of precedence is the docker.yml
file and then environment variables.
Environment Variables
;;;;;;;;;;;;;;;;;;;;;;
To connect to a single Docker API the following variables can be defined in the environment to control the connection
options. These are the same environment variables used by the Docker modules.
DOCKER_HOST
The URL or Unix socket path used to connect to the Docker API. Defaults to unix://var/run/docker.sock.
DOCKER_API_VERSION:
The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
by docker-py.
DOCKER_TIMEOUT:
The maximum amount of time in seconds to wait on a response fromm the API. Defaults to 60 seconds.
DOCKER_TLS:
Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
Defaults to False.
DOCKER_TLS_VERIFY:
Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
Default is False
DOCKER_TLS_HOSTNAME:
When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
to localhost.
DOCKER_CERT_PATH:
Path to the directory containing the client certificate, client key and CA certificate.
DOCKER_SSL_VERSION:
Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
was 1.0
In addition to the connection variables there are a couple variables used to control the execution and output of the
script:
DOCKER_CONFIG_FILE
Path to the configuration file. Defaults to ./docker.yml.
DOCKER_PRIVATE_SSH_PORT:
The private port (container port) on which SSH is listening for connections. Defaults to 22.
DOCKER_DEFAULT_IP:
The IP address to assign to ansible_host when the container's SSH port is mapped to interface '0.0.0.0'.
Configuration File
;;;;;;;;;;;;;;;;;;
Using a configuration file provides a means for defining a set of Docker APIs from which to build an inventory.
The default name of the file is derived from the name of the inventory script. By default the script will look for
basename of the script (i.e. docker) with an extension of '.yml'.
You can also override the default name of the script by defining DOCKER_CONFIG_FILE in the environment.
Here's what you can define in docker_inventory.yml:
defaults
Defines a default connection. Defaults will be taken from this and applied to any values not provided
for a host defined in the hosts list.
hosts
If you wish to get inventory from more than one Docker host, define a hosts list.
For the default host and each host in the hosts list define the following attributes:
.. code-block:: yaml
host:
description: The URL or Unix socket path used to connect to the Docker API.
required: yes
tls:
description: Connect using TLS without verifying the authenticity of the Docker host server.
default: false
required: false
tls_verify:
description: Connect using TLS without verifying the authenticity of the Docker host server.
default: false
required: false
cert_path:
description: Path to the client's TLS certificate file.
default: null
required: false
cacert_path:
description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
default: null
required: false
key_path:
description: Path to the client's TLS key file.
default: null
required: false
version:
description: The Docker API version.
required: false
default: will be supplied by the docker-py module.
timeout:
description: The amount of time in seconds to wait on an API response.
required: false
default: 60
default_ip:
description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
'0.0.0.0'.
required: false
default: 127.0.0.1
private_ssh_port:
description: The port containers use for SSH
required: false
default: 22

View File

@ -11,7 +11,7 @@ Introduction
Ansible contains modules for managing Google Compute Engine resources, including creating instances, controlling network access, working with persistent disks, and managing
load balancers. Additionally, there is an inventory plugin that can automatically suck down all of your GCE instances into Ansible dynamic inventory, and create groups by tag and other properties.
The GCE modules all require the apache-libcloud module, which you can install from pip:
The GCE modules all require the apache-libcloud module which you can install from pip:
.. code-block:: bash
@ -22,16 +22,19 @@ The GCE modules all require the apache-libcloud module, which you can install fr
Credentials
-----------
To work with the GCE modules, you'll first need to get some credentials. You can create new one from the `console <https://console.developers.google.com/>`_ by going to the "APIs and Auth" section and choosing to create a new client ID for a service account. Once you've created a new client ID and downloaded (you must click **Generate new P12 Key**) the generated private key (in the `pkcs12 format <http://en.wikipedia.org/wiki/PKCS_12>`_), you'll need to convert the key by running the following command:
To work with the GCE modules, you'll first need to get some credentials in the
JSON format:
.. code-block:: bash
1. `Create a Service Account <https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount>`_
2. `Download JSON credentials <https://support.google.com/cloud/answer/6158849?hl=en&ref_topic=6262490#serviceaccounts>`_
$ openssl pkcs12 -in pkey.pkcs12 -passin pass:notasecret -nodes -nocerts | openssl rsa -out pkey.pem
There are three different ways to provide credentials to Ansible so that it can talk with Google Cloud for provisioning and configuration actions:
There are two different ways to provide credentials to Ansible so that it can talk with Google Cloud for provisioning and configuration actions:
.. note:: If you would like to use JSON credentials you must have libcloud >= 0.17.0
* by providing to the modules directly
* by populating a ``secrets.py`` file
* by setting environment variables
Calling Modules By Passing Credentials
``````````````````````````````````````
@ -39,7 +42,7 @@ Calling Modules By Passing Credentials
For the GCE modules you can specify the credentials as arguments:
* ``service_account_email``: email associated with the project
* ``pem_file``: path to the pem file
* ``credentials_file``: path to the JSON credentials file
* ``project_id``: id of the project
For example, to create a new instance using the cloud module, you can use the following configuration:
@ -48,12 +51,12 @@ For example, to create a new instance using the cloud module, you can use the fo
- name: Create instance(s)
hosts: localhost
connection: local
connection: local
gather_facts: no
vars:
service_account_email: unique-id@developer.gserviceaccount.com
pem_file: /path/to/project.pem
credentials_file: /path/to/project.json
project_id: project-id
machine_type: n1-standard-1
image: debian-7
@ -61,28 +64,50 @@ For example, to create a new instance using the cloud module, you can use the fo
tasks:
- name: Launch instances
gce:
instance_names: dev
gce:
instance_names: dev
machine_type: "{{ machine_type }}"
image: "{{ image }}"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
credentials_file: "{{ credentials_file }}"
project_id: "{{ project_id }}"
Calling Modules with secrets.py
```````````````````````````````
When running Ansible inside a GCE VM you can use the service account credentials from the local metadata server by
setting both ``service_account_email`` and ``credentials_file`` to a blank string.
Configuring Modules with secrets.py
```````````````````````````````````
Create a file ``secrets.py`` looking like following, and put it in some folder which is in your ``$PYTHONPATH``:
.. code-block:: python
GCE_PARAMS = ('i...@project.googleusercontent.com', '/path/to/project.pem')
GCE_PARAMS = ('i...@project.googleusercontent.com', '/path/to/project.json')
GCE_KEYWORD_PARAMS = {'project': 'project_id'}
Ensure to enter the email address from the created services account and not the one from your main account.
Now the modules can be used as above, but the account information can be omitted.
If you are running Ansible from inside a GCE VM with an authorized service account you can set the email address and
credentials path as follows so that get automatically picked up:
.. code-block:: python
GCE_PARAMS = ('', '')
GCE_KEYWORD_PARAMS = {'project': 'project_id'}
Configuring Modules with Environment Variables
``````````````````````````````````````````````
Set the following environment variables before running Ansible in order to configure your credentials:
.. code-block:: bash
GCE_EMAIL
GCE_PROJECT
GCE_CREDENTIALS_FILE_PATH
GCE Dynamic Inventory
---------------------
@ -171,7 +196,7 @@ A playbook would looks like this:
machine_type: n1-standard-1 # default
image: debian-7
service_account_email: unique-id@developer.gserviceaccount.com
pem_file: /path/to/project.pem
credentials_file: /path/to/project.json
project_id: project-id
tasks:
@ -181,7 +206,7 @@ A playbook would looks like this:
machine_type: "{{ machine_type }}"
image: "{{ image }}"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
credentials_file: "{{ credentials_file }}"
project_id: "{{ project_id }}"
tags: webserver
register: gce
@ -224,7 +249,7 @@ a basic example of what is possible::
machine_type: n1-standard-1 # default
image: debian-7
service_account_email: unique-id@developer.gserviceaccount.com
pem_file: /path/to/project.pem
credentials_file: /path/to/project.json
project_id: project-id
roles:
@ -238,13 +263,12 @@ a basic example of what is possible::
args:
fwname: "all-http"
name: "default"
allowed: "tcp:80"
state: "present"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
allowed: "tcp:80"
state: "present"
service_account_email: "{{ service_account_email }}"
credentials_file: "{{ credentials_file }}"
project_id: "{{ project_id }}"
By pointing your browser to the IP of the server, you should see a page welcoming you.
Upgrades to this documentation are welcome, hit the github link at the top right of this page if you would like to make additions!
Upgrades to this documentation are welcome, hit the github link at the top right of this page if you would like to make additions!

View File

@ -1,7 +1,7 @@
Detailed Guides
```````````````
This section is new and evolving. The idea here is explore particular use cases in greater depth and provide a more "top down" explanation of some basic features.
This section is new and evolving. The idea here is to explore particular use cases in greater depth and provide a more "top down" explanation of some basic features.
.. toctree::
:maxdepth: 1
@ -13,5 +13,6 @@ This section is new and evolving. The idea here is explore particular use cases
guide_cloudstack
guide_vagrant
guide_rolling_upgrade
guide_docker
Pending topics may include: Docker, Jenkins, Google Compute Engine, Linode/DigitalOcean, Continuous Deployment, and more.

View File

@ -149,7 +149,7 @@ Callbacks are pieces of code in ansible that get called on specific events, perm
This is a developer-centric feature that allows low-level extensions around Ansible to be loaded from
different locations::
callback_plugins = ~/.ansible/plugins/callback_plugins/:/usr/share/ansible_plugins/callback_plugins
callback_plugins = ~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback
Most users will not need to use this feature. See :doc:`developing_plugins` for more details

View File

@ -801,11 +801,11 @@ In 2.x, we have made the order of precedence more specific (with the last listed
* playbook group_vars
* playbook host_vars
* host facts
* registered vars
* set_facts
* play vars
* play vars_prompt
* play vars_files
* registered vars
* set_facts
* role and include vars
* block vars (only for tasks in block)
* task vars (only for the task)

View File

@ -29,7 +29,7 @@ To create a new encrypted data file, run the following command::
First you will be prompted for a password. The password used with vault currently must be the same for all files you wish to use together at the same time.
After providing a password, the tool will launch whatever editor you have defined with $EDITOR, and defaults to vim. Once you are done with the editor session, the file will be saved as encrypted data.
After providing a password, the tool will launch whatever editor you have defined with $EDITOR, and defaults to vi (before 2.1 the default was vim). Once you are done with the editor session, the file will be saved as encrypted data.
The default cipher is AES (which is shared-secret based).

View File

@ -331,7 +331,7 @@
#libvirt_lxc_noseclabel = yes
[colors]
#higlight = white
#highlight = white
#verbose = blue
#warn = bright purple
#error = red

View File

@ -8,6 +8,15 @@ set PREFIX_PYTHONPATH $ANSIBLE_HOME/lib
set PREFIX_PATH $ANSIBLE_HOME/bin
set PREFIX_MANPATH $ANSIBLE_HOME/docs/man
# set quiet flag
if set -q argv
switch $argv
case '-q' '--quiet'
set QUIET "true"
case '*'
end
end
# Set PYTHONPATH
if not set -q PYTHONPATH
set -gx PYTHONPATH $PREFIX_PYTHONPATH
@ -15,7 +24,9 @@ else
switch PYTHONPATH
case "$PREFIX_PYTHONPATH*"
case "*"
echo "Appending PYTHONPATH"
if not [ $QUIET ]
echo "Appending PYTHONPATH"
end
set -gx PYTHONPATH "$PREFIX_PYTHONPATH:$PYTHONPATH"
end
end
@ -38,7 +49,11 @@ set -gx ANSIBLE_LIBRARY $ANSIBLE_HOME/library
# Generate egg_info so that pkg_resources works
pushd $ANSIBLE_HOME
python setup.py egg_info
if [ $QUIET ]
python setup.py -q egg_info
else
python setup.py egg_info
end
if test -e $PREFIX_PYTHONPATH/ansible*.egg-info
rm -r $PREFIX_PYTHONPATH/ansible*.egg-info
end
@ -47,22 +62,19 @@ find . -type f -name "*.pyc" -delete
popd
if set -q argv
switch $argv
case '-q' '--quiet'
case '*'
echo ""
echo "Setting up Ansible to run out of checkout..."
echo ""
echo "PATH=$PATH"
echo "PYTHONPATH=$PYTHONPATH"
echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY"
echo "MANPATH=$MANPATH"
echo ""
echo "Remember, you may wish to specify your host file with -i"
echo ""
echo "Done!"
echo ""
end
if not [ $QUIET ]
echo ""
echo "Setting up Ansible to run out of checkout..."
echo ""
echo "PATH=$PATH"
echo "PYTHONPATH=$PYTHONPATH"
echo "ANSIBLE_LIBRARY=$ANSIBLE_LIBRARY"
echo "MANPATH=$MANPATH"
echo ""
echo "Remember, you may wish to specify your host file with -i"
echo ""
echo "Done!"
echo ""
end
set -e QUIET

View File

@ -175,7 +175,7 @@ def ziploader_setup(modfile, modname):
print("* ziploader module detected; extracted module source to: %s" % debug_dir)
return modfile, argsfile
def runtest(modstyle, modfile, argspath, modname, module_style):
def runtest(modfile, argspath, modname, module_style):
"""Test run a module, piping it's output for reporting."""
if module_style == 'ziploader':
modfile, argspath = ziploader_setup(modfile, modname)

View File

@ -19,5 +19,10 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
__version__ = '2.1.0'
__author__ = 'Ansible, Inc.'
# Note: Do not add any code to this file. The ansible module may be
# a namespace package when using Ansible-2.1+ Anything in this file may not be
# available if one of the other packages in the namespace is loaded first.
#
# This is for backwards compat. Code should be ported to get these from
# ansible.release instead of from here.
from ansible.release import __version__, __author__

View File

@ -30,7 +30,7 @@ import getpass
import signal
import subprocess
from ansible import __version__
from ansible.release import __version__
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.utils.unicode import to_bytes, to_unicode
@ -90,7 +90,10 @@ class CLI(object):
break
if not self.action:
raise AnsibleOptionsError("Missing required action")
# if no need for action if version/help
tmp_options, tmp_args = self.parser.parse_args()
if not(hasattr(tmp_options, 'help') and tmp_options.help) or (hasattr(tmp_options, 'version') and tmp_options.version):
raise AnsibleOptionsError("Missing required action")
def execute(self):
"""
@ -476,7 +479,7 @@ class CLI(object):
display.display(text)
else:
self.pager_pipe(text, os.environ['PAGER'])
elif subprocess.call('(less --version) 2> /dev/null', shell = True) == 0:
elif subprocess.call('(less --version) &> /dev/null', shell = True) == 0:
self.pager_pipe(text, 'less')
else:
display.display(text)

View File

@ -130,7 +130,7 @@ class AdHocCLI(CLI):
variable_manager.set_inventory(inventory)
no_hosts = False
if len(inventory.list_hosts(pattern)) == 0:
if len(inventory.list_hosts()) == 0:
# Empty inventory
display.warning("provided hosts list is empty, only localhost is available")
no_hosts = True
@ -139,7 +139,7 @@ class AdHocCLI(CLI):
hosts = inventory.list_hosts(pattern)
if len(hosts) == 0 and no_hosts is False:
# Invalid limit
raise AnsibleError("Specified --limit does not match any hosts")
raise AnsibleError("Specified hosts and/or --limit does not match any hosts")
if self.options.listhosts:
display.display(' hosts (%d):' % len(hosts))

View File

@ -38,7 +38,7 @@ import sys
from ansible import constants as C
from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.errors import AnsibleError
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory import Inventory
@ -301,7 +301,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
def do_become(self, arg):
"""Toggle whether plays run with become"""
if arg:
self.options.become_user = arg
self.options.become = C.mk_boolean(arg)
display.v("become changed to %s" % self.options.become)
self.set_prompt()
else:
@ -419,13 +419,19 @@ class ConsoleCLI(CLI, cmd.Cmd):
self.inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=self.options.inventory)
self.variable_manager.set_inventory(self.inventory)
if len(self.inventory.list_hosts(self.pattern)) == 0:
no_hosts = False
if len(self.inventory.list_hosts()) == 0:
# Empty inventory
no_hosts = True
display.warning("provided hosts list is empty, only localhost is available")
self.inventory.subset(self.options.subset)
hosts = self.inventory.list_hosts(self.pattern)
if len(hosts) == 0 and not no_hosts:
raise AnsibleError("Specified hosts and/or --limit does not match any hosts")
self.groups = self.inventory.list_groups()
self.hosts = [x.name for x in self.inventory.list_hosts(self.pattern)]
self.hosts = [x.name for x in hosts]
# This hack is to work around readline issues on a mac:
# http://stackoverflow.com/a/7116997/541202

View File

@ -51,7 +51,7 @@ class GalaxyCLI(CLI):
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
@ -65,95 +65,71 @@ class GalaxyCLI(CLI):
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
)
self.set_action()
# options specific to actions
# common
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_true', dest='ignore_certs', default=C.GALAXY_IGNORE_CERTS, help='Ignore SSL certificate validation errors.')
# specific to actions
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.')
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")
self.parser.add_option('-p', '--init-path', dest='init_path', default="./",
help='The path in which the skeleton role will be created. The default is the current working directory.')
self.parser.add_option(
'--offline', dest='offline', default=False, action='store_true',
help="Don't query the galaxy API when creating roles")
self.parser.add_option('-p', '--init-path', dest='init_path', default="./", help='The path in which the skeleton role will be created. The default is the current working directory.')
elif self.action == "install":
self.parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,version] | tar_file(s)]")
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
help='Ignore errors and continue with the next specified role.')
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False,
help='Don\'t download roles listed as dependencies')
self.parser.add_option('-r', '--role-file', dest='role_file',
help='A file containing a list of roles to be imported')
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False, help='Ignore errors and continue with the next specified role.')
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False, help='Don\'t download roles listed as dependencies')
self.parser.add_option('-r', '--role-file', dest='role_file', help='A file containing a list of roles to be imported')
elif self.action == "remove":
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.')
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.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]")
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.add_option('--author', dest='author', help='GitHub 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.')
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 in ['init', 'info']:
self.parser.add_option( '--offline', dest='offline', default=False, action='store_true', help="Don't query the galaxy API when creating roles")
if not self.action in ("delete","import","init","login","setup"):
# NOTE: while the option type=str, the default is a list, and the
# callback will set the value to a list.
self.parser.add_option('-p', '--roles-path', dest='roles_path',
action="callback", callback=CLI.expand_paths,
type=str, 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 ("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_true', dest='ignore_certs', default=False,
help='Ignore SSL certificate validation errors.')
self.parser.add_option('-p', '--roles-path', dest='roles_path', action="callback", callback=CLI.expand_paths, type=str, 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 ("init","install"):
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False,
help='Force overwriting an existing role')
self.parser.add_option('-f', '--force', dest='force', action='store_true', default=False, help='Force overwriting an existing role')
self.options, self.args =self.parser.parse_args()
display.verbosity = self.options.verbosity
self.galaxy = Galaxy(self.options)
return True
def run(self):
super(GalaxyCLI, self).run()
# if not offline, get connect to galaxy api
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.api = GalaxyAPI(self.galaxy)
self.execute()
def exit_without_ignore(self, rc=1):
@ -242,7 +218,7 @@ class GalaxyCLI(CLI):
# platforms included (but commented out), the galaxy_tags
# list, and the dependencies section
platforms = []
if not offline and self.api:
if not offline:
platforms = self.api.get_list("platforms") or []
# group the list of platforms from the api based
@ -315,7 +291,7 @@ class GalaxyCLI(CLI):
role_info.update(install_info)
remote_data = False
if self.api:
if not self.options.offline:
remote_data = self.api.lookup_role_by_name(role, False)
if remote_data:
@ -489,22 +465,23 @@ class GalaxyCLI(CLI):
else:
# show all valid roles in the roles_path directory
roles_path = self.get_opt('roles_path')
roles_path = os.path.expanduser(roles_path)
if not os.path.exists(roles_path):
raise AnsibleOptionsError("- the path %s does not exist. Please specify a valid path with --roles-path" % roles_path)
elif not os.path.isdir(roles_path):
raise AnsibleOptionsError("- %s exists, but it is not a directory. Please specify a valid path with --roles-path" % roles_path)
path_files = os.listdir(roles_path)
for path_file in path_files:
gr = GalaxyRole(self.galaxy, path_file)
if gr.metadata:
install_info = gr.install_info
version = None
if install_info:
version = install_info.get("version", None)
if not version:
version = "(unknown version)"
display.display("- %s, %s" % (path_file, version))
for path in roles_path:
role_path = os.path.expanduser(path)
if not os.path.exists(role_path):
raise AnsibleOptionsError("- the path %s does not exist. Please specify a valid path with --roles-path" % role_path)
elif not os.path.isdir(role_path):
raise AnsibleOptionsError("- %s exists, but it is not a directory. Please specify a valid path with --roles-path" % role_path)
path_files = os.listdir(role_path)
for path_file in path_files:
gr = GalaxyRole(self.galaxy, path_file)
if gr.metadata:
install_info = gr.install_info
version = None
if install_info:
version = install_info.get("version", None)
if not version:
version = "(unknown version)"
display.display("- %s, %s" % (path_file, version))
return 0
def execute_search(self):

View File

@ -28,6 +28,7 @@ from ansible.compat.six.moves import configparser
from ansible.parsing.quoting import unquote
from ansible.errors import AnsibleOptionsError
from ansible.utils.path import makedirs_safe
# copied from utils, avoid circular reference fun :)
def mk_boolean(value):
@ -39,16 +40,22 @@ def mk_boolean(value):
else:
return False
def shell_expand(path):
def shell_expand(path, expand_relative_paths=False):
'''
shell_expand is needed as os.path.expanduser does not work
when path is None, which is the default for ANSIBLE_PRIVATE_KEY_FILE
'''
if path:
path = os.path.expanduser(os.path.expandvars(path))
if expand_relative_paths and not path.startswith('/'):
# paths are always 'relative' to the config?
if 'CONFIG_FILE' in globals():
CFGDIR = os.path.dirname(CONFIG_FILE)
path = os.path.join(CFGDIR, path)
path = os.path.abspath(path)
return path
def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False, isnone=False, ispath=False, ispathlist=False, istmppath=False):
def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False, isnone=False, ispath=False, ispathlist=False, istmppath=False, expand_relative_paths=False):
''' return a configuration variable with casting '''
value = _get_config(p, section, key, env_var, default)
if boolean:
@ -69,11 +76,13 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False,
elif istmppath:
value = shell_expand(value)
if not os.path.exists(value):
os.makedirs(value, 0o700)
value = tempfile.mkdtemp(prefix='ansible-local-tmp', dir=value)
makedirs_safe(value, 0o700)
prefix = 'ansible-local-%s' % os.getpid()
value = tempfile.mkdtemp(prefix=prefix, dir=value)
elif ispathlist:
if isinstance(value, string_types):
value = [shell_expand(x) for x in value.split(os.pathsep)]
value = [shell_expand(x, expand_relative_paths=expand_relative_paths) \
for x in value.split(os.pathsep)]
elif isinstance(value, string_types):
value = unquote(value)
return value
@ -140,7 +149,7 @@ DEFAULT_PATTERN = get_config(p, DEFAULTS, 'pattern', None, None)
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, ispathlist=True)
DEFAULT_ROLES_PATH = get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles', ispathlist=True)
DEFAULT_ROLES_PATH = get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles', ispathlist=True, expand_relative_paths=True)
DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '$HOME/.ansible/tmp')
DEFAULT_LOCAL_TMP = get_config(p, DEFAULTS, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', '$HOME/.ansible/tmp', istmppath=True)
DEFAULT_MODULE_NAME = get_config(p, DEFAULTS, 'module_name', None, 'command')
@ -302,7 +311,7 @@ COLOR_DEPRECATE = get_config(p, 'colors', 'deprecate', 'ANSIBLE_COLOR_DEPRECAT
COLOR_SKIP = get_config(p, 'colors', 'skip', 'ANSIBLE_COLOR_SKIP', 'cyan')
COLOR_UNREACHABLE = get_config(p, 'colors', 'unreachable', 'ANSIBLE_COLOR_UNREACHABLE', 'bright red')
COLOR_OK = get_config(p, 'colors', 'ok', 'ANSIBLE_COLOR_OK', 'green')
COLOR_CHANGED = get_config(p, 'colors', 'ok', 'ANSIBLE_COLOR_CHANGED', 'yellow')
COLOR_CHANGED = get_config(p, 'colors', 'changed', 'ANSIBLE_COLOR_CHANGED', 'yellow')
COLOR_DIFF_ADD = get_config(p, 'colors', 'diff_add', 'ANSIBLE_COLOR_DIFF_ADD', 'green')
COLOR_DIFF_REMOVE = get_config(p, 'colors', 'diff_remove', 'ANSIBLE_COLOR_DIFF_REMOVE', 'red')
COLOR_DIFF_LINES = get_config(p, 'colors', 'diff_lines', 'ANSIBLE_COLOR_DIFF_LINES', 'cyan')

View File

@ -30,11 +30,14 @@ import zipfile
from io import BytesIO
# from Ansible
from ansible import __version__
from ansible.release import __version__, __author__
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.utils.unicode import to_bytes, to_unicode
from ansible.plugins.strategy import action_write_locks
# Must import strategy and use write_locks from there
# If we import write_locks directly then we end up binding a
# variable to the object and then it never gets updated.
from ansible.plugins import strategy
try:
from __main__ import display
@ -118,7 +121,7 @@ def invoke_module(module, modlib_path, json_params):
else:
os.environ['PYTHONPATH'] = modlib_path
p = subprocess.Popen(['%(interpreter)s', module], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
p = subprocess.Popen([%(interpreter)s, module], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
(stdout, stderr) = p.communicate(json_params)
if not isinstance(stderr, (bytes, unicode)):
@ -215,7 +218,7 @@ def debug(command, zipped_mod, json_params):
else:
os.environ['PYTHONPATH'] = basedir
p = subprocess.Popen(['%(interpreter)s', script_path, args_path], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
p = subprocess.Popen([%(interpreter)s, script_path, args_path], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
(stdout, stderr) = p.communicate()
if not isinstance(stderr, (bytes, unicode)):
@ -267,18 +270,33 @@ if __name__ == '__main__':
# remote_tmpdir and this module executing under async. So we cannot
# store this in remote_tmpdir (use system tempdir instead)
temp_path = tempfile.mkdtemp(prefix='ansible_')
zipped_mod = os.path.join(temp_path, 'ansible_modlib.zip')
modlib = open(zipped_mod, 'wb')
modlib.write(base64.b64decode(ZIPDATA))
modlib.close()
if len(sys.argv) == 2:
exitcode = debug(sys.argv[1], zipped_mod, ZIPLOADER_PARAMS)
else:
z = zipfile.ZipFile(zipped_mod)
z = zipfile.ZipFile(zipped_mod, mode='r')
module = os.path.join(temp_path, 'ansible_module_%(ansible_module)s.py')
f = open(module, 'wb')
f.write(z.read('ansible_module_%(ansible_module)s.py'))
f.close()
# When installed via setuptools (including python setup.py install),
# ansible may be installed with an easy-install.pth file. That file
# may load the system-wide install of ansible rather than the one in
# the module. sitecustomize is the only way to override that setting.
z = zipfile.ZipFile(zipped_mod, mode='a')
# py3: zipped_mod will be text, py2: it's bytes. Need bytes at the end
z = zipfile.ZipFile(zipped_mod, mode='a')
sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% zipped_mod
sitecustomize = sitecustomize.encode('utf-8')
z.writestr('sitecustomize.py', sitecustomize)
z.close()
exitcode = invoke_module(module, zipped_mod, ZIPLOADER_PARAMS)
finally:
try:
@ -372,12 +390,12 @@ def _get_shebang(interpreter, task_vars, args=tuple()):
file rather than trust that we reformatted what they already have
correctly.
"""
interpreter_config = u'ansible_%s_interpreter' % os.path.basename(interpreter)
interpreter_config = u'ansible_%s_interpreter' % os.path.basename(interpreter).strip()
if interpreter_config not in task_vars:
return (None, interpreter)
interpreter = task_vars[interpreter_config]
interpreter = task_vars[interpreter_config].strip()
shebang = u'#!' + interpreter
if args:
@ -385,12 +403,6 @@ def _get_shebang(interpreter, task_vars, args=tuple()):
return (shebang, interpreter)
def _get_facility(task_vars):
facility = C.DEFAULT_SYSLOG_FACILITY
if 'ansible_syslog_facility' in task_vars:
facility = task_vars['ansible_syslog_facility']
return facility
def recursive_finder(name, data, py_module_names, py_module_cache, zf):
"""
Using ModuleDepFinder, make sure we have all of the module_utils files that
@ -529,14 +541,7 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
py_module_names = set()
if module_substyle == 'python':
# ziploader for new-style python classes
constants = dict(
SELINUX_SPECIAL_FS=C.DEFAULT_SELINUX_SPECIAL_FS,
SYSLOG_FACILITY=_get_facility(task_vars),
)
params = dict(ANSIBLE_MODULE_ARGS=module_args,
ANSIBLE_MODULE_CONSTANTS=constants,
)
params = dict(ANSIBLE_MODULE_ARGS=module_args,)
python_repred_params = to_bytes(repr(json.dumps(params)), errors='strict')
try:
@ -551,19 +556,34 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
zipdata = None
# Optimization -- don't lock if the module has already been cached
if os.path.exists(cached_module_filename):
display.debug('ZIPLOADER: using cached module: %s' % cached_module_filename)
zipdata = open(cached_module_filename, 'rb').read()
# Fool the check later... I think we should just remove the check
py_module_names.add(('basic',))
else:
with action_write_locks[module_name]:
if module_name in strategy.action_write_locks:
display.debug('ZIPLOADER: Using lock for %s' % module_name)
lock = strategy.action_write_locks[module_name]
else:
# If the action plugin directly invokes the module (instead of
# going through a strategy) then we don't have a cross-process
# Lock specifically for this module. Use the "unexpected
# module" lock instead
display.debug('ZIPLOADER: Using generic lock for %s' % module_name)
lock = strategy.action_write_locks[None]
display.debug('ZIPLOADER: Acquiring lock')
with lock:
display.debug('ZIPLOADER: Lock acquired: %s' % id(lock))
# Check that no other process has created this while we were
# waiting for the lock
if not os.path.exists(cached_module_filename):
display.debug('ZIPLOADER: Creating module')
# Create the module zip data
zipoutput = BytesIO()
zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
zf.writestr('ansible/__init__.py', b''.join((b"__version__ = '", to_bytes(__version__), b"'\n")))
zf.writestr('ansible/module_utils/__init__.py', b'')
zf.writestr('ansible/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\ntry:\n from ansible.release import __version__,__author__\nexcept ImportError:\n __version__="' + to_bytes(__version__) + b'"\n __author__="' + to_bytes(__author__) + b'"\n')
zf.writestr('ansible/module_utils/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\n')
zf.writestr('ansible_module_%s.py' % module_name, module_data)
@ -579,15 +599,19 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
# Note -- if we have a global function to setup, that would
# be a better place to run this
os.mkdir(lookup_path)
display.debug('ZIPLOADER: Writing module')
with open(cached_module_filename + '-part', 'w') as f:
f.write(zipdata)
# Rename the file into its final position in the cache so
# future users of this module can read it off the
# filesystem instead of constructing from scratch.
display.debug('ZIPLOADER: Renaming module')
os.rename(cached_module_filename + '-part', cached_module_filename)
display.debug('ZIPLOADER: Done creating module')
if zipdata is None:
display.debug('ZIPLOADER: Reading module after lock')
# Another process wrote the file while we were waiting for
# the write lock. Go ahead and read the data from disk
# instead of re-creating it.
@ -601,6 +625,12 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
shebang, interpreter = _get_shebang(u'/usr/bin/python', task_vars)
if shebang is None:
shebang = u'#!/usr/bin/python'
# Enclose the parts of the interpreter in quotes because we're
# substituting it into the template as a Python string
interpreter_parts = interpreter.split(u' ')
interpreter = u"'{0}'".format(u"', '".join(interpreter_parts))
output.write(to_bytes(ACTIVE_ZIPLOADER_TEMPLATE % dict(
zipdata=zipdata,
ansible_module=module_name,
@ -656,7 +686,7 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
# The main event -- substitute the JSON args string into the module
module_data = module_data.replace(REPLACER_JSONARGS, module_args_json)
facility = b'syslog.' + to_bytes(_get_facility(task_vars), errors='strict')
facility = b'syslog.' + to_bytes(task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY), errors='strict')
module_data = module_data.replace(b'syslog.LOG_USER', facility)
return (module_data, module_style, shebang)

View File

@ -216,10 +216,13 @@ class PlayIterator:
self._play.handlers.extend(play.compile_roles_handlers())
def get_host_state(self, host):
try:
return self._host_states[host.name].copy()
except KeyError:
raise AnsibleError("invalid host (%s) specified for playbook iteration" % host)
# Since we're using the PlayIterator to carry forward failed hosts,
# in the event that a previous host was not in the current inventory
# we create a stub state for it now
if host.name not in self._host_states:
self._host_states[host.name] = HostState(blocks=[])
return self._host_states[host.name].copy()
def get_next_task_for_host(self, host, peek=False):
@ -295,10 +298,10 @@ class PlayIterator:
setup_block = self._blocks[0]
if setup_block.has_tasks() and len(setup_block.block) > 0:
task = setup_block.block[0]
if not peek:
# mark the host as having gathered facts, because we're
# returning the setup task to be executed
host.set_gathered_facts(True)
if not peek:
# mark the host as having gathered facts, because we're
# returning the setup task to be executed
host.set_gathered_facts(True)
else:
# This is the second trip through ITERATING_SETUP, so we clear
# the flag and move onto the next block in the list while setting
@ -326,8 +329,7 @@ class PlayIterator:
if self._check_failed_state(state.tasks_child_state):
# failed child state, so clear it and move into the rescue portion
state.tasks_child_state = None
state.fail_state |= self.FAILED_TASKS
state.run_state = self.ITERATING_RESCUE
self._set_failed_state(state)
else:
# get the next task recursively
if task is None or state.tasks_child_state.run_state == self.ITERATING_COMPLETE:
@ -365,8 +367,7 @@ class PlayIterator:
(state.rescue_child_state, task) = self._get_next_task_from_state(state.rescue_child_state, host=host, peek=peek)
if self._check_failed_state(state.rescue_child_state):
state.rescue_child_state = None
state.fail_state |= self.FAILED_RESCUE
state.run_state = self.ITERATING_ALWAYS
self._set_failed_state(state)
else:
if task is None or state.rescue_child_state.run_state == self.ITERATING_COMPLETE:
state.rescue_child_state = None
@ -396,8 +397,7 @@ class PlayIterator:
(state.always_child_state, task) = self._get_next_task_from_state(state.always_child_state, host=host, peek=peek)
if self._check_failed_state(state.always_child_state):
state.always_child_state = None
state.fail_state |= self.FAILED_ALWAYS
state.run_state = self.ITERATING_COMPLETE
self._set_failed_state(state)
else:
if task is None or state.always_child_state.run_state == self.ITERATING_COMPLETE:
state.always_child_state = None
@ -466,18 +466,21 @@ class PlayIterator:
def mark_host_failed(self, host):
s = self.get_host_state(host)
display.debug("marking host %s failed, current state: %s" % (host, s))
s = self._set_failed_state(s)
display.debug("^ failed state is now: %s" % s)
self._host_states[host.name] = s
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)
return dict((host, True) for (host, state) in iteritems(self._host_states) if self._check_failed_state(state))
def _check_failed_state(self, state):
if state is None:
return False
elif state.fail_state != self.FAILED_NONE:
if state.run_state == self.ITERATING_RESCUE and state.fail_state&self.FAILED_RESCUE == 0 or \
state.run_state == self.ITERATING_ALWAYS and state.fail_state&self.FAILED_ALWAYS == 0:
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

View File

@ -27,7 +27,8 @@ 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.unicode import to_unicode
from ansible.utils.path import makedirs_safe
from ansible.utils.unicode import to_unicode, to_str
try:
from __main__ import display
@ -70,7 +71,7 @@ class PlaybookExecutor:
try:
for playbook_path in self._playbooks:
pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader)
self._inventory.set_playbook_basedir(os.path.dirname(playbook_path))
self._inventory.set_playbook_basedir(os.path.realpath(os.path.dirname(playbook_path)))
if self._tqm is None: # we are doing a listing
entry = {'playbook': playbook_path}
@ -128,6 +129,10 @@ class PlaybookExecutor:
else:
self._tqm._unreachable_hosts.update(self._unreachable_hosts)
previously_failed = len(self._tqm._failed_hosts)
previously_unreachable = len(self._tqm._unreachable_hosts)
break_play = False
# we are actually running plays
for batch in self._get_serialized_batches(new_play):
if len(batch) == 0:
@ -140,24 +145,31 @@ class PlaybookExecutor:
# and run it...
result = self._tqm.run(play=play)
# break the play if the result equals the special return code
if result == self._tqm.RUN_FAILED_BREAK_PLAY:
result = self._tqm.RUN_FAILED_HOSTS
break_play = True
# check the number of failures here, to see if they're above the maximum
# failure percentage allowed, or if any errors are fatal. If either of those
# 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.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:
failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \
(previously_failed + previously_unreachable)
if len(batch) == failed_hosts_count:
break_play = True
break
# clear the failed hosts dictionaires in the TQM for the next batch
# update the previous counts so they don't accumulate incorrectly
# over multiple serial batches
previously_failed += len(self._tqm._failed_hosts) - previously_failed
previously_unreachable += len(self._tqm._unreachable_hosts) - previously_unreachable
# save the unreachable hosts from this batch
self._unreachable_hosts.update(self._tqm._unreachable_hosts)
self._tqm.clear_failed_hosts()
# if the last result wasn't zero or 3 (some hosts were unreachable),
# break out of the serial batch loop
if result not in (0, 3):
if break_play:
break
i = i + 1 # per play
@ -174,8 +186,10 @@ class PlaybookExecutor:
if len(retries) > 0:
if C.RETRY_FILES_SAVE_PATH:
basedir = C.shell_expand(C.RETRY_FILES_SAVE_PATH)
elif playbook_path:
basedir = os.path.dirname(os.path.abspath(playbook_path))
else:
basedir = os.path.dirname(playbook_path)
basedir = '~/'
(retry_name, _) = os.path.splitext(os.path.basename(playbook_path))
filename = os.path.join(basedir, "%s.retry" % retry_name)
@ -247,13 +261,13 @@ class PlaybookExecutor:
re-running on ONLY the failed hosts. This may duplicate some variable
information in group_vars/host_vars but that is ok, and expected.
'''
try:
makedirs_safe(os.path.dirname(retry_path))
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))
display.warning("Could not create retry file '%s'.\n\t%s" % (retry_path, to_str(e)))
return False
return True

View File

@ -173,7 +173,7 @@ class ResultProcess(multiprocessing.Process):
# if this task is registering facts, do that now
loop_var = 'item'
if result._task.loop_control:
loop_var = result._task.loop_control.get('loop_var') or 'item'
loop_var = result._task.loop_control.loop_var or 'item'
item = result_item.get(loop_var, None)
if result._task.action == 'include_vars':
for (key, value) in iteritems(result_item['ansible_facts']):

View File

@ -232,7 +232,7 @@ class TaskExecutor:
loop_var = self._task.loop_control.loop_var or 'item'
if loop_var in task_vars:
raise AnsibleError("the loop variable '%s' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something else to avoid variable collisions" % loop_var)
display.warning("The loop variable '%s' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something else to avoid variable collisions and unexpected behavior." % loop_var)
items = self._squash_items(items, loop_var, task_vars)
for item in items:
@ -269,59 +269,68 @@ class TaskExecutor:
Squash items down to a comma-separated list for certain modules which support it
(typically package management modules).
'''
# _task.action could contain templatable strings (via action: and
# local_action:) Template it before comparing. If we don't end up
# optimizing it here, the templatable string might use template vars
# that aren't available until later (it could even use vars from the
# with_items loop) so don't make the templated string permanent yet.
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
task_action = self._task.action
if templar._contains_vars(task_action):
task_action = templar.template(task_action, fail_on_undefined=False)
name = None
try:
# _task.action could contain templatable strings (via action: and
# local_action:) Template it before comparing. If we don't end up
# optimizing it here, the templatable string might use template vars
# that aren't available until later (it could even use vars from the
# with_items loop) so don't make the templated string permanent yet.
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
task_action = self._task.action
if templar._contains_vars(task_action):
task_action = templar.template(task_action, fail_on_undefined=False)
if len(items) > 0 and task_action in self.SQUASH_ACTIONS:
if all(isinstance(o, string_types) for o in items):
final_items = []
if len(items) > 0 and task_action in self.SQUASH_ACTIONS:
if all(isinstance(o, string_types) for o in items):
final_items = []
name = None
for allowed in ['name', 'pkg', 'package']:
name = self._task.args.pop(allowed, None)
if name is not None:
break
for allowed in ['name', 'pkg', 'package']:
name = self._task.args.pop(allowed, None)
if name is not None:
break
# 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 name:
if templar._contains_vars(name):
variables[loop_var] = '\0$'
template_no_item = templar.template(name, variables, cache=False)
variables[loop_var] = '\0@'
template_with_item = templar.template(name, variables, cache=False)
del variables[loop_var]
# 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 name:
if templar._contains_vars(name):
variables[loop_var] = '\0$'
template_no_item = templar.template(name, variables, cache=False)
variables[loop_var] = '\0@'
template_with_item = templar.template(name, variables, cache=False)
del variables[loop_var]
# 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 template_no_item != template_with_item:
for item in items:
variables[loop_var] = item
if self._task.evaluate_conditional(templar, variables):
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.
# 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 template_no_item != template_with_item:
for item in items:
variables[loop_var] = item
if self._task.evaluate_conditional(templar, variables):
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.
except:
# Squashing is an optimization. If it fails for any reason,
# simply use the unoptimized list of items.
# Restore the name parameter
if name is not None:
self._task.args['name'] = name
pass
return items
def _execute(self, variables=None):
@ -400,7 +409,17 @@ class TaskExecutor:
# get the connection and the handler for this execution
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)
hostvars = variables.get('hostvars', None)
if hostvars:
try:
target_hostvars = hostvars.raw_get(self._host.name)
except:
# FIXME: this should catch the j2undefined error here
# specifically instead of all exceptions
target_hostvars = dict()
else:
target_hostvars = dict()
self._connection.set_host_overrides(host=self._host, hostvars=target_hostvars)
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
@ -414,10 +433,14 @@ class TaskExecutor:
self._task.args = dict((i[0], i[1]) for i in iteritems(self._task.args) if i[1] != omit_token)
# Read some values from the task, so that we can modify them if need be
if self._task.until is not None:
if self._task.until:
retries = self._task.retries
if retries <= 0:
if retries is None:
retries = 3
elif retries <= 0:
retries = 1
else:
retries += 1
else:
retries = 1
@ -431,7 +454,7 @@ class TaskExecutor:
display.debug("starting attempt loop")
result = None
for attempt in range(retries):
for attempt in range(1, retries + 1):
display.debug("running the handler")
try:
result = self._handler.run(task_vars=variables)
@ -448,17 +471,8 @@ class TaskExecutor:
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
try:
if 'skipped' in result and result['skipped'] or 'failed' in result and result['failed']:
return result
result = json.loads(result.get('stdout'))
except (TypeError, ValueError) as 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)
result = self._poll_async_result(result=result, templar=templar, task_vars=vars_copy)
# ensure no log is preserved
result["_ansible_no_log"] = self._play_context.no_log
@ -494,23 +508,23 @@ class TaskExecutor:
_evaluate_changed_when_result(result)
_evaluate_failed_when_result(result)
if attempt < retries - 1:
if retries > 1:
cond = Conditional(loader=self._loader)
cond.when = self._task.until
if cond.evaluate_conditional(templar, vars_copy):
break
else:
# no conditional check, or it failed, so sleep for the specified time
result['attempts'] = attempt + 1
result['retries'] = retries
result['_ansible_retry'] = True
display.debug('Retrying task, attempt %d of %d' % (attempt + 1, retries))
self._rslt_q.put(TaskResult(self._host, self._task, result), block=False)
time.sleep(delay)
if attempt < retries:
result['attempts'] = attempt
result['_ansible_retry'] = True
result['retries'] = retries
display.debug('Retrying task, attempt %d of %d' % (attempt, retries))
self._rslt_q.put(TaskResult(self._host, self._task, result), block=False)
time.sleep(delay)
else:
if retries > 1:
# we ran out of attempts, so mark the result as failed
result['attempts'] = retries
result['failed'] = True
# do the final update of the local variables here, for both registered
@ -542,11 +556,14 @@ class TaskExecutor:
display.debug("attempt loop complete, returning result")
return result
def _poll_async_result(self, result, templar):
def _poll_async_result(self, result, templar, task_vars=None):
'''
Polls for the specified JID to be complete
'''
if task_vars is None:
task_vars = self._job_vars
async_jid = result.get('ansible_job_id')
if async_jid is None:
return dict(failed=True, msg="No job id was returned by the async task")
@ -574,14 +591,22 @@ class TaskExecutor:
while time_left > 0:
time.sleep(self._task.poll)
async_result = normal_handler.run()
if int(async_result.get('finished', 0)) == 1 or 'failed' in async_result or 'skipped' in async_result:
async_result = normal_handler.run(task_vars=task_vars)
# We do not bail out of the loop in cases where the failure
# is associated with a parsing error. The async_runner can
# have issues which result in a half-written/unparseable result
# file on disk, which manifests to the user as a timeout happening
# before it's time to timeout.
if int(async_result.get('finished', 0)) == 1 or ('failed' in async_result and async_result.get('_ansible_parsed', False)) or 'skipped' in async_result:
break
time_left -= self._task.poll
if int(async_result.get('finished', 0)) != 1:
return dict(failed=True, msg="async task did not complete within the requested time")
if async_result.get('_ansible_parsed'):
return dict(failed=True, msg="async task did not complete within the requested time")
else:
return dict(failed=True, msg="async task produced unparseable results", async_result=async_result)
else:
return async_result
@ -595,14 +620,14 @@ class TaskExecutor:
# since we're delegating, we don't want to use interpreter values
# which would have been set for the original target host
for i in variables.keys():
if i.startswith('ansible_') and i.endswith('_interpreter'):
if isinstance(i, string_types) and i.startswith('ansible_') and i.endswith('_interpreter'):
del variables[i]
# now replace the interpreter values with those that may have come
# from the delegated-to host
delegated_vars = variables.get('ansible_delegated_vars', dict()).get(self._task.delegate_to, dict())
if isinstance(delegated_vars, dict):
for i in delegated_vars:
if i.startswith("ansible_") and i.endswith("_interpreter"):
if isinstance(i, string_types) and i.startswith("ansible_") and i.endswith("_interpreter"):
variables[i] = delegated_vars[i]
conn_type = self._play_context.connection
@ -629,6 +654,8 @@ class TaskExecutor:
raise AnsibleError("the connection plugin '%s' was not found" % conn_type)
if self._play_context.accelerate:
# accelerate is deprecated as of 2.1...
display.deprecated('Accelerated mode is deprecated. Consider using SSH with ControlPersist and pipelining enabled instead')
# launch the accelerated daemon here
ssh_connection = connection
handler = self._shared_loader_obj.action_loader.get(

View File

@ -58,6 +58,13 @@ class TaskQueueManager:
which dispatches the Play's tasks to hosts.
'''
RUN_OK = 0
RUN_ERROR = 1
RUN_FAILED_HOSTS = 2
RUN_UNREACHABLE_HOSTS = 3
RUN_FAILED_BREAK_PLAY = 4
RUN_UNKNOWN_ERROR = 255
def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None, run_additional_callbacks=True, run_tree=False):
self._inventory = inventory
@ -107,7 +114,7 @@ class TaskQueueManager:
self._result_prc = ResultProcess(self._final_q, self._workers)
self._result_prc.start()
def _initialize_notified_handlers(self, handlers):
def _initialize_notified_handlers(self, play):
'''
Clears and initializes the shared notified handlers dict with entries
for each handler in the play, which is an empty array that will contain
@ -116,8 +123,7 @@ class TaskQueueManager:
# Zero the dictionary first by removing any entries there.
# Proxied dicts don't support iteritems, so we have to use keys()
for key in self._notified_handlers.keys():
del self._notified_handlers[key]
self._notified_handlers.clear()
def _process_block(b):
temp_list = []
@ -129,12 +135,13 @@ class TaskQueueManager:
return temp_list
handler_list = []
for handler_block in handlers:
for handler_block in play.handlers:
handler_list.extend(_process_block(handler_block))
# then initialize it with the handler names from the handler list
# then initialize it with the given handler list
for handler in handler_list:
self._notified_handlers[handler.get_name()] = []
if handler not in self._notified_handlers:
self._notified_handlers[handler] = []
def load_callbacks(self):
'''
@ -199,6 +206,7 @@ class TaskQueueManager:
new_play = play.copy()
new_play.post_validate(templar)
new_play.handlers = new_play.compile_roles_handlers() + new_play.handlers
self.hostvars = HostVars(
inventory=self._inventory,
@ -219,7 +227,7 @@ class TaskQueueManager:
self.send_callback('v2_playbook_on_play_start', new_play)
# initialize the shared dictionary containing the notified handlers
self._initialize_notified_handlers(new_play.handlers)
self._initialize_notified_handlers(new_play)
# load the specified strategy (or the default linear one)
strategy = strategy_loader.get(new_play.strategy, self)
@ -236,6 +244,16 @@ class TaskQueueManager:
start_at_done = self._start_at_done,
)
# Because the TQM may survive multiple play runs, we start by marking
# any hosts as failed in the iterator here which may have been marked
# as failed in previous runs. Then we clear the internal list of failed
# hosts so we know what failed this round.
for host_name in self._failed_hosts.keys():
host = self._inventory.get_host(host_name)
iterator.mark_host_failed(host)
self.clear_failed_hosts()
# during initialization, the PlayContext will clear the start_at_task
# field to signal that a matching task was found, so check that here
# and remember it so we don't try to skip tasks on future plays
@ -244,6 +262,11 @@ class TaskQueueManager:
# and run the play using the strategy and cleanup on way out
play_return = strategy.run(iterator, play_context)
# now re-save the hosts that failed from the iterator to our internal list
for host_name in iterator.get_failed_hosts():
self._failed_hosts[host_name] = True
self._cleanup_processes()
return play_return
@ -286,6 +309,18 @@ class TaskQueueManager:
def terminate(self):
self._terminated = True
def has_dead_workers(self):
# [<WorkerProcess(WorkerProcess-2, stopped[SIGKILL])>,
# <WorkerProcess(WorkerProcess-2, stopped[SIGTERM])>
defunct = False
for idx,x in enumerate(self._workers):
if hasattr(x[0], 'exitcode'):
if x[0].exitcode in [-9, -15]:
defunct = True
return defunct
def send_callback(self, method_name, *args, **kwargs):
for callback_plugin in [self._stdout_callback] + self._callback_plugins:
# a plugin that set self.disabled to True will not be called

View File

@ -40,14 +40,16 @@ class TaskResult:
return self._check_key('changed')
def is_skipped(self):
# loop results
if 'results' in self._result and self._task.loop:
flag = True
for res in self._result.get('results', []):
if isinstance(res, dict):
flag &= res.get('skipped', False)
return flag
else:
return self._result.get('skipped', False)
results = self._result['results']
# Loop tasks are only considered skipped if all items were skipped.
# some squashed results (eg, yum) are not dicts and can't be skipped individually
if results and all(isinstance(res, dict) and res.get('skipped', False) for res in results):
return True
# regular tasks and squashed non-dict results
return self._result.get('skipped', False)
def is_failed(self):
if 'failed_when_result' in self._result or \
@ -60,7 +62,7 @@ class TaskResult:
return self._check_key('unreachable')
def _check_key(self, key):
if 'results' in self._result and self._task.loop:
if self._result.get('results', []) and self._task.loop:
flag = False
for res in self._result.get('results', []):
if isinstance(res, dict):

View File

@ -28,7 +28,6 @@ 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
@ -41,6 +40,21 @@ except ImportError:
from ansible.utils.display import Display
display = Display()
def g_connect(method):
''' wrapper to lazily initialize connection info to galaxy '''
def wrapped(self, *args, **kwargs):
if not self.initialized:
display.vvvv("Initial connection to galaxy_server: %s" % self._api_server)
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)
self.baseurl = '%s/api/%s' % (self._api_server, server_version)
self.version = server_version # for future use
display.vvvv("Base API: %s" % self.baseurl)
self.initialized = True
return method(self, *args, **kwargs)
return wrapped
class GalaxyAPI(object):
''' This class is meant to be used as a API client for an Ansible Galaxy server '''
@ -51,25 +65,17 @@ class GalaxyAPI(object):
self.galaxy = galaxy
self.token = GalaxyToken()
self._api_server = C.GALAXY_SERVER
self._validate_certs = not C.GALAXY_IGNORE_CERTS
self._validate_certs = not galaxy.options.ignore_certs
self.baseurl = None
self.version = None
self.initialized = False
# set validate_certs
if galaxy.options.ignore_certs:
self._validate_certs = False
display.vvv('Validate TLS certificates: %s' % self._validate_certs)
display.debug('Validate TLS certificates: %s' % self._validate_certs)
# 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)
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)
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()
@ -77,6 +83,7 @@ class GalaxyAPI(object):
raise AnsibleError("No access token. You must first use login to authenticate and obtain an access token.")
return {'Authorization': 'Token ' + token}
@g_connect
def __call_galaxy(self, url, args=None, headers=None, method=None):
if args and not headers:
headers = self.__auth_header()
@ -91,13 +98,13 @@ class GalaxyAPI(object):
@property
def api_server(self):
return self._api_server
return self._api_server
@property
def validate_certs(self):
return self._validate_certs
def get_server_api_version(self):
def _get_server_api_version(self):
"""
Fetches the Galaxy API current version to ensure
the API server is up and reachable.
@ -107,8 +114,9 @@ class GalaxyAPI(object):
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)
raise AnsibleError("The API server (%s) is not responding, please try again later" % url)
@g_connect
def authenticate(self, github_token):
"""
Retrieve an authentication token
@ -119,6 +127,7 @@ class GalaxyAPI(object):
data = json.load(resp)
return data
@g_connect
def create_import_task(self, github_user, github_repo, reference=None):
"""
Post an import request
@ -134,6 +143,7 @@ class GalaxyAPI(object):
return data['results']
return data
@g_connect
def get_import_task(self, task_id=None, github_user=None, github_repo=None):
"""
Check the status of an import task.
@ -145,10 +155,11 @@ class GalaxyAPI(object):
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']
@g_connect
def lookup_role_by_name(self, role_name, notify=True):
"""
Find a role by name.
@ -170,6 +181,7 @@ class GalaxyAPI(object):
return data["results"][0]
return None
@g_connect
def fetch_role_related(self, related, role_id):
"""
Fetch the list of related items for the given role.
@ -190,6 +202,7 @@ class GalaxyAPI(object):
except:
return None
@g_connect
def get_list(self, what):
"""
Fetch the list of items specified.
@ -213,6 +226,7 @@ class GalaxyAPI(object):
except Exception as error:
raise AnsibleError("Failed to download the %s list: %s" % (what, str(error)))
@g_connect
def search_roles(self, search, **kwargs):
search_url = self.baseurl + '/search/roles/?'
@ -228,7 +242,7 @@ class GalaxyAPI(object):
if tags and isinstance(tags, basestring):
tags = tags.split(',')
search_url += '&tags_autocomplete=' + '+'.join(tags)
if platforms and isinstance(platforms, basestring):
platforms = platforms.split(',')
search_url += '&platforms_autocomplete=' + '+'.join(platforms)
@ -238,10 +252,11 @@ class GalaxyAPI(object):
if author:
search_url += '&username_autocomplete=%s' % author
data = self.__call_galaxy(search_url)
return data
@g_connect
def add_secret(self, source, github_user, github_repo, secret):
url = "%s/notification_secrets/" % self.baseurl
args = urllib.urlencode({
@ -253,16 +268,19 @@ class GalaxyAPI(object):
data = self.__call_galaxy(url, args=args)
return data
@g_connect
def list_secrets(self):
url = "%s/notification_secrets" % self.baseurl
data = self.__call_galaxy(url, headers=self.__auth_header())
return data
@g_connect
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
@g_connect
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')

View File

@ -54,13 +54,9 @@ class GalaxyRole(object):
self._metadata = None
self._install_info = None
self._validate_certs = not galaxy.options.ignore_certs
self._validate_certs = not C.GALAXY_IGNORE_CERTS
# set validate_certs
if galaxy.options.ignore_certs:
self._validate_certs = False
display.vvv('Validate TLS certificates: %s' % self._validate_certs)
display.debug('Validate TLS certificates: %s' % self._validate_certs)
self.options = galaxy.options
self.galaxy = galaxy

View File

@ -34,7 +34,7 @@ from ansible.inventory.dir import InventoryDirectory, get_file_parser
from ansible.inventory.group import Group
from ansible.inventory.host import Host
from ansible.plugins import vars_loader
from ansible.utils.unicode import to_unicode
from ansible.utils.unicode import to_unicode, to_bytes
from ansible.utils.vars import combine_vars
from ansible.parsing.utils.addresses import parse_address
@ -58,6 +58,7 @@ class Inventory(object):
self.host_list = host_list
self._loader = loader
self._variable_manager = variable_manager
self.localhost = None
# caching to avoid repeated calculations, particularly with
# external inventory scripts.
@ -68,6 +69,12 @@ class Inventory(object):
self._pattern_cache = {}
self._vars_plugins = []
self._basedir = self.basedir()
# Contains set of filenames under group_vars directories
self._group_vars_files = self._find_group_vars_files(self._basedir)
self._host_vars_files = self._find_host_vars_files(self._basedir)
# to be set by calling set_playbook_basedir by playbook code
self._playbook_basedir = None
@ -119,7 +126,15 @@ class Inventory(object):
display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_unicode(e))
host = h
port = None
all.add_host(Host(host, port))
new_host = Host(host, port)
if h in C.LOCALHOST:
# set default localhost from inventory to avoid creating an implicit one. Last localhost defined 'wins'.
if self.localhost is not None:
display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name))
display.vvvv("Set default localhost to %s" % h)
self.localhost = new_host
all.add_host(new_host)
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'
if self.is_directory(host_list):
@ -128,7 +143,7 @@ class Inventory(object):
self.parser = InventoryDirectory(loader=self._loader, groups=self.groups, filename=host_list)
else:
self.parser = get_file_parser(host_list, self.groups, self._loader)
vars_loader.add_directory(self.basedir(), with_subdir=True)
vars_loader.add_directory(self._basedir, with_subdir=True)
if not self.parser:
# should never happen, but JIC
@ -142,10 +157,12 @@ class Inventory(object):
for g in self.groups:
group = self.groups[g]
group.vars = combine_vars(group.vars, self.get_group_variables(group.name))
self.get_group_vars(group)
# set host vars from host_vars/ files and vars plugins
for host in self.get_hosts():
# get host vars from host_vars/ files and vars plugins
for host in self.get_hosts(ignore_limits_and_restrictions=True):
host.vars = combine_vars(host.vars, self.get_host_variables(host.name))
self.get_host_vars(host)
def _match(self, str, pattern_str):
try:
@ -204,7 +221,7 @@ class Inventory(object):
# 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 = [ h for h in hosts if h.name in self._restriction ]
seen = set()
HOSTS_PATTERNS_CACHE[pattern_hash] = [x for x in hosts if x not in seen and not seen.add(x)]
@ -439,10 +456,14 @@ class Inventory(object):
for group in groups.values():
if pattern == 'all':
for host in group.get_hosts():
if host.implicit:
continue
__append_host_to_results(host)
else:
if self._match(group.name, pattern) and group.name not in ('all', 'ungrouped'):
for host in group.get_hosts():
if host.implicit:
continue
__append_host_to_results(host)
else:
matching_hosts = self._match_list(group.get_hosts(), 'name', pattern)
@ -455,13 +476,24 @@ class Inventory(object):
return results
def _create_implicit_localhost(self, pattern):
new_host = Host(pattern)
new_host.address = "127.0.0.1"
new_host.vars = self.get_host_vars(new_host)
new_host.set_variable("ansible_connection", "local")
if "ansible_python_interpreter" not in new_host.vars:
new_host.set_variable("ansible_python_interpreter", sys.executable)
self.get_group("ungrouped").add_host(new_host)
if self.localhost:
new_host = self.localhost
else:
new_host = Host(pattern)
new_host.address = "127.0.0.1"
new_host.implicit = True
new_host.vars = self.get_host_vars(new_host)
new_host.set_variable("ansible_connection", "local")
if "ansible_python_interpreter" not in new_host.vars:
py_interp = sys.executable
if not py_interp:
# sys.executable is not set in some cornercases. #13585
display.warning('Unable to determine python interpreter from sys.executable. Using /usr/bin/python default. You can correct this by setting ansible_python_interpreter for localhost')
py_interp = '/usr/bin/python'
new_host.set_variable("ansible_python_interpreter", py_interp)
self.get_group("ungrouped").add_host(new_host)
self.localhost = new_host
return new_host
def clear_pattern_cache(self):
@ -482,23 +514,31 @@ class Inventory(object):
def get_host(self, hostname):
if hostname not in self._hosts_cache:
self._hosts_cache[hostname] = self._get_host(hostname)
if hostname in C.LOCALHOST:
for host in C.LOCALHOST.difference((hostname,)):
self._hosts_cache[host] = self._hosts_cache[hostname]
return self._hosts_cache[hostname]
def _get_host(self, hostname):
if hostname in C.LOCALHOST:
for host in self.get_group('all').get_hosts():
if host.name in C.LOCALHOST:
return host
return self._create_implicit_localhost(hostname)
matching_host = None
for group in self.groups.values():
for host in group.get_hosts():
if hostname == host.name:
matching_host = host
self._hosts_cache[host.name] = host
if hostname in C.LOCALHOST:
if self.localhost:
matching_host= self.localhost
else:
for host in self.get_group('all').get_hosts():
if host.name in C.LOCALHOST:
matching_host = host
break
if not matching_host:
matching_host = self._create_implicit_localhost(hostname)
# update caches
self._hosts_cache[hostname] = matching_host
for host in C.LOCALHOST.difference((hostname,)):
self._hosts_cache[host] = self._hosts_cache[hostname]
else:
for group in self.groups.values():
for host in group.get_hosts():
if host not in self._hosts_cache:
self._hosts_cache[host.name] = host
if hostname == host.name:
matching_host = host
return matching_host
def get_group(self, groupname):
@ -567,9 +607,6 @@ class Inventory(object):
if self.parser is not None:
vars = combine_vars(vars, self.parser.get_host_variables(host))
# Read host_vars/ files
vars = combine_vars(vars, self.get_host_vars(host))
return vars
def add_group(self, group):
@ -600,7 +637,7 @@ class Inventory(object):
return
elif not isinstance(restriction, list):
restriction = [ restriction ]
self._restriction = restriction
self._restriction = [ h.name for h in restriction ]
def subset(self, subset_pattern):
"""
@ -680,6 +717,12 @@ class Inventory(object):
"""
# Only update things if dir is a different playbook basedir
if dir_name != self._playbook_basedir:
# we're changing the playbook basedir, so if we had set one previously
# clear the host/group vars entries from the VariableManager so they're
# not incorrectly used by playbooks from different directories
if self._playbook_basedir:
self._variable_manager.clear_playbook_hostgroup_vars_files(self._playbook_basedir)
self._playbook_basedir = dir_name
# get group vars from group_vars/ files
# TODO: excluding the new_pb_basedir directory may result in group_vars
@ -687,26 +730,51 @@ class Inventory(object):
# we do this shouldn't be too much of an issue. Still, this should
# be fixed at some point to allow a "first load" to touch all of the
# directories, then later runs only touch the new basedir specified
for group in self.groups.values():
#group.vars = combine_vars(group.vars, self.get_group_vars(group, new_pb_basedir=True))
group.vars = combine_vars(group.vars, self.get_group_vars(group))
# get host vars from host_vars/ files
for host in self.get_hosts():
#host.vars = combine_vars(host.vars, self.get_host_vars(host, new_pb_basedir=True))
host.vars = combine_vars(host.vars, self.get_host_vars(host))
found_group_vars = self._find_group_vars_files(self._playbook_basedir)
if found_group_vars:
self._group_vars_files = self._group_vars_files.union(found_group_vars)
for group in self.groups.values():
self.get_group_vars(group)
found_host_vars = self._find_host_vars_files(self._playbook_basedir)
if found_host_vars:
self._host_vars_files = self._host_vars_files.union(found_host_vars)
# get host vars from host_vars/ files
for host in self.get_hosts():
self.get_host_vars(host)
# invalidate cache
self._vars_per_host = {}
self._vars_per_group = {}
def get_host_vars(self, host, new_pb_basedir=False):
def get_host_vars(self, host, new_pb_basedir=False, return_results=False):
""" Read host_vars/ files """
return self._get_hostgroup_vars(host=host, group=None, new_pb_basedir=new_pb_basedir)
return self._get_hostgroup_vars(host=host, group=None, new_pb_basedir=new_pb_basedir, return_results=return_results)
def get_group_vars(self, group, new_pb_basedir=False):
def get_group_vars(self, group, new_pb_basedir=False, return_results=False):
""" Read group_vars/ files """
return self._get_hostgroup_vars(host=None, group=group, new_pb_basedir=new_pb_basedir)
return self._get_hostgroup_vars(host=None, group=group, new_pb_basedir=new_pb_basedir, return_results=return_results)
def _get_hostgroup_vars(self, host=None, group=None, new_pb_basedir=False):
def _find_group_vars_files(self, basedir):
""" Find group_vars/ files """
if basedir in ('', None):
basedir = './'
path = os.path.realpath(os.path.join(basedir, 'group_vars'))
found_vars = set()
if os.path.exists(path):
found_vars = set(os.listdir(unicode(path)))
return found_vars
def _find_host_vars_files(self, basedir):
""" Find host_vars/ files """
if basedir in ('', None):
basedir = './'
path = os.path.realpath(os.path.join(basedir, 'host_vars'))
found_vars = set()
if os.path.exists(path):
found_vars = set(os.listdir(unicode(path)))
return found_vars
def _get_hostgroup_vars(self, host=None, group=None, new_pb_basedir=False, return_results=False):
"""
Loads variables from group_vars/<groupname> and host_vars/<hostname> in directories parallel
to the inventory base directory or in the same directory as the playbook. Variables in the playbook
@ -715,14 +783,15 @@ class Inventory(object):
results = {}
scan_pass = 0
_basedir = self.basedir()
_basedir = self._basedir
_playbook_basedir = self._playbook_basedir
# look in both the inventory base directory and the playbook base directory
# unless we do an update for a new playbook base dir
if not new_pb_basedir:
basedirs = [_basedir, self._playbook_basedir]
if not new_pb_basedir and _playbook_basedir:
basedirs = [_basedir, _playbook_basedir]
else:
basedirs = [self._playbook_basedir]
basedirs = [_basedir]
for basedir in basedirs:
# this can happen from particular API usages, particularly if not run
@ -737,17 +806,22 @@ class Inventory(object):
continue
# save work of second scan if the directories are the same
if _basedir == self._playbook_basedir and scan_pass != 1:
if _basedir == _playbook_basedir and scan_pass != 1:
continue
if group and host is None:
# Before trying to load vars from file, check that the directory contains relvant file names
if host is None and any(map(lambda ext: group.name + ext in self._group_vars_files, C.YAML_FILENAME_EXTENSIONS)):
# load vars in dir/group_vars/name_of_group
base_path = os.path.abspath(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:
base_path = to_unicode(os.path.abspath(os.path.join(to_bytes(basedir), b"group_vars/" + to_bytes(group.name))), errors='strict')
host_results = self._variable_manager.add_group_vars_file(base_path, self._loader)
if return_results:
results = combine_vars(results, host_results)
elif group is None and any(map(lambda ext: host.name + ext in self._host_vars_files, C.YAML_FILENAME_EXTENSIONS)):
# same for hostvars in dir/host_vars/name_of_host
base_path = os.path.abspath(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))
base_path = to_unicode(os.path.abspath(os.path.join(to_bytes(basedir), b"host_vars/" + to_bytes(host.name))), errors='strict')
group_results = self._variable_manager.add_host_vars_file(base_path, self._loader)
if return_results:
results = combine_vars(results, group_results)
# all done, results is a dictionary of variables for this particular host.
return results

View File

@ -140,10 +140,14 @@ class Group:
for kk in kid_hosts:
if kk not in seen:
seen[kk] = 1
if self.name == 'all' and kk.implicit:
continue
hosts.append(kk)
for mine in self.hosts:
if mine not in seen:
seen[mine] = 1
if self.name == 'all' and mine.implicit:
continue
hosts.append(mine)
return hosts

View File

@ -60,6 +60,7 @@ class Host:
uuid=self._uuid,
gathered_facts=self._gathered_facts,
groups=groups,
implicit=self.implicit,
)
def deserialize(self, data):
@ -69,6 +70,7 @@ class Host:
self.vars = data.get('vars', dict())
self.address = data.get('address', '')
self._uuid = data.get('uuid', uuid.uuid4())
self.implicit= data.get('implicit', False)
groups = data.get('groups', [])
for group_data in groups:
@ -89,6 +91,7 @@ class Host:
self._gathered_facts = False
self._uuid = uuid.uuid4()
self.implicit = False
def __repr__(self):
return self.get_name()

View File

@ -124,7 +124,7 @@ class InventoryParser(object):
del pending_declarations[groupname]
continue
elif line.startswith('['):
elif line.startswith('[') and line.endswith(']'):
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")

View File

@ -27,6 +27,7 @@ from ansible.inventory.group import Group
from ansible.inventory.expand_hosts import detect_range
from ansible.inventory.expand_hosts import expand_hostname_range
from ansible.parsing.utils.addresses import parse_address
from ansible.compat.six import string_types
class InventoryParser(object):
"""
@ -77,6 +78,11 @@ class InventoryParser(object):
self.groups[group] = Group(name=group)
if isinstance(group_data, dict):
#make sure they are dicts
for section in ['vars', 'children', 'hosts']:
if section in group_data and isinstance(group_data[section], string_types):
group_data[section] = { group_data[section]: None}
if 'vars' in group_data:
for var in group_data['vars']:
if var != 'ansible_group_priority':

View File

@ -15,3 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Note: Do not add any code to this file. module_utils may be a namespace
# package when using Ansible-2.1+ Anything in this file may not be available
# if one of the other packages in the namespace is loaded first.

View File

@ -24,7 +24,10 @@ import os
import re
import sys
import copy
import importlib
import inspect
from distutils.version import LooseVersion
from os.path import expanduser
from ansible.module_utils.basic import *
@ -66,8 +69,6 @@ CIDR_PATTERN = re.compile("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){
AZURE_SUCCESS_STATE = "Succeeded"
AZURE_FAILED_STATE = "Failed"
AZURE_MIN_VERSION = "2016-03-30"
HAS_AZURE = True
HAS_AZURE_EXC = None
@ -75,20 +76,19 @@ try:
from enum import Enum
from msrest.serialization import Serializer
from msrestazure.azure_exceptions import CloudError
from azure.mgmt.compute import __version__ as azure_compute_version
from azure.mgmt.network.models import PublicIPAddress, NetworkSecurityGroup, SecurityRule, NetworkInterface, \
NetworkInterfaceIPConfiguration, Subnet
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
from azure.mgmt.network.network_management_client import NetworkManagementClient,\
NetworkManagementClientConfiguration
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient,\
ResourceManagementClientConfiguration
from azure.mgmt.storage.storage_management_client import StorageManagementClient,\
StorageManagementClientConfiguration
from azure.mgmt.compute.compute_management_client import ComputeManagementClient,\
ComputeManagementClientConfiguration
from azure.mgmt.network.version import VERSION as network_client_version
from azure.mgmt.storage.version import VERSION as storage_client_version
from azure.mgmt.compute.version import VERSION as compute_client_version
from azure.mgmt.resource.version import VERSION as resource_client_version
from azure.mgmt.network.network_management_client import NetworkManagementClient
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient
from azure.mgmt.storage.storage_management_client import StorageManagementClient
from azure.mgmt.compute.compute_management_client import ComputeManagementClient
from azure.storage.cloudstorageaccount import CloudStorageAccount
except ImportError, exc:
except ImportError as exc:
HAS_AZURE_EXC = exc
HAS_AZURE = False
@ -103,6 +103,23 @@ def azure_id_to_dict(id):
return result
AZURE_EXPECTED_VERSIONS = dict(
storage_client_version="0.30.0rc5",
compute_client_version="0.30.0rc5",
network_client_version="0.30.0rc5",
resource_client_version="0.30.0rc5"
)
AZURE_MIN_RELEASE = '2.0.0rc5'
def check_client_version(client_name, client_version, expected_version):
# Pinning Azure modules to 2.0.0rc5.
if LooseVersion(client_version) != LooseVersion(expected_version):
self.fail("Installed {0} client version is {1}. The supported version is {2}. Try "
"`pip install azure=={3}`".format(client_name, client_version, expected_version,
AZURE_MIN_RELEASE))
class AzureRMModuleBase(object):
def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
@ -134,11 +151,8 @@ class AzureRMModuleBase(object):
required_if=merged_required_if)
if not HAS_AZURE:
self.fail("The Azure Python SDK is not installed (try 'pip install azure') - {0}".format(HAS_AZURE_EXC))
if azure_compute_version < AZURE_MIN_VERSION:
self.fail("Expecting azure.mgmt.compute.__version__ to be >= {0}. Found version {1} "
"Do you have Azure >= 2.0.0rc2 installed?".format(AZURE_MIN_VERSION, azure_compute_version))
self.fail("Do you have azure=={1} installed? Try `pip install azure=={1}`"
"- {0}".format(HAS_AZURE_EXC, AZURE_MIN_RELEASE))
self._network_client = None
self._storage_client = None
@ -146,7 +160,7 @@ class AzureRMModuleBase(object):
self._compute_client = None
self.check_mode = self.module.check_mode
self.facts_module = facts_module
self.debug = self.module.params.get('debug')
# self.debug = self.module.params.get('debug')
# authenticate
self.credentials = self._get_credentials(self.module.params)
@ -194,12 +208,12 @@ class AzureRMModuleBase(object):
def log(self, msg, pretty_print=False):
pass
# Use only during module development
# if self.debug:
# log_file = open('azure_rm.log', 'a')
# if pretty_print:
# log_file.write(json.dumps(msg, indent=4, sort_keys=True))
# else:
# log_file.write(msg + u'\n')
#if self.debug:
# log_file = open('azure_rm.log', 'a')
# if pretty_print:
# log_file.write(json.dumps(msg, indent=4, sort_keys=True))
# else:
# log_file.write(msg + u'\n')
def validate_tags(self, tags):
'''
@ -215,52 +229,6 @@ class AzureRMModuleBase(object):
if not isinstance(value, str):
self.fail("Tags values must be strings. Found {0}:{1}".format(str(key), str(value)))
def _tag_purge(self, tags):
'''
Remove metadata tags not found in user provided tags parameter. Returns tuple
with bool indicating something changed and dict of new tags to be assigned to
the object.
:param tags: object metadata tags
:return: bool, dict of tags
'''
if not self.module.params.get('tags'):
# purge all tags
return True, dict()
new_tags = copy.copy(tags)
changed = False
for key in tags:
if not self.module.params['tags'].get(key):
# key not found in user provided parameters
new_tags.pop(key)
changed = True
if changed:
self.log('CHANGED: purged tags')
return changed, new_tags
def _tag_update(self, tags):
'''
Update metadata tags with values in user provided tags parameter. Returns
tuple with bool indicating something changed and dict of new tags to be
assigned to the object.
:param tags: object metadata tags
:return: bool, dict of tags
'''
if isinstance(tags, dict):
new_tags = copy.copy(tags)
else:
new_tags = dict()
changed = False
if self.module.params.get('tags'):
for key, value in self.module.params['tags'].items():
if not (new_tags.get(key) and new_tags[key] == value):
changed = True
new_tags[key] = value
if changed:
self.log('CHANGED: updated tags')
return changed, new_tags
def update_tags(self, tags):
'''
Call from the module to update metadata tags. Returns tuple
@ -270,15 +238,18 @@ class AzureRMModuleBase(object):
:param tags: metadata tags from the object
:return: bool, dict
'''
new_tags = copy.copy(tags) if isinstance(tags, dict) else dict()
changed = False
updated, new_tags = self._tag_update(tags)
if updated:
changed = True
if not self.module.params['append_tags']:
purged, new_tags = self._tag_purge(new_tags)
if purged:
changed = True
if isinstance(self.module.params.get('tags'), dict):
for key, value in self.module.params['tags'].iteritems():
if not new_tags.get(key) or new_tags[key] != value:
changed = True
new_tags[key] = value
if isinstance(tags, dict):
for key, value in tags.iteritems():
if not self.module.params['tags'].get(key):
new_tags.pop(key)
changed = True
return changed, new_tags
def has_tags(self, obj_tags, tag_list):
@ -323,7 +294,7 @@ class AzureRMModuleBase(object):
return self.rm_client.resource_groups.get(resource_group)
except CloudError:
self.fail("Parameter error: resource group {0} not found".format(resource_group))
except Exception, exc:
except Exception as exc:
self.fail("Error retrieving resource group {0} - {1}".format(resource_group, str(exc)))
def _get_profile(self, profile="default"):
@ -331,7 +302,7 @@ class AzureRMModuleBase(object):
try:
config = ConfigParser.ConfigParser()
config.read(path)
except Exception, exc:
except Exception as exc:
self.fail("Failed to access {0}. Check that the file exists and you have read "
"access. {1}".format(path, str(exc)))
credentials = dict()
@ -341,21 +312,21 @@ class AzureRMModuleBase(object):
except:
pass
if credentials.get('client_id') is not None or credentials.get('ad_user') is not None:
if credentials.get('subscription_id'):
return credentials
return None
def _get_env_credentials(self):
env_credentials = dict()
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems():
env_credentials[attribute] = os.environ.get(env_variable, None)
if env_credentials['profile'] is not None:
if env_credentials['profile']:
credentials = self._get_profile(env_credentials['profile'])
return credentials
if env_credentials['client_id'] is not None:
if env_credentials.get('subscription_id') is not None:
return env_credentials
return None
@ -367,7 +338,7 @@ class AzureRMModuleBase(object):
self.log('Getting credentials')
arg_credentials = dict()
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.iteritems():
arg_credentials[attribute] = params.get(attribute, None)
# try module params
@ -376,7 +347,7 @@ class AzureRMModuleBase(object):
credentials = self._get_profile(arg_credentials['profile'])
return credentials
if arg_credentials['client_id'] is not None:
if arg_credentials['subscription_id']:
self.log('Received credentials from parameters.')
return arg_credentials
@ -394,18 +365,27 @@ class AzureRMModuleBase(object):
return None
def serialize_obj(self, obj, class_name):
def serialize_obj(self, obj, class_name, enum_modules=[]):
'''
Return a JSON representation of an Azure object.
:param obj: Azure object
:param class_name: Name of the object's class
:param enum_modules: List of module names to build enum dependencies from.
:return: serialized result
'''
serializer = Serializer()
dependencies = dict()
if enum_modules:
for module_name in enum_modules:
mod = importlib.import_module(module_name)
for mod_class_name, mod_class_obj in inspect.getmembers(mod, predicate=inspect.isclass):
dependencies[mod_class_name] = mod_class_obj
self.log("dependencies: ");
self.log(str(dependencies))
serializer = Serializer(classes=dependencies)
return serializer.body(obj, class_name)
def get_poller_result(self, poller, wait=20):
def get_poller_result(self, poller, wait=5):
'''
Consistent method of waiting on and retrieving results from Azure's long poller
@ -418,7 +398,7 @@ class AzureRMModuleBase(object):
self.log("Waiting for {0} sec".format(delay))
poller.wait(timeout=delay)
return poller.result()
except Exception, exc:
except Exception as exc:
self.log(str(exc))
raise
@ -463,15 +443,13 @@ class AzureRMModuleBase(object):
# Get keys from the storage account
self.log('Getting keys')
account_keys = self.storage_client.storage_accounts.list_keys(resource_group_name, storage_account_name)
keys['key1'] = account_keys.key1
keys['key2'] = account_keys.key2
except Exception, exc:
except Exception as exc:
self.fail("Error getting keys for account {0} - {1}".format(storage_account_name, str(exc)))
try:
self.log('Create blob service')
return CloudStorageAccount(storage_account_name, keys['key1']).create_block_blob_service()
except Exception, exc:
return CloudStorageAccount(storage_account_name, account_keys.keys[0].value).create_block_blob_service()
except Exception as exc:
self.fail("Error creating blob service client for storage account {0} - {1}".format(storage_account_name,
str(exc)))
@ -508,7 +486,7 @@ class AzureRMModuleBase(object):
self.log('Creating default public IP {0}'.format(public_ip_name))
try:
poller = self.network_client.public_ip_addresses.create_or_update(resource_group, public_ip_name, params)
except Exception, exc:
except Exception as exc:
self.fail("Error creating {0} - {1}".format(public_ip_name, str(exc)))
return self.get_poller_result(poller)
@ -578,7 +556,7 @@ class AzureRMModuleBase(object):
poller = self.network_client.network_security_groups.create_or_update(resource_group,
security_group_name,
parameters)
except Exception, exc:
except Exception as exc:
self.fail("Error creating default security rule {0} - {1}".format(security_group_name, str(exc)))
return self.get_poller_result(poller)
@ -589,16 +567,15 @@ class AzureRMModuleBase(object):
# time we attempt to use the requested client.
resource_client = self.rm_client
resource_client.providers.register(key)
except Exception, exc:
except Exception as exc:
self.fail("One-time registration of {0} failed - {1}".format(key, str(exc)))
@property
def storage_client(self):
self.log('Getting storage client...')
if not self._storage_client:
config = StorageManagementClientConfiguration(self.azure_credentials, self.subscription_id)
config.add_user_agent(ANSIBLE_USER_AGENT)
self._storage_client = StorageManagementClient(config)
check_client_version('storage', storage_client_version, AZURE_EXPECTED_VERSIONS['storage_client_version'])
self._storage_client = StorageManagementClient(self.azure_credentials, self.subscription_id)
self._register('Microsoft.Storage')
return self._storage_client
@ -606,9 +583,8 @@ class AzureRMModuleBase(object):
def network_client(self):
self.log('Getting network client')
if not self._network_client:
config = NetworkManagementClientConfiguration(self.azure_credentials, self.subscription_id)
config.add_user_agent(ANSIBLE_USER_AGENT)
self._network_client = NetworkManagementClient(config)
check_client_version('network', network_client_version, AZURE_EXPECTED_VERSIONS['network_client_version'])
self._network_client = NetworkManagementClient(self.azure_credentials, self.subscription_id)
self._register('Microsoft.Network')
return self._network_client
@ -616,17 +592,15 @@ class AzureRMModuleBase(object):
def rm_client(self):
self.log('Getting resource manager client')
if not self._resource_client:
config = ResourceManagementClientConfiguration(self.azure_credentials, self.subscription_id)
config.add_user_agent(ANSIBLE_USER_AGENT)
self._resource_client = ResourceManagementClient(config)
check_client_version('resource', resource_client_version, AZURE_EXPECTED_VERSIONS['resource_client_version'])
self._resource_client = ResourceManagementClient(self.azure_credentials, self.subscription_id)
return self._resource_client
@property
def compute_client(self):
self.log('Getting compute client')
if not self._compute_client:
config = ComputeManagementClientConfiguration(self.azure_credentials, self.subscription_id)
config.add_user_agent(ANSIBLE_USER_AGENT)
self._compute_client = ComputeManagementClient(config)
check_client_version('compute', compute_client_version, AZURE_EXPECTED_VERSIONS['compute_client_version'])
self._compute_client = ComputeManagementClient(self.azure_credentials, self.subscription_id)
self._register('Microsoft.Compute')
return self._compute_client

View File

@ -27,8 +27,8 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
BOOLEANS_TRUE = ['yes', 'on', '1', 'true', 1, True]
BOOLEANS_FALSE = ['no', 'off', '0', 'false', 0, False]
BOOLEANS_TRUE = ['yes', 'on', '1', 'true', 'True', 1, True]
BOOLEANS_FALSE = ['no', 'off', '0', 'false', 'False', 0, False]
BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE
# ansible modules can be written in any language. To simplify
@ -136,10 +136,10 @@ except ImportError:
try:
import simplejson as json
except ImportError:
print('{"msg": "Error: ansible requires the stdlib json or simplejson module, neither was found!", "failed": true}')
print('\n{"msg": "Error: ansible requires the stdlib json or simplejson module, neither was found!", "failed": true}')
sys.exit(1)
except SyntaxError:
print('{"msg": "SyntaxError: probably due to installed simplejson being for a different python version", "failed": true}')
print('\n{"msg": "SyntaxError: probably due to installed simplejson being for a different python version", "failed": true}')
sys.exit(1)
HAVE_SELINUX=False
@ -219,14 +219,10 @@ except ImportError:
_literal_eval = literal_eval
from ansible import __version__
# Backwards compat. New code should just import and use __version__
ANSIBLE_VERSION = __version__
# Internal global holding passed in params and constants. This is consulted
# in case multiple AnsibleModules are created. Otherwise each AnsibleModule
# would attempt to read from stdin. Other code should not use this directly
# as it is an internal implementation detail
# Internal global holding passed in params. This is consulted in case
# multiple AnsibleModules are created. Otherwise each AnsibleModule would
# attempt to read from stdin. Other code should not use this directly as it
# is an internal implementation detail
_ANSIBLE_ARGS = None
FILE_COMMON_ARGUMENTS=dict(
@ -524,9 +520,59 @@ def is_executable(path):
or stat.S_IXGRP & os.stat(path)[stat.ST_MODE]
or stat.S_IXOTH & os.stat(path)[stat.ST_MODE])
def _load_params():
''' read the modules parameters and store them globally.
class AnsibleFallbackNotFound(Exception):
pass
This function may be needed for certain very dynamic custom modules which
want to process the parameters that are being handed the module. Since
this is so closely tied to the implementation of modules we cannot
guarantee API stability for it (it may change between versions) however we
will try not to break it gratuitously. It is certainly more future-proof
to call this function and consume its outputs than to implement the logic
inside it as a copy in your own code.
'''
global _ANSIBLE_ARGS
if _ANSIBLE_ARGS is not None:
buffer = _ANSIBLE_ARGS
else:
# debug overrides to read args from file or cmdline
# Avoid tracebacks when locale is non-utf8
# We control the args and we pass them as utf8
if len(sys.argv) > 1:
if os.path.isfile(sys.argv[1]):
fd = open(sys.argv[1], 'rb')
buffer = fd.read()
fd.close()
else:
buffer = sys.argv[1]
if sys.version_info >= (3,):
buffer = buffer.encode('utf-8', errors='surrogateescape')
# default case, read from stdin
else:
if sys.version_info < (3,):
buffer = sys.stdin.read()
else:
buffer = sys.stdin.buffer.read()
_ANSIBLE_ARGS = buffer
try:
params = json.loads(buffer.decode('utf-8'))
except ValueError:
# This helper used too early for fail_json to work.
print('\n{"msg": "Error: Module unable to decode valid JSON on stdin. Unable to figure out what parameters were passed", "failed": true}')
sys.exit(1)
if sys.version_info < (3,):
params = json_dict_unicode_to_bytes(params)
try:
return params['ANSIBLE_MODULE_ARGS']
except KeyError:
# This helper does not have access to fail_json so we have to print
# json output on our own.
print('\n{"msg": "Error: Module unable to locate ANSIBLE_MODULE_ARGS in json data from stdin. Unable to figure out what parameters were passed", "failed": true}')
sys.exit(1)
def env_fallback(*args, **kwargs):
''' Load value from environment '''
@ -536,6 +582,23 @@ def env_fallback(*args, **kwargs):
else:
raise AnsibleFallbackNotFound
def _lenient_lowercase(lst):
"""Lowercase elements of a list.
If an element is not a string, pass it through untouched.
"""
lowered = []
for value in lst:
try:
lowered.append(value.lower())
except AttributeError:
lowered.append(value)
return lowered
class AnsibleFallbackNotFound(Exception):
pass
class AnsibleModule(object):
def __init__(self, argument_spec, bypass_checks=False, no_log=False,
@ -562,7 +625,7 @@ class AnsibleModule(object):
self.run_command_environ_update = {}
self.aliases = {}
self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug', '_ansible_diff', '_ansible_verbosity']
self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug', '_ansible_diff', '_ansible_verbosity', '_ansible_selinux_special_fs', '_ansible_version', '_ansible_syslog_facility']
if add_file_common_args:
for k, v in FILE_COMMON_ARGUMENTS.items():
@ -578,7 +641,7 @@ class AnsibleModule(object):
except Exception:
e = get_exception()
# Use exceptions here because it isn't safe to call fail_json until no_log is processed
print('{"failed": true, "msg": "Module alias error: %s"}' % str(e))
print('\n{"failed": true, "msg": "Module alias error: %s"}' % str(e))
sys.exit(1)
# Save parameter values that should never be logged
@ -613,6 +676,7 @@ class AnsibleModule(object):
'float': self._check_type_float,
'path': self._check_type_path,
'raw': self._check_type_raw,
'jsonarg': self._check_type_jsonarg,
}
if not bypass_checks:
self._check_required_arguments()
@ -764,7 +828,7 @@ class AnsibleModule(object):
return (uid, gid)
def find_mount_point(self, path):
path = os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
path = os.path.realpath(os.path.expanduser(os.path.expandvars(path)))
while not os.path.ismount(path):
path = os.path.dirname(path)
return path
@ -785,7 +849,7 @@ class AnsibleModule(object):
(device, mount_point, fstype, options, rest) = line.split(' ', 4)
if path_mount_point == mount_point:
for fs in self.constants['SELINUX_SPECIAL_FS']:
for fs in self._selinux_special_fs:
if fs in fstype:
special_context = self.selinux_context(path_mount_point)
return (True, special_context)
@ -1178,7 +1242,8 @@ class AnsibleModule(object):
return aliases_results
def _check_arguments(self, check_invalid_arguments):
for (k,v) in self.params.items():
self._syslog_facility = 'LOG_USER'
for (k,v) in list(self.params.items()):
if k == '_ansible_check_mode' and v:
if not self.supports_check_mode:
@ -1197,6 +1262,15 @@ class AnsibleModule(object):
elif k == '_ansible_verbosity':
self._verbosity = v
elif k == '_ansible_selinux_special_fs':
self._selinux_special_fs = v
elif k == '_ansible_syslog_facility':
self._syslog_facility = v
elif k == '_ansible_version':
self.ansible_version = v
elif check_invalid_arguments and k not in self._legal_inputs:
self.fail_json(msg="unsupported parameter for module: %s" % k)
@ -1270,9 +1344,28 @@ class AnsibleModule(object):
if type(choices) == list:
if k in self.params:
if self.params[k] not in choices:
choices_str=",".join([str(c) for c in choices])
msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k])
self.fail_json(msg=msg)
# PyYaml converts certain strings to bools. If we can unambiguously convert back, do so before checking the value. If we can't figure this out, module author is responsible.
lowered_choices = None
if self.params[k] == 'False':
lowered_choices = _lenient_lowercase(choices)
FALSEY = frozenset(BOOLEANS_FALSE)
overlap = FALSEY.intersection(choices)
if len(overlap) == 1:
# Extract from a set
(self.params[k],) = overlap
if self.params[k] == 'True':
if lowered_choices is None:
lowered_choices = _lenient_lowercase(choices)
TRUTHY = frozenset(BOOLEANS_TRUE)
overlap = TRUTHY.intersection(choices)
if len(overlap) == 1:
(self.params[k],) = overlap
if self.params[k] not in choices:
choices_str=",".join([str(c) for c in choices])
msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k])
self.fail_json(msg=msg)
else:
self.fail_json(msg="internal error: do not know how to interpret argument_spec")
@ -1390,7 +1483,7 @@ class AnsibleModule(object):
if isinstance(value, float):
return value
if isinstance(value, basestring):
if isinstance(value, (bytes, unicode, int)):
return float(value)
raise TypeError('%s cannot be converted to a float' % type(value))
@ -1399,6 +1492,16 @@ class AnsibleModule(object):
value = self._check_type_str(value)
return os.path.expanduser(os.path.expandvars(value))
def _check_type_jsonarg(self, value):
# Return a jsonified string. Sometimes the controller turns a json
# string into a dict/list so transform it back into json here
if isinstance(value, (unicode, bytes)):
return value.strip()
else:
if isinstance(value (list, tuple, dict)):
return json.dumps(value)
raise TypeError('%s cannot be converted to a json string' % type(value))
def _check_type_raw(self, value):
return value
@ -1460,54 +1563,18 @@ class AnsibleModule(object):
continue
def _load_params(self):
''' read the input and set the params attribute. Sets the constants as well.'''
''' read the input and set the params attribute.
This method is for backwards compatibility. The guts of the function
were moved out in 2.1 so that custom modules could read the parameters.
'''
# debug overrides to read args from file or cmdline
global _ANSIBLE_ARGS
if _ANSIBLE_ARGS is not None:
buffer = _ANSIBLE_ARGS
else:
# Avoid tracebacks when locale is non-utf8
# We control the args and we pass them as utf8
if len(sys.argv) > 1:
if os.path.isfile(sys.argv[1]):
fd = open(sys.argv[1], 'rb')
buffer = fd.read()
fd.close()
else:
buffer = sys.argv[1]
if sys.version_info >= (3,):
buffer = buffer.encode('utf-8', errors='surrogateescape')
# default case, read from stdin
else:
if sys.version_info < (3,):
buffer = sys.stdin.read()
else:
buffer = sys.stdin.buffer.read()
_ANSIBLE_ARGS = buffer
try:
params = json.loads(buffer.decode('utf-8'))
except ValueError:
# This helper used too early for fail_json to work.
print('{"msg": "Error: Module unable to decode valid JSON on stdin. Unable to figure out what parameters were passed", "failed": true}')
sys.exit(1)
if sys.version_info < (3,):
params = json_dict_unicode_to_bytes(params)
try:
self.params = params['ANSIBLE_MODULE_ARGS']
self.constants = params['ANSIBLE_MODULE_CONSTANTS']
except KeyError:
# This helper used too early for fail_json to work.
print('{"msg": "Error: Module unable to locate ANSIBLE_MODULE_ARGS and ANSIBLE_MODULE_CONSTANTS in json data from stdin. Unable to figure out what parameters were passed", "failed": true}')
sys.exit(1)
self.params = _load_params()
def _log_to_syslog(self, msg):
if HAS_SYSLOG:
module = 'ansible-%s' % os.path.basename(__file__)
facility = getattr(syslog, self.constants.get('SYSLOG_FACILITY', 'LOG_USER'), syslog.LOG_USER)
facility = getattr(syslog, self._syslog_facility, syslog.LOG_USER)
syslog.openlog(str(module), 0, facility)
syslog.syslog(syslog.LOG_INFO, msg)
@ -1693,7 +1760,7 @@ class AnsibleModule(object):
kwargs['invocation'] = {'module_args': self.params}
kwargs = remove_values(kwargs, self.no_log_values)
self.do_cleanup_files()
print(self.jsonify(kwargs))
print('\n%s' % self.jsonify(kwargs))
sys.exit(0)
def fail_json(self, **kwargs):
@ -1705,7 +1772,7 @@ class AnsibleModule(object):
kwargs['invocation'] = {'module_args': self.params}
kwargs = remove_values(kwargs, self.no_log_values)
self.do_cleanup_files()
print(self.jsonify(kwargs))
print('\n%s' % self.jsonify(kwargs))
sys.exit(1)
def fail_on_missing_params(self, required_params=None):
@ -1975,6 +2042,21 @@ class AnsibleModule(object):
old_env_vals['PATH'] = os.environ['PATH']
os.environ['PATH'] = "%s:%s" % (path_prefix, os.environ['PATH'])
# If using test-module and explode, the remote lib path will resemble ...
# /tmp/test_module_scratch/debug_dir/ansible/module_utils/basic.py
# If using ansible or ansible-playbook with a remote system ...
# /tmp/ansible_vmweLQ/ansible_modlib.zip/ansible/module_utils/basic.py
# Clean out python paths set by ziploader
if 'PYTHONPATH' in os.environ:
pypaths = os.environ['PYTHONPATH'].split(':')
pypaths = [x for x in pypaths \
if not x.endswith('/ansible_modlib.zip') \
and not x.endswith('/debug_dir')]
os.environ['PYTHONPATH'] = ':'.join(pypaths)
if not os.environ['PYTHONPATH']:
del os.environ['PYTHONPATH']
# create a printable version of the command for use
# in reporting later, which strips out things like
# passwords from the args list
@ -2016,7 +2098,6 @@ class AnsibleModule(object):
stdin=st_in,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=os.environ,
)
if cwd and os.path.isdir(cwd):

View File

@ -93,6 +93,9 @@ class AnsibleCloudStack(object):
# these keys will be compared case sensitive in self.has_changed()
self.case_sensitive_keys = [
'id',
'displaytext',
'displayname',
'description',
]
self.module = module
@ -154,12 +157,27 @@ class AnsibleCloudStack(object):
continue
if key in current_dict:
if self.case_sensitive_keys and key in self.case_sensitive_keys:
if str(value) != str(current_dict[key]):
if isinstance(value, (int, float, long, complex)):
# ensure we compare the same type
if isinstance(value, int):
current_dict[key] = int(current_dict[key])
elif isinstance(value, float):
current_dict[key] = float(current_dict[key])
elif isinstance(value, long):
current_dict[key] = long(current_dict[key])
elif isinstance(value, complex):
current_dict[key] = complex(current_dict[key])
if value != current_dict[key]:
return True
else:
if self.case_sensitive_keys and key in self.case_sensitive_keys:
if value != current_dict[key].encode('utf-8'):
return True
# Test for diff in case insensitive way
elif value.lower() != current_dict[key].encode('utf-8').lower():
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

View File

@ -1,20 +1,29 @@
# 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.
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com>
#
# This file is part of Ansible
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 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.
# * 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.
#
# 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 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.
#
import re

View File

@ -31,7 +31,13 @@ import struct
import datetime
import getpass
import pwd
import ConfigParser
try:
# python2
import ConfigParser as configparser
except ImportError:
# python3
import configparser
from ansible.module_utils.basic import get_all_subclasses
# py2 vs py3; replace with six via ziploader
@ -40,7 +46,12 @@ try:
except ImportError:
from io import StringIO
from string import maketrans
try:
# python2
from string import maketrans
except ImportError:
# python3
maketrans = str.maketrans
try:
import selinux
@ -147,6 +158,7 @@ class Facts(object):
{ 'path' : '/usr/sbin/urpmi', 'name' : 'urpmi' },
{ 'path' : '/usr/bin/pacman', 'name' : 'pacman' },
{ 'path' : '/bin/opkg', 'name' : 'opkg' },
{ 'path' : '/usr/pkg/bin/pkgin', 'name' : 'pkgin' },
{ 'path' : '/opt/local/bin/pkgin', 'name' : 'pkgin' },
{ 'path' : '/opt/local/bin/port', 'name' : 'macports' },
{ 'path' : '/usr/local/bin/brew', 'name' : 'homebrew' },
@ -260,10 +272,10 @@ class Facts(object):
fact = json.loads(out)
except ValueError:
# load raw ini
cp = ConfigParser.ConfigParser()
cp = configparser.ConfigParser()
try:
cp.readfp(StringIO(out))
except ConfigParser.Error:
except configparser.Error:
fact = "error loading fact - please check content"
else:
fact = {}
@ -1127,6 +1139,8 @@ class LinuxHardware(Hardware):
mtab = get_file_content('/etc/mtab', '')
for line in mtab.split('\n'):
fields = line.rstrip('\n').split()
if len(fields) < 4:
continue
if fields[0].startswith('/') or ':/' in fields[0]:
if(fields[2] != 'none'):
size_total, size_available = self._get_mount_size_facts(fields[1])
@ -1176,7 +1190,8 @@ class LinuxHardware(Hardware):
sysfs_no_links = 0
try:
path = os.readlink(os.path.join("/sys/block/", block))
except OSError, e:
except OSError:
e = sys.exc_info()[1]
if e.errno == errno.EINVAL:
path = block
sysfs_no_links = 1

View File

@ -27,18 +27,29 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import json
import os
import traceback
from distutils.version import LooseVersion
from libcloud.compute.types import Provider
from libcloud.compute.providers import get_driver
try:
from libcloud.compute.types import Provider
import libcloud
from libcloud.compute.providers import get_driver
HAS_LIBCLOUD_BASE = True
except ImportError:
HAS_LIBCLOUD_BASE = False
USER_AGENT_PRODUCT="Ansible-gce"
USER_AGENT_VERSION="v1"
def gce_connect(module, provider=None):
"""Return a Google Cloud Engine connection."""
if not HAS_LIBCLOUD_BASE:
module.fail_json(msg='libcloud must be installed to use this module')
service_account_email = module.params.get('service_account_email', None)
credentials_file = module.params.get('credentials_file', None)
pem_file = module.params.get('pem_file', None)
project_id = module.params.get('project_id', None)
@ -50,6 +61,8 @@ def gce_connect(module, provider=None):
project_id = os.environ.get('GCE_PROJECT', None)
if not pem_file:
pem_file = os.environ.get('GCE_PEM_FILE_PATH', None)
if not credentials_file:
credentials_file = os.environ.get('GCE_CREDENTIALS_FILE_PATH', pem_file)
# If we still don't have one or more of our credentials, attempt to
# get the remaining values from the libcloud secrets file.
@ -62,25 +75,41 @@ def gce_connect(module, provider=None):
if hasattr(secrets, 'GCE_PARAMS'):
if not service_account_email:
service_account_email = secrets.GCE_PARAMS[0]
if not pem_file:
pem_file = secrets.GCE_PARAMS[1]
if not credentials_file:
credentials_file = secrets.GCE_PARAMS[1]
keyword_params = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
if not project_id:
project_id = keyword_params.get('project', None)
# If we *still* don't have the credentials we need, then it's time to
# just fail out.
if service_account_email is None or pem_file is None or project_id is None:
if service_account_email is None or credentials_file is None or project_id is None:
module.fail_json(msg='Missing GCE connection parameters in libcloud '
'secrets file.')
return None
else:
# We have credentials but lets make sure that if they are JSON we have the minimum
# libcloud requirement met
try:
# Try to read credentials as JSON
with open(credentials_file) as credentials:
json.loads(credentials.read())
# If the credentials are proper JSON and we do not have the minimum
# required libcloud version, bail out and return a descriptive error
if LooseVersion(libcloud.__version__) < '0.17.0':
module.fail_json(msg='Using JSON credentials but libcloud minimum version not met. '
'Upgrade to libcloud>=0.17.0.')
return None
except ValueError, e:
# Not JSON
pass
# Allow for passing in libcloud Google DNS (e.g, Provider.GOOGLE)
if provider is None:
provider = Provider.GCE
try:
gce = get_driver(provider)(service_account_email, pem_file,
gce = get_driver(provider)(service_account_email, credentials_file,
datacenter=module.params.get('zone', None),
project=project_id)
gce.connection.user_agent_append("%s/%s" % (

View File

@ -77,9 +77,14 @@ class Cli(object):
key_filename = self.module.params['ssh_keyfile']
timeout = self.module.params['timeout']
allow_agent = (key_filename is not None) or (key_filename is None and password is None)
try:
self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE)
self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename, timeout=timeout)
self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE,
errors_re=CLI_ERRORS_RE)
self.shell.open(host, port=port, username=username,
password=password, key_filename=key_filename,
allow_agent=allow_agent, timeout=timeout)
except ShellError:
e = get_exception()
msg = 'failed to connect to %s:%s - %s' % (host, port, str(e))

View File

@ -1,20 +1,29 @@
# 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.
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com>
#
# This file is part of Ansible
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 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.
# * 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.
#
# 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 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.
#
import re

View File

@ -93,9 +93,12 @@ class Cli(object):
password = self.module.params['password']
key_filename = self.module.params['ssh_keyfile']
allow_agent = (key_filename is not None) or (key_filename is None and password is None)
try:
self.shell = Shell()
self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename)
self.shell.open(host, port=port, username=username, password=password,
key_filename=key_filename, allow_agent=allow_agent)
except ShellError:
e = get_exception()
msg = 'failed to connect to %s:%s - %s' % (host, port, str(e))
@ -152,9 +155,10 @@ class Netconf(object):
user = self.module.params['username']
passwd = self.module.params['password']
key_filename = self.module.params['ssh_keyfile']
self.device = Device(host, user=user, passwd=passwd, port=port,
gather_facts=False).open()
gather_facts=False, ssh_private_key_file=key_filename).open()
self.config = Config(self.device)
@ -350,6 +354,8 @@ def get_module(**kwargs):
module.fail_json(msg='paramiko is required but does not appear to be installed')
elif module.params['transport'] == 'netconf' and not HAS_PYEZ:
module.fail_json(msg='junos-eznc >= 1.2.2 is required but does not appear to be installed')
elif module.params['transport'] == 'netconf' and not HAS_JXMLEASE:
module.fail_json(msg='jxmlease is required but does not appear to be installed')
module.connect()
return module

View File

@ -1,24 +1,32 @@
# 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.
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
# Copyright (c) 2015 Peter Sprygada, <psprygada@ansible.com>
#
# This file is part of Ansible
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 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.
# * 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.
#
# 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 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.
#
import re
import collections
import itertools
import shlex
@ -109,10 +117,9 @@ class NetworkConfig(object):
return self._config
def __str__(self):
config = collections.OrderedDict()
for item in self._config:
self.expand(item, config)
return '\n'.join(self.flatten(config))
if self._device_os == 'junos':
return self.to_lines(self.expand(self.items))
return self.to_block(self.expand(self.items))
def load(self, contents):
self._config = parse(contents, indent=self.indent)
@ -154,26 +161,29 @@ class NetworkConfig(object):
regexp = r'%s' % regexp
return re.findall(regexp, str(self))
def expand(self, obj, items):
block = [item.raw for item in obj.parents]
block.append(obj.raw)
def to_lines(self, section):
lines = list()
for entry in section[1:]:
line = ['set']
line.extend([p.text for p in entry.parents])
line.append(entry.text)
lines.append(' '.join(line))
return lines
current_level = items
for b in block:
if b not in current_level:
current_level[b] = collections.OrderedDict()
current_level = current_level[b]
for c in obj.children:
if c.raw not in current_level:
current_level[c.raw] = collections.OrderedDict()
def to_block(self, section):
return '\n'.join([item.raw for item in section])
def flatten(self, data, obj=None):
if obj is None:
obj = list()
for k, v in data.items():
obj.append(k)
self.flatten(v, obj)
return obj
def expand(self, objs):
visited = set()
expanded = list()
for o in objs:
for p in o.parents:
if p not in visited:
visited.add(p)
expanded.append(p)
expanded.append(o)
visited.add(o)
return expanded
def get_object(self, path):
for item in self.items:
@ -229,13 +239,20 @@ class NetworkConfig(object):
if self._device_os == 'junos':
return updates
diffs = dict()
changes = list()
for update in updates:
if replace == 'block' and update.parents:
update = update.parents[-1]
self.expand(update, diffs)
if replace == 'block':
if update.parents:
changes.append(update.parents[-1])
for child in update.parents[-1].children:
changes.append(child)
else:
changes.append(update)
else:
changes.append(update)
updates = self.expand(changes)
return self.flatten(diffs)
return [item.text for item in self.expand(updates)]
def _build_children(self, children, parents=None, offset=0):
for item in children:
@ -259,6 +276,8 @@ class NetworkConfig(object):
config.append(line)
if parent:
parent.children.append(line)
if parent.parents:
line.parents.append(*parent.parents)
line.parents.append(parent)
parent = line
offset += self.indent
@ -382,7 +401,7 @@ class Conditional(object):
return self.number(value) <= self.value
def contains(self, value):
return self.value in value
return str(self.value) in value

View File

@ -35,7 +35,8 @@ NET_COMMON_ARGS = dict(
transport=dict(default='cli', choices=['cli', 'nxapi']),
use_ssl=dict(default=False, type='bool'),
validate_certs=dict(default=True, type='bool'),
provider=dict(type='dict')
provider=dict(type='dict'),
timeout=dict(default=10, type='int')
)
NXAPI_COMMAND_TYPES = ['cli_show', 'cli_show_ascii', 'cli_conf', 'bash']
@ -168,11 +169,17 @@ class Cli(object):
username = self.module.params['username']
password = self.module.params['password']
timeout = self.module.params['timeout']
key_filename = self.module.params['ssh_keyfile']
allow_agent = (key_filename is not None) or (key_filename is None and password is None)
try:
self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE, errors_re=CLI_ERRORS_RE)
self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename)
self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE,
errors_re=CLI_ERRORS_RE)
self.shell.open(host, port=port, username=username,
password=password, key_filename=key_filename,
allow_agent=allow_agent, timeout=timeout)
except ShellError:
e = get_exception()
msg = 'failed to connect to %s:%s - %s' % (host, port, str(e))

View File

@ -114,7 +114,20 @@ class Rest(object):
if not port:
port = 80
self.baseurl = '%s://%s:%s/rest/v1' % (proto, host, port)
baseurl = '%s://%s:%s' % (proto, host, port)
headers = dict({'Content-Type': 'application/x-www-form-urlencoded'})
# Get a cookie and save it the rest of the operations.
url = '%s/%s' % (baseurl, 'login')
data = 'username=%s&password=%s' % (self.module.params['username'],
self.module.params['password'])
resp, hdrs = fetch_url(self.module, url, data=data,
headers=headers, method='POST')
# Update the base url for the rest of the operations.
self.baseurl = '%s/rest/v1' % (baseurl)
self.headers = dict({'Content-Type': 'application/json',
'Accept': 'application/json',
'Cookie': resp.headers.get('Set-Cookie')})
def _url_builder(self, path):
if path[0] == '/':
@ -127,7 +140,7 @@ class Rest(object):
if headers is None:
headers = dict()
headers.update({'Content-Type': 'application/json'})
headers.update(self.headers)
resp, hdrs = fetch_url(self.module, url, data=data, headers=headers,
method=method)

View File

@ -32,7 +32,6 @@ import os
import re
from uuid import UUID
from ansible import __version__
from ansible.module_utils.basic import BOOLEANS
FINAL_STATUSES = ('ACTIVE', 'ERROR')
@ -264,7 +263,7 @@ def rax_required_together():
def setup_rax_module(module, rax_module, region_required=True):
"""Set up pyrax in a standard way for all modules"""
rax_module.USER_AGENT = 'ansible/%s %s' % (__version__,
rax_module.USER_AGENT = 'ansible/%s %s' % (module.ansible_version,
rax_module.USER_AGENT)
api_key = module.params.get('api_key')

View File

@ -27,6 +27,7 @@ except ImportError:
try:
import paramiko
from paramiko.ssh_exception import AuthenticationException
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
@ -101,12 +102,17 @@ class Shell(object):
if not look_for_keys:
look_for_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)
try:
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(timeout)
self.shell = self.ssh.invoke_shell()
self.shell.settimeout(timeout)
except socket.gaierror:
raise ShellError("unable to resolve host name")
except AuthenticationException:
raise ShellError('Unable to authenticate to remote device')
if self.kickstart:
self.shell.sendall("\n")

View File

@ -81,7 +81,6 @@
# agrees to be bound by the terms and conditions of this License
# Agreement.
import httplib
import netrc
import os
import re
@ -91,7 +90,13 @@ import platform
import tempfile
import base64
from ansible.module_utils.basic import get_distribution
from ansible.module_utils.basic import get_distribution, get_exception
try:
import httplib
except ImportError:
# Python 3
import http.client as httplib
try:
import urllib2
@ -626,6 +631,13 @@ class SSLValidationHandler(urllib2.BaseHandler):
use_proxy = self.detect_no_proxy(req.get_full_url())
if not use_proxy:
try:
# cleanup the temp file created, don't worry
# if it fails for some reason
os.remove(tmp_ca_cert_path)
except:
pass
# ignore proxy settings for this host request
return req
@ -664,7 +676,8 @@ class SSLValidationHandler(urllib2.BaseHandler):
# close the ssl connection
#ssl_s.unwrap()
s.close()
except (ssl.SSLError, socket.error), e:
except (ssl.SSLError, socket.error):
e = get_exception()
# fail if we tried all of the certs but none worked
if 'connection refused' in str(e).lower():
raise ConnectionError('Failed to connect to %s:%s.' % (self.hostname, self.port))
@ -887,27 +900,34 @@ def fetch_url(module, url, data=None, headers=None, method=None,
url_password=password, http_agent=http_agent, force_basic_auth=force_basic_auth,
follow_redirects=follow_redirects)
info.update(r.info())
info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), url=r.geturl(), status=r.getcode()))
except NoSSLError, e:
info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 'unknown'), url=r.geturl(), status=r.code))
except NoSSLError:
e = get_exception()
distribution = get_distribution()
if distribution is not None and distribution.lower() == 'redhat':
module.fail_json(msg='%s. You can also install python-ssl from EPEL' % str(e))
else:
module.fail_json(msg='%s' % str(e))
except (ConnectionError, ValueError), e:
except (ConnectionError, ValueError):
e = get_exception()
module.fail_json(msg=str(e))
except urllib2.HTTPError, e:
except urllib2.HTTPError:
e = get_exception()
try:
body = e.read()
except AttributeError:
body = ''
info.update(dict(msg=str(e), status=e.code, body=body, **e.info()))
except urllib2.URLError, e:
info.update(dict(msg=str(e), body=body, **e.info()))
info['status'] = e.code
except urllib2.URLError:
e = get_exception()
code = int(getattr(e, 'code', -1))
info.update(dict(msg="Request failed: %s" % str(e), status=code))
except socket.error, e:
except socket.error:
e = get_exception()
info.update(dict(msg="Connection failure: %s" % str(e), status=-1))
except Exception, e:
except Exception:
e = get_exception()
info.update(dict(msg="An unknown error occurred: %s" % str(e), status=-1))
return r, info

View File

@ -46,7 +46,8 @@ def vca_argument_spec():
api_version=dict(default=DEFAULT_VERSION),
service_type=dict(default=DEFAULT_SERVICE_TYPE, choices=SERVICE_MAP.keys()),
vdc_name=dict(),
gateway_name=dict(default='gateway')
gateway_name=dict(default='gateway'),
verify_certs=dict(type='bool', default=True)
)
class VcaAnsibleModule(AnsibleModule):
@ -110,7 +111,7 @@ class VcaAnsibleModule(AnsibleModule):
def create_instance(self):
service_type = self.params.get('service_type', DEFAULT_SERVICE_TYPE)
if service_type == 'vcd':
if service_type == 'vcd':
host = self.params['host']
else:
host = LOGIN_HOST[service_type]
@ -130,8 +131,12 @@ class VcaAnsibleModule(AnsibleModule):
service_type = self.params['service_type']
password = self.params['password']
if not self.vca.login(password=password):
self.fail('Login to VCA failed', response=self.vca.response.content)
login_org = None
if service_type == 'vcd':
login_org = self.params['org']
if not self.vca.login(password=password, org=login_org):
self.fail('Login to VCA failed', response=self.vca.response)
try:
method_name = 'login_%s' % service_type
@ -140,7 +145,7 @@ class VcaAnsibleModule(AnsibleModule):
except AttributeError:
self.fail('no login method exists for service_type %s' % service_type)
except VcaError, e:
self.fail(e.message, response=self.vca.response.content, **e.kwargs)
self.fail(e.message, response=self.vca.response, **e.kwargs)
def login_vca(self):
instance_id = self.params['instance_id']
@ -155,14 +160,14 @@ class VcaAnsibleModule(AnsibleModule):
org = self.params['org']
if not org:
raise VcaError('missing required or for service_type vchs')
raise VcaError('missing required org for service_type vchs')
self.vca.login_to_org(service_id, org)
def login_vcd(self):
org = self.params['org']
if not org:
raise VcaError('missing required or for service_type vchs')
raise VcaError('missing required org for service_type vcd')
if not self.vca.token:
raise VcaError('unable to get token for service_type vcd')

View File

@ -160,12 +160,13 @@ class DataLoader():
if not file_name or not isinstance(file_name, string_types):
raise AnsibleParserError("Invalid filename: '%s'" % str(file_name))
if not self.path_exists(file_name) or not self.is_file(file_name):
b_file_name = to_bytes(file_name)
if not self.path_exists(b_file_name) or not self.is_file(b_file_name):
raise AnsibleFileNotFound("the file_name '%s' does not exist, or is not readable" % file_name)
show_content = True
try:
with open(file_name, 'rb') as f:
with open(b_file_name, 'rb') as f:
data = f.read()
if self._vault.is_encrypted(data):
data = self._vault.decrypt(data)

View File

@ -30,6 +30,12 @@ from hashlib import sha256
from binascii import hexlify
from binascii import unhexlify
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
# Note: Only used for loading obsolete VaultAES files. All files are written
# using the newer VaultAES256 which does not require md5
from hashlib import md5
@ -70,6 +76,10 @@ try:
HAS_PBKDF2HMAC = True
except ImportError:
pass
except Exception as e:
display.warning("Optional dependency 'cryptography' raised an exception, falling back to 'Crypto'")
import traceback
display.debug("Traceback from import of cryptography was {0}".format(traceback.format_exc()))
from ansible.compat.six import PY3
from ansible.utils.unicode import to_unicode, to_bytes
@ -445,7 +455,7 @@ class VaultEditor:
os.chown(dest, prev.st_uid, prev.st_gid)
def _editor_shell_command(self, filename):
EDITOR = os.environ.get('EDITOR','vim')
EDITOR = os.environ.get('EDITOR','vi')
editor = shlex.split(EDITOR)
editor.append(filename)
@ -471,7 +481,7 @@ class VaultFile(object):
# VaultFile a context manager instead (implement __enter__ and __exit__)
def __del__(self):
self.filehandle.close()
os.unlink(self.tmplfile)
os.unlink(self.tmpfile)
def is_encrypted(self):
peak = self.filehandle.readline()

View File

@ -61,6 +61,7 @@ class Playbook:
self._basedir = os.path.normpath(os.path.join(self._basedir, os.path.dirname(file_name)))
# set the loaders basedir
cur_basedir = self._loader.get_basedir()
self._loader.set_basedir(self._basedir)
self._file_name = file_name
@ -74,6 +75,8 @@ class Playbook:
ds = self._loader.load_from_file(os.path.basename(file_name))
if not isinstance(ds, list):
# restore the basedir in case this error is caught and handled
self._loader.set_basedir(cur_basedir)
raise AnsibleParserError("playbooks must be a list of plays", obj=ds)
# Parse the playbook entries. For plays, we simply parse them
@ -81,6 +84,8 @@ class Playbook:
# PlaybookInclude() object
for entry in ds:
if not isinstance(entry, dict):
# restore the basedir in case this error is caught and handled
self._loader.set_basedir(cur_basedir)
raise AnsibleParserError("playbook entries must be either a valid play or an include statement", obj=entry)
if 'include' in entry:
@ -93,6 +98,9 @@ class Playbook:
entry_obj = Play.load(entry, variable_manager=variable_manager, loader=self._loader)
self._entries.append(entry_obj)
# we're done, so restore the old basedir in the loader
self._loader.set_basedir(cur_basedir)
def get_loader(self):
return self._loader

View File

@ -31,7 +31,7 @@ from ansible.compat.six import iteritems, string_types
from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleParserError
from ansible.errors import AnsibleParserError, AnsibleUndefinedVariable
from ansible.parsing.dataloader import DataLoader
from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.utils.boolean import boolean
@ -86,6 +86,19 @@ class Base:
# and init vars, avoid using defaults in field declaration as it lives across plays
self.vars = dict()
def dump_me(self, depth=0):
if depth == 0:
print("DUMPING OBJECT ------------------------------------------------------")
print("%s- %s (%s, id=%s)" % (" " * depth, self.__class__.__name__, self, id(self)))
if hasattr(self, '_block') and self.__class__.__name__ == 'Task' and self._block:
self._block.dump_me(depth+2)
for attr_name in ('_parent_block', '_task_include'):
if hasattr(self, attr_name):
attr = getattr(self, attr_name)
if attr is not None:
attr.dump_me(depth+2)
if hasattr(self, '_play') and self._play:
self._play.dump_me(depth+2)
# The following three functions are used to programatically define data
# descriptors (aka properties) for the Attributes of all of the playbook
@ -386,7 +399,7 @@ class Base:
except (TypeError, ValueError) as e:
raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to an %s."
" Error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds())
except UndefinedError as e:
except (AnsibleUndefinedVariable, UndefinedError) as e:
if templar._fail_on_undefined_errors and name != 'name':
raise AnsibleParserError("the field '%s' has an invalid value, which appears to include a variable that is undefined."
" The error was: %s" % (name,e), obj=self.get_ds())

View File

@ -65,8 +65,6 @@ class Block(Base, Become, Conditional, Taggable):
all_vars = self.vars.copy()
if self._role:
all_vars.update(self._role.get_vars(self._dep_chain))
if self._parent_block:
all_vars.update(self._parent_block.get_vars())
if self._task_include:
@ -271,9 +269,6 @@ class Block(Base, Become, Conditional, Taggable):
if self._parent_block is not None:
if not self._parent_block.evaluate_conditional(templar, all_vars):
return False
elif self._role is not None:
if not self._role.evaluate_conditional(templar, all_vars):
return False
return super(Block, self).evaluate_conditional(templar, all_vars)
def set_loader(self, loader):
@ -388,3 +383,24 @@ class Block(Base, Become, Conditional, Taggable):
def has_tasks(self):
return len(self.block) > 0 or len(self.rescue) > 0 or len(self.always) > 0
def get_include_params(self):
if self._parent:
return self._parent.get_include_params()
else:
return dict()
def all_parents_static(self):
'''
Determine if all of the parents of this block were statically loaded
or not. Since Task/TaskInclude objects may be in the chain, they simply
call their parents all_parents_static() method. Only Block objects in
the chain check the statically_loaded value of the parent.
'''
from ansible.playbook.task_include import TaskInclude
if self._task_include and not self._task_include.statically_loaded:
return False
elif self._parent_block:
return self._parent_block.all_parents_static()
return True

View File

@ -0,0 +1,33 @@
# (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
from ansible.errors import AnsibleError
#from ansible.inventory.host import Host
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.handler import Handler
class HandlerTaskInclude(Handler, TaskInclude):
@staticmethod
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
t = HandlerTaskInclude(block=block, role=role, task_include=task_include)
return t.load_data(data, variable_manager=variable_manager, loader=loader)

View File

@ -38,7 +38,7 @@ def load_list_of_blocks(ds, play, parent_block=None, role=None, task_include=Non
return a list of Block() objects, where implicit blocks
are created for each bare Task.
'''
# we import here to prevent a circular dependency with imports
from ansible.playbook.block import Block
@ -81,6 +81,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
from ansible.playbook.handler import Handler
from ansible.playbook.task import Task
from ansible.playbook.task_include import TaskInclude
from ansible.playbook.handler_task_include import HandlerTaskInclude
from ansible.template import Templar
assert isinstance(ds, list)
@ -95,7 +96,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
play=play,
parent_block=block,
role=role,
task_include=task_include,
task_include=None,
use_handlers=use_handlers,
variable_manager=variable_manager,
loader=loader,
@ -103,50 +104,56 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
task_list.append(t)
else:
if 'include' in task_ds:
t = TaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
if use_handlers:
t = HandlerTaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
else:
t = TaskInclude.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
all_vars = variable_manager.get_vars(loader=loader, play=play, task=t)
templar = Templar(loader=loader, variables=all_vars)
# check to see if this include is static, which can be true if:
# 1. the user set the 'static' option to true
# check to see if this include is dynamic or static:
# 1. the user has set the 'static' option to false or true
# 2. one of the appropriate config options was set
# 3. the included file name contains no variables, and has no loop
is_static = t.static or \
C.DEFAULT_TASK_INCLUDES_STATIC or \
(use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \
not templar._contains_vars(t.args.get('_raw_params')) and t.loop is None
if t.static is not None:
is_static = t.static
else:
is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \
(use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \
(not templar._contains_vars(t.args['_raw_params']) and t.all_parents_static() and not t.loop)
if is_static:
if t.loop is not None:
raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds)
# FIXME: all of this code is very similar (if not identical) to that in
# plugins/strategy/__init__.py, and should be unified to avoid
# patches only being applied to one or the other location
if task_include:
# handle relative includes by walking up the list of parent include
# tasks and checking the relative result to see if it exists
parent_include = task_include
cumulative_path = None
while parent_include is not None:
parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params')))
if cumulative_path is None:
cumulative_path = parent_include_dir
elif not os.path.isabs(cumulative_path):
cumulative_path = os.path.join(parent_include_dir, cumulative_path)
include_target = templar.template(t.args['_raw_params'])
if t._role:
new_basedir = os.path.join(t._role._role_path, 'tasks', cumulative_path)
include_file = loader.path_dwim_relative(new_basedir, 'tasks', include_target)
else:
include_file = loader.path_dwim_relative(loader.get_basedir(), cumulative_path, include_target)
# we set a flag to indicate this include was static
t.statically_loaded = True
if os.path.exists(include_file):
break
else:
parent_include = parent_include._task_include
else:
# handle relative includes by walking up the list of parent include
# tasks and checking the relative result to see if it exists
parent_include = task_include
cumulative_path = None
found = False
while parent_include is not None:
parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params')))
if cumulative_path is None:
cumulative_path = parent_include_dir
elif not os.path.isabs(cumulative_path):
cumulative_path = os.path.join(parent_include_dir, cumulative_path)
include_target = templar.template(t.args['_raw_params'])
if t._role:
new_basedir = os.path.join(t._role._role_path, 'tasks', cumulative_path)
include_file = loader.path_dwim_relative(new_basedir, 'tasks', include_target)
else:
include_file = loader.path_dwim_relative(loader.get_basedir(), cumulative_path, include_target)
if os.path.exists(include_file):
found = True
break
else:
parent_include = parent_include._task_include
if not found:
try:
include_target = templar.template(t.args['_raw_params'])
except AnsibleUndefinedVariable as e:
@ -172,6 +179,12 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
return []
elif not isinstance(data, list):
raise AnsibleError("included task files must contain a list of tasks", obj=data)
# since we can't send callbacks here, we display a message directly in
# the same fashion used by the on_include callback. We also do it here,
# because the recursive nature of helper methods means we may be loading
# nested includes, and we want the include order printed correctly
display.display("statically included: %s" % include_file, color=C.COLOR_SKIP)
except AnsibleFileNotFound as e:
if t.static or \
C.DEFAULT_TASK_INCLUDES_STATIC or \
@ -223,7 +236,6 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
b.tags = list(set(b.tags).union(tags))
# END FIXME
# FIXME: send callback here somehow...
# FIXME: handlers shouldn't need this special handling, but do
# right now because they don't iterate blocks correctly
if use_handlers:
@ -233,11 +245,11 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
task_list.extend(included_blocks)
else:
task_list.append(t)
elif use_handlers:
t = Handler.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
task_list.append(t)
else:
t = Task.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
if use_handlers:
t = Handler.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
else:
t = Task.load(task_ds, block=block, role=role, task_include=task_include, variable_manager=variable_manager, loader=loader)
task_list.append(t)
return task_list

View File

@ -136,28 +136,6 @@ class Play(Base, Taggable, Become):
return super(Play, self).preprocess_data(ds)
def _load_hosts(self, attr, ds):
'''
Loads the hosts from the given datastructure, which might be a list
or a simple string. We also switch integers in this list back to strings,
as the YAML parser will turn things that look like numbers into numbers.
'''
if isinstance(ds, (string_types, int)):
ds = [ ds ]
if not isinstance(ds, list):
raise AnsibleParserError("'hosts' must be specified as a list or a single pattern", obj=ds)
# YAML parsing of things that look like numbers may have
# resulted in integers showing up in the list, so convert
# them back to strings to prevent problems
for idx,item in enumerate(ds):
if isinstance(item, int):
ds[idx] = "%s" % item
return ds
def _load_tasks(self, attr, ds):
'''
Loads a list of blocks from a list which may be mixed tasks/blocks.
@ -265,7 +243,7 @@ class Play(Base, Taggable, Become):
if len(self.roles) > 0:
for r in self.roles:
block_list.extend(r.get_handler_blocks())
block_list.extend(r.get_handler_blocks(play=self))
return block_list

View File

@ -21,7 +21,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import pipes
import pwd
import random
import re
import string
@ -29,11 +31,9 @@ import string
from ansible.compat.six import iteritems, string_types
from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.playbook.attribute import Attribute, FieldAttribute
from ansible.playbook.attribute import FieldAttribute
from ansible.playbook.base import Base
from ansible.template import Templar
from ansible.utils.boolean import boolean
from ansible.utils.unicode import to_unicode
__all__ = ['PlayContext']
@ -356,7 +356,7 @@ class PlayContext(Base):
# and likewise for the remote user
for user_var in MAGIC_VARIABLE_MAPPING.get('remote_user'):
if user_var in delegated_vars:
if user_var in delegated_vars and delegated_vars[user_var]:
break
else:
delegated_vars['ansible_user'] = task.remote_user or self.remote_user
@ -411,6 +411,12 @@ class PlayContext(Base):
if new_info.port is None and C.DEFAULT_REMOTE_PORT is not None:
new_info.port = int(C.DEFAULT_REMOTE_PORT)
# if the final connection type is local, reset the remote_user value
# to that of the currently logged in user, to ensure any become settings
# are obeyed correctly
if new_info.connection == 'local':
new_info.remote_user = pwd.getpwuid(os.getuid()).pw_name
# special overrides for the connection setting
if len(delegated_vars) > 0:
# in the event that we were using local before make sure to reset the
@ -447,16 +453,21 @@ class PlayContext(Base):
success_key = None
self.prompt = None
if executable is None:
executable = self.executable
if self.become:
if not executable:
executable = self.executable
becomecmd = None
randbits = ''.join(random.choice(string.ascii_lowercase) for x in range(32))
success_key = 'BECOME-SUCCESS-%s' % randbits
success_cmd = pipes.quote('echo %s; %s' % (success_key, cmd))
if executable:
command = '%s -c %s' % (executable, success_cmd)
else:
command = success_cmd
# set executable to use for the privilege escalation method, with various overrides
exe = self.become_exe or \
getattr(self, '%s_exe' % self.become_method, None) or \
@ -485,9 +496,9 @@ class PlayContext(Base):
# force quick error if password is required but not supplied, should prevent sudo hangs.
if self.become_pass:
prompt = '[sudo via ansible, key=%s] password: ' % randbits
becomecmd = '%s %s -p "%s" -u %s %s -c %s' % (exe, flags.replace('-n',''), prompt, self.become_user, executable, success_cmd)
becomecmd = '%s %s -p "%s" -u %s %s' % (exe, flags.replace('-n',''), prompt, self.become_user, command)
else:
becomecmd = '%s %s -u %s %s -c %s' % (exe, flags, self.become_user, executable, success_cmd)
becomecmd = '%s %s -u %s %s' % (exe, flags, self.become_user, command)
elif self.become_method == 'su':
@ -498,7 +509,7 @@ class PlayContext(Base):
return bool(SU_PROMPT_LOCALIZATIONS_RE.match(data))
prompt = detect_su_prompt
becomecmd = '%s %s %s -c %s' % (exe, flags, self.become_user, pipes.quote('%s -c %s' % (executable, success_cmd)))
becomecmd = '%s %s %s -c %s' % (exe, flags, self.become_user, pipes.quote(command))
elif self.become_method == 'pbrun':
@ -534,7 +545,7 @@ class PlayContext(Base):
exe = self.become_exe or 'dzdo'
becomecmd = '%s -u %s %s -c %s' % (exe, self.become_user, executable, success_cmd)
becomecmd = '%s -u %s %s' % (exe, self.become_user, command)
else:
raise AnsibleError("Privilege escalation method not found: %s" % self.become_method)

View File

@ -96,7 +96,7 @@ class PlaybookInclude(Base, Conditional, Taggable):
# plays. If so, we can take a shortcut here and simply prepend them to
# those attached to each block (if any)
if forward_conditional:
for task_block in entry.tasks:
for task_block in entry.pre_tasks + entry.roles + entry.tasks + entry.post_tasks:
task_block.when = self.when[:] + task_block.when
return pb

View File

@ -118,16 +118,6 @@ class Role(Base, Become, Conditional, Taggable):
if role_include.role not in play.ROLE_CACHE:
play.ROLE_CACHE[role_include.role] = dict()
if parent_role:
if parent_role.when:
new_when = parent_role.when[:]
new_when.extend(r.when or [])
r.when = new_when
if parent_role.tags:
new_tags = parent_role.tags[:]
new_tags.extend(r.tags or [])
r.tags = new_tags
play.ROLE_CACHE[role_include.role][hashed_params] = r
return r
@ -311,12 +301,24 @@ class Role(Base, Become, Conditional, Taggable):
def get_task_blocks(self):
return self._task_blocks[:]
def get_handler_blocks(self):
def get_handler_blocks(self, play, dep_chain=None):
block_list = []
# update the dependency chain here
if dep_chain is None:
dep_chain = []
new_dep_chain = dep_chain + [self]
for dep in self.get_direct_dependencies():
dep_blocks = dep.get_handler_blocks()
dep_blocks = dep.get_handler_blocks(play=play, dep_chain=new_dep_chain)
block_list.extend(dep_blocks)
block_list.extend(self._handler_blocks)
for task_block in self._handler_blocks:
new_task_block = task_block.copy()
new_task_block._dep_chain = new_dep_chain
new_task_block._play = play
block_list.append(new_task_block)
return block_list
def has_run(self, host):

View File

@ -84,7 +84,7 @@ class Task(Base, Conditional, Taggable, Become):
_notify = FieldAttribute(isa='list')
_poll = FieldAttribute(isa='int')
_register = FieldAttribute(isa='string')
_retries = FieldAttribute(isa='int', default=3)
_retries = FieldAttribute(isa='int')
_until = FieldAttribute(isa='list', default=[])
def __init__(self, block=None, role=None, task_include=None):
@ -105,7 +105,7 @@ class Task(Base, Conditional, Taggable, Become):
def get_name(self):
''' return the name of the task '''
if self._role and self.name:
if self._role and self.name and ("%s : " % self._role._role_name) not in self.name:
return "%s : %s" % (self._role.get_name(), self.name)
elif self.name:
return self.name
@ -196,7 +196,7 @@ class Task(Base, Conditional, Taggable, Become):
if 'vars' in ds:
# _load_vars is defined in Base, and is used to load a dictionary
# or list of dictionaries in a standard way
new_ds['vars'] = self._load_vars(None, ds.pop('vars'))
new_ds['vars'] = self._load_vars(None, ds.get('vars'))
else:
new_ds['vars'] = dict()
@ -245,13 +245,6 @@ class Task(Base, Conditional, Taggable, Become):
super(Task, self).post_validate(templar)
def _post_validate_register(self, attr, value, templar):
'''
Override post validation for the register args field, which is not
supposed to be templated
'''
return value
def _post_validate_loop_args(self, attr, value, templar):
'''
Override post validation for the loop args field, which is templated
@ -454,3 +447,42 @@ class Task(Base, Conditional, Taggable, Become):
'''
return self._get_parent_attribute('any_errors_fatal')
def _get_attr_loop(self):
return self._attributes['loop']
def _get_attr_loop_control(self):
return self._attributes['loop_control']
def get_dep_chain(self):
if self._parent:
return self._parent.get_dep_chain()
else:
return None
def get_search_path(self):
'''
Return the list of paths you should search for files, in order.
This follows role/playbook dependency chain.
'''
path_stack = []
dep_chain = self.get_dep_chain()
# inside role: add the dependency chain from current to dependant
if dep_chain:
path_stack.extend(reversed([x._role_path for x in dep_chain]))
# add path of task itself, unless it is already in the list
task_dir = os.path.dirname(self.get_path())
if task_dir not in path_stack:
path_stack.append(task_dir)
return path_stack
def all_parents_static(self):
if self._task_include and not self._task_include.statically_loaded:
return False
elif self._block:
return self._block.all_parents_static()
return True

View File

@ -41,13 +41,22 @@ class TaskInclude(Task):
# =================================================================================
# ATTRIBUTES
_static = FieldAttribute(isa='bool', default=False)
_static = FieldAttribute(isa='bool', default=None)
def __init__(self, block=None, role=None, task_include=None):
super(TaskInclude, self).__init__(block=block, role=role, task_include=task_include)
self.statically_loaded = False
@staticmethod
def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None):
t = TaskInclude(block=block, role=role, task_include=task_include)
return t.load_data(data, variable_manager=variable_manager, loader=loader)
def copy(self, exclude_block=False):
new_me = super(TaskInclude, self).copy(exclude_block=exclude_block)
new_me.statically_loaded = self.statically_loaded
return new_me
def get_vars(self):
'''
We override the parent Task() classes get_vars here because

View File

@ -145,15 +145,15 @@ class PluginLoader:
def _get_package_paths(self):
''' Gets the path of a Python package '''
paths = []
if not self.package:
return []
if not hasattr(self, 'package_path'):
m = __import__(self.package)
parts = self.package.split('.')[1:]
self.package_path = os.path.join(os.path.dirname(m.__file__), *parts)
paths.extend(self._all_directories(self.package_path))
return paths
for parent_mod in parts:
m = getattr(m, parent_mod)
self.package_path = os.path.dirname(m.__file__)
return self._all_directories(self.package_path)
def _get_paths(self):
''' Return a list of paths to search for plugins in '''

View File

@ -35,6 +35,7 @@ from ansible.compat.six import binary_type, text_type, iteritems, with_metaclass
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleConnectionFailure
from ansible.executor.module_common import modify_module
from ansible.release import __version__
from ansible.parsing.utils.jsonify import jsonify
from ansible.utils.unicode import to_bytes, to_unicode
@ -240,7 +241,8 @@ class ActionBase(with_metaclass(ABCMeta, object)):
raise AnsibleConnectionFailure(output)
try:
rc = self._connection._shell.join_path(result['stdout'].strip(), u'').splitlines()[-1]
stdout_parts = result['stdout'].strip().split('%s=' % basefile, 1)
rc = self._connection._shell.join_path(stdout_parts[-1], u'').splitlines()[-1]
except IndexError:
# stdout was empty or just space, set to / to trigger error in next if
rc = '/'
@ -291,7 +293,29 @@ class ActionBase(with_metaclass(ABCMeta, object)):
return remote_path
def _fixup_perms(self, remote_path, remote_user, execute=False, recursive=True):
def _fixup_perms(self, remote_path, remote_user, execute=True, recursive=True):
"""
We need the files we upload to be readable (and sometimes executable)
by the user being sudo'd to but we want to limit other people's access
(because the files could contain passwords or other private
information.
Deprecated in favor of _fixup_perms2. Ansible code has been updated to
use _fixup_perms2. This code is maintained to provide partial support
for custom actions (non-recursive mode only).
"""
display.deprecated('_fixup_perms is deprecated. Use _fixup_perms2 instead.', version='2.4', removed=False)
if recursive:
raise AnsibleError('_fixup_perms with recursive=True (the default) is no longer supported. ' +
'Use _fixup_perms2 if support for previous releases is not required. '
'Otherwise use fixup_perms with recursive=False.')
return self._fixup_perms2([remote_path], remote_user, execute)
def _fixup_perms2(self, remote_paths, remote_user, execute=True):
"""
We need the files we upload to be readable (and sometimes executable)
by the user being sudo'd to but we want to limit other people's access
@ -299,17 +323,17 @@ class ActionBase(with_metaclass(ABCMeta, object)):
information. We achieve this in one of these ways:
* If no sudo is performed or the remote_user is sudo'ing to
themselves, we don't have to change permisions.
themselves, we don't have to change permissions.
* If the remote_user sudo's to a privileged user (for instance, root),
we don't have to change permissions
* If the remote_user is a privileged user and sudo's to an
unprivileged user then we change the owner of the file to the
unprivileged user so they can read it.
* If the remote_user is an unprivieged user and we're sudo'ing to
a second unprivileged user then we attempt to grant the second
unprivileged user access via file system acls.
* If granting file system acls fails we can set the file to be world
readable so that the second unprivileged user can read the file.
* If the remote_user sudo's to an unprivileged user then we attempt to
grant the unprivileged user access via file system acls.
* If granting file system acls fails we try to change the owner of the
file with chown which only works in case the remote_user is
privileged or the remote systems allows chown calls by unprivileged
users (e.g. HP-UX)
* If the chown fails we can set the file to be world readable so that
the second unprivileged user can read the file.
Since this could allow other users to get access to private
information we only do this ansible is configured with
"allow_world_readable_tmpfiles" in the ansible.cfg
@ -317,51 +341,39 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if self._connection._shell.SHELL_FAMILY == 'powershell':
# This won't work on Powershell as-is, so we'll just completely skip until
# we have a need for it, at which point we'll have to do something different.
return remote_path
if remote_path is None:
# Sometimes code calls us naively -- it has a var which could
# contain a path to a tmp dir but doesn't know if it needs to
# exist or not. If there's no path, then there's no need for us
# to do work
self._display.debug('_fixup_perms called with remote_path==None. Sure this is correct?')
return remote_path
return remote_paths
if self._play_context.become and self._play_context.become_user not in ('root', remote_user):
# Unprivileged user that's different than the ssh user. Let's get
# to work!
# Try chown'ing the file. This will only work if our SSH user has
# root privileges, but since we can't reliably determine that from
# the username (think "toor" on FreeBSD), let's just try first and
# apologize later:
res = self._remote_chown(remote_path, self._play_context.become_user, recursive=recursive)
if res['rc'] == 0:
# root can read things that don't have read bit but can't
# execute them without the execute bit, so we might need to
# set that even if we're root. We just ran chown successfully,
# so apparently we are root.
# Try to use file system acls to make the files readable for sudo'd
# user
if execute:
mode = 'rx'
else:
mode = 'rX'
res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, mode)
if res['rc'] != 0:
# File system acls failed; let's try to use chown next
# Set executable bit first as on some systems an
# unprivileged user can use chown
if execute:
res = self._remote_chmod('u+x', remote_path, recursive=recursive)
res = self._remote_chmod(remote_paths, 'u+x')
if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
elif remote_user == 'root':
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.')
else:
# Chown'ing failed. We're probably lacking root privileges; let's try something else.
if execute:
mode = 'rx'
else:
mode = 'rX'
# Try to use fs acls to solve this problem
res = self._remote_set_user_facl(remote_path, self._play_context.become_user, mode, recursive=recursive, sudoable=False)
if res['rc'] != 0:
res = self._remote_chown(remote_paths, self._play_context.become_user)
if res['rc'] != 0 and remote_user == 'root':
# chown failed even if remove_user is root
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.')
elif res['rc'] != 0:
if C.ALLOW_WORLD_READABLE_TMPFILES:
# fs acls failed -- do things this insecure way only
# if the user opted in in the config file
self._display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user which may be insecure. For information on securing this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user')
res = self._remote_chmod('a+%s' % mode, remote_path, recursive=recursive)
# chown and fs acls failed -- do things this insecure
# way only if the user opted in in the config file
display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user which may be insecure. For information on securing this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user')
res = self._remote_chmod(remote_paths, 'a+%s' % mode)
if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
else:
@ -370,33 +382,33 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# Can't depend on the file being transferred with execute
# permissions. Only need user perms because no become was
# used here
res = self._remote_chmod('u+x', remote_path, recursive=recursive)
res = self._remote_chmod(remote_paths, 'u+x')
if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
return remote_path
return remote_paths
def _remote_chmod(self, mode, path, recursive=True, sudoable=False):
def _remote_chmod(self, paths, mode, sudoable=False):
'''
Issue a remote chmod command
'''
cmd = self._connection._shell.chmod(mode, path, recursive=recursive)
cmd = self._connection._shell.chmod(paths, mode)
res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res
def _remote_chown(self, path, user, group=None, recursive=True, sudoable=False):
def _remote_chown(self, paths, user, sudoable=False):
'''
Issue a remote chown command
'''
cmd = self._connection._shell.chown(path, user, group, recursive=recursive)
cmd = self._connection._shell.chown(paths, user)
res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res
def _remote_set_user_facl(self, path, user, mode, recursive=True, sudoable=False):
def _remote_set_user_facl(self, paths, user, mode, sudoable=False):
'''
Issue a remote call to setfacl
'''
cmd = self._connection._shell.set_user_facl(path, user, mode, recursive=recursive)
cmd = self._connection._shell.set_user_facl(paths, user, mode)
res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res
@ -426,7 +438,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
return mystat['stat']
def _remote_checksum(self, path, all_vars):
def _remote_checksum(self, path, all_vars, follow=False):
'''
Produces a remote checksum given a path,
Returns a number 0-4 for specific errors instead of checksum, also ensures it is different
@ -438,7 +450,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
'''
x = "0" # unknown error has occured
try:
remote_stat = self._execute_remote_stat(path, all_vars, follow=False)
remote_stat = self._execute_remote_stat(path, all_vars, follow=follow)
if remote_stat['exists'] and remote_stat['isdir']:
x = "3" # its a directory not a file
else:
@ -480,21 +492,49 @@ class ActionBase(with_metaclass(ABCMeta, object)):
else:
return initial_fragment
def _filter_leading_non_json_lines(self, data):
@staticmethod
def _filter_non_json_lines(data):
'''
Used to avoid random output from SSH at the top of JSON output, like messages from
tcagetattr, or where dropbear spews MOTD on every single command (which is nuts).
need to filter anything which starts not with '{', '[', ', '=' or is an empty line.
filter only leading lines since multiline JSON is valid.
need to filter anything which does not start with '{', '[', or is an empty line.
Have to be careful how we filter trailing junk as multiline JSON is valid.
'''
idx = 0
for line in data.splitlines(True):
if line.startswith((u'{', u'[')):
# Filter initial junk
lines = data.splitlines()
for start, line in enumerate(lines):
line = line.strip()
if line.startswith(u'{'):
endchar = u'}'
break
idx = idx + len(line)
elif line.startswith(u'['):
endchar = u']'
break
else:
display.debug('No start of json char found')
raise ValueError('No start of json char found')
return data[idx:]
# Filter trailing junk
lines = lines[start:]
lines.reverse()
for end, line in enumerate(lines):
if line.strip().endswith(endchar):
break
else:
display.debug('No end of json char found')
raise ValueError('No end of json char found')
if end < len(lines) - 1:
# Trailing junk is uncommon and can point to things the user might
# want to change. So print a warning if we find any
trailing_junk = lines[:end]
trailing_junk.reverse()
display.warning('Module invocation had junk after the JSON data: %s' % '\n'.join(trailing_junk))
lines = lines[end:]
lines.reverse()
return '\n'.join(lines)
def _strip_success_message(self, data):
'''
@ -539,7 +579,16 @@ class ActionBase(with_metaclass(ABCMeta, object)):
module_args['_ansible_diff'] = self._play_context.diff
# let module know our verbosity
module_args['_ansible_verbosity'] = self._display.verbosity
module_args['_ansible_verbosity'] = display.verbosity
# give the module information about the ansible version
module_args['_ansible_version'] = __version__
# set the syslog facility to be used in the module
module_args['_ansible_syslog_facility'] = task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY)
# let module know about filesystems that selinux treats specially
module_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS
(module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars)
if not shebang:
@ -566,7 +615,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# the remote system, which can be read and parsed by the module
args_data = ""
for k,v in iteritems(module_args):
args_data += '%s="%s" ' % (k, pipes.quote(text_type(v)))
args_data += '%s=%s ' % (k, pipes.quote(text_type(v)))
self._transfer_data(args_file_path, args_data)
elif module_style == 'non_native_want_json':
self._transfer_data(args_file_path, json.dumps(module_args))
@ -574,9 +623,17 @@ class ActionBase(with_metaclass(ABCMeta, object)):
environment_string = self._compute_environment_string()
remote_files = None
if args_file_path:
remote_files = tmp, remote_module_path, args_file_path
elif remote_module_path:
remote_files = tmp, remote_module_path
# Fix permissions of the tmp path and tmp files. This should be
# called after all files have been transferred.
self._fixup_perms(tmp, remote_user, recursive=True)
if remote_files:
self._fixup_perms2(remote_files, remote_user)
cmd = ""
in_data = None
@ -627,10 +684,11 @@ class ActionBase(with_metaclass(ABCMeta, object)):
def _parse_returned_data(self, res):
try:
data = json.loads(self._filter_leading_non_json_lines(res.get('stdout', u'')))
data = json.loads(self._filter_non_json_lines(res.get('stdout', u'')))
data['_ansible_parsed'] = True
except ValueError:
# not valid json, lets try to capture error
data = dict(failed=True, parsed=False)
data = dict(failed=True, _ansible_parsed=False)
data['msg'] = "MODULE FAILURE"
data['module_stdout'] = res.get('stdout', u'')
if 'stderr' in res:
@ -669,11 +727,21 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if self._connection.allow_executable:
if executable is None:
executable = self._play_context.executable
# mitigation for SSH race which can drop stdout (https://github.com/ansible/ansible/issues/13876)
# only applied for the default executable to avoid interfering with the raw action
cmd = self._connection._shell.append_command(cmd, 'sleep 0')
if executable:
cmd = executable + ' -c ' + pipes.quote(cmd)
display.debug("_low_level_execute_command(): executing: %s" % (cmd,))
rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable)
# Change directory to basedir of task for command execution
cwd = os.getcwd()
os.chdir(self._loader.get_basedir())
try:
rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable)
finally:
os.chdir(cwd)
# stdout and stderr may be either a file-like or a bytes object.
# Convert either one to a text type

View File

@ -153,7 +153,7 @@ class ActionModule(ActionBase):
xfered = self._transfer_file(path, remote_path)
# fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True)
self._fixup_perms2((tmp, remote_path), remote_user)
new_module_args.update( dict( src=xfered,))

View File

@ -70,17 +70,13 @@ class ActionModule(ActionBase):
args_data += '%s="%s" ' % (k, pipes.quote(to_unicode(v)))
argsfile = self._transfer_data(self._connection._shell.join_path(tmp, 'arguments'), args_data)
self._fixup_perms(tmp, remote_user, execute=True, recursive=True)
# Only the following two files need to be executable but we'd have to
# make three remote calls if we wanted to just set them executable.
# There's not really a problem with marking too many of the temp files
# executable so we go ahead and mark them all as executable in the
# line above (the line above is needed in any case [although
# execute=False is okay if we uncomment the lines below] so that all
# the files are readable in case the remote_user and become_user are
# different and both unprivileged)
#self._fixup_perms(remote_module_path, remote_user, execute=True, recursive=False)
#self._fixup_perms(async_module_path, remote_user, execute=True, recursive=False)
remote_paths = tmp, remote_module_path, async_module_path
# argsfile doesn't need to be executable, but this saves an extra call to the remote host
if argsfile:
remote_paths += argsfile,
self._fixup_perms2(remote_paths, remote_user, execute=True)
async_limit = self._task.async
async_jid = str(random.randint(0, 999999999999))
@ -96,4 +92,11 @@ class ActionModule(ActionBase):
result['changed'] = True
if 'skipped' in result and result['skipped'] or 'failed' in result and result['failed']:
return result
# the async_wrapper module returns dumped JSON via its stdout
# response, so we parse it here and replace the result
result = self._parse_returned_data(result)
return result

View File

@ -217,8 +217,10 @@ class ActionModule(ActionBase):
# Define a remote directory that we will copy the file to.
tmp_src = self._connection._shell.join_path(tmp, 'source')
remote_path = None
if not raw:
self._transfer_file(source_full, tmp_src)
remote_path = self._transfer_file(source_full, tmp_src)
else:
self._transfer_file(source_full, dest_file)
@ -227,7 +229,8 @@ class ActionModule(ActionBase):
self._loader.cleanup_tmp_file(source_full)
# fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True)
if remote_path:
self._fixup_perms2((tmp, remote_path), remote_user)
if raw:
# Continue to next iteration if raw is defined.
@ -245,6 +248,8 @@ class ActionModule(ActionBase):
original_basename=source_rel,
)
)
if 'content' in new_module_args:
del new_module_args['content']
module_return = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, tmp=tmp, delete_remote_tmp=delete_remote_tmp)
module_executed = True

View File

@ -54,7 +54,11 @@ class ActionModule(ActionBase):
try:
results = self._templar.template(self._task.args['var'], convert_bare=True, fail_on_undefined=True, bare_deprecated=False)
if results == self._task.args['var']:
raise AnsibleUndefinedVariable
# if results is not str/unicode type, raise an exception
if type(results) not in [str, unicode]:
raise AnsibleUndefinedVariable
# If var name is same as result, try to template it
results = self._templar.template("{{" + results + "}}", convert_bare=True, fail_on_undefined=True)
except AnsibleUndefinedVariable:
results = "VARIABLE IS NOT DEFINED!"

View File

@ -64,7 +64,8 @@ class ActionModule(ActionBase):
remote_checksum = None
if not self._play_context.become:
# calculate checksum for the remote file, don't bother if using become as slurp will be used
remote_checksum = self._remote_checksum(source, all_vars=task_vars)
# Force remote_checksum to follow symlinks because fetch always follows symlinks
remote_checksum = self._remote_checksum(source, all_vars=task_vars, follow=True)
# use slurp if permissions are lacking or privilege escalation is needed
remote_data = None

View File

@ -34,6 +34,8 @@ class ActionModule(ActionBase):
result = super(ActionModule, self).run(tmp, task_vars)
source = self._task.args.get('_raw_params')
if source is None:
raise AnsibleError("No filename was specified to include.", self._task._ds)
if self._task._role:
source = self._loader.path_dwim_relative(self._task._role._role_path, 'vars', source)

View File

@ -75,6 +75,9 @@ class ActionModule(ActionBase):
def _handle_template(self):
src = self._task.args.get('src')
if not src:
return
working_path = self._get_working_path()
if os.path.isabs(src) or urlparse.urlsplit('src').scheme:
@ -93,6 +96,17 @@ class ActionModule(ActionBase):
except IOError:
return dict(failed=True, msg='unable to load src file')
# Create a template search path in the following order:
# [working_path, self_role_path, dependent_role_paths, dirname(source)]
searchpath = [working_path]
if self._task._role is not None:
searchpath.append(self._task._role._role_path)
dep_chain = self._task._block.get_dep_chain()
if dep_chain is not None:
for role in dep_chain:
searchpath.append(role._role_path)
searchpath.append(os.path.dirname(source))
self._templar.environment.loader.searchpath = searchpath
self._task.args['src'] = self._templar.template(template_data)

View File

@ -59,7 +59,7 @@ class ActionModule(ActionBase):
tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src))
self._transfer_file(src, tmp_src)
self._fixup_perms(tmp, remote_user, recursive=True)
self._fixup_perms2((tmp, tmp_src), remote_user)
new_module_args = self._task.args.copy()
new_module_args.update(

View File

@ -37,7 +37,7 @@ class ActionModule(ActionBase):
result['skipped'] = True
return result
executable = self._task.args.get('executable')
executable = self._task.args.get('executable', False)
result.update(self._low_level_execute_command(self._task.args.get('_raw_params'), executable=executable))
return result

View File

@ -19,7 +19,6 @@ __metaclass__ = type
import os
from ansible import constants as C
from ansible.plugins.action import ActionBase
@ -79,9 +78,8 @@ class ActionModule(ActionBase):
tmp_src = self._connection._shell.join_path(tmp, os.path.basename(source))
self._transfer_file(source, tmp_src)
sudoable = True
# set file permissions, more permissive when the copy is done as a different user
self._fixup_perms(tmp, remote_user, execute=True, recursive=True)
self._fixup_perms2((tmp, tmp_src), remote_user, execute=True)
# add preparation steps to one ssh roundtrip executing the script
env_string = self._compute_environment_string()

View File

@ -85,7 +85,15 @@ class ActionModule(ActionBase):
alternative ssh port to a vagrant host.)
"""
transport = self._connection.transport
if host not in C.LOCALHOST or transport != "local":
# If we're connecting to a remote host or we're delegating to another
# host or we're connecting to a different ssh instance on the
# localhost then we have to format the path as a remote rsync path
if host not in C.LOCALHOST or transport != "local" or \
(host in C.LOCALHOST and not port_matches_localhost_port):
# If we're delegating to non-localhost and but the
# inventory_hostname host is localhost then we need the module to
# fix up the rsync path to use the controller's public DNS/IP
# instead of "localhost"
if port_matches_localhost_port and host in C.LOCALHOST:
self._task.args['_substitute_controller'] = True
return self._format_rsync_rsh_target(host, path, user)
@ -258,12 +266,13 @@ class ActionModule(ActionBase):
# MUNGE SRC AND DEST PER REMOTE_HOST INFO
src = self._task.args.get('src', None)
dest = self._task.args.get('dest', None)
if src is None or dest is None:
return dict(failed=True,
msg="synchronize requires both src and dest parameters are set")
if not dest_is_local:
# Private key handling
if use_delegate:
private_key = task_vars.get('ansible_ssh_private_key_file') or self._play_context.private_key_file
else:
private_key = task_vars.get('ansible_ssh_private_key_file') or self._play_context.private_key_file
private_key = self._play_context.private_key_file
if private_key is not None:
private_key = os.path.expanduser(private_key)
@ -322,7 +331,12 @@ class ActionModule(ActionBase):
self._task.args['rsync_path'] = '"%s"' % rsync_path
if use_ssh_args:
self._task.args['ssh_args'] = C.ANSIBLE_SSH_ARGS
ssh_args = [
getattr(self._play_context, 'ssh_args', ''),
getattr(self._play_context, 'ssh_common_args', ''),
getattr(self._play_context, 'ssh_extra_args', ''),
]
self._task.args['ssh_args'] = ' '.join([a for a in ssh_args if a])
# run the module and store the result
result.update(self._execute_module('synchronize', task_vars=task_vars))

View File

@ -164,7 +164,7 @@ class ActionModule(ActionBase):
xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant)
# fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True)
self._fixup_perms2((tmp, xfered), remote_user)
# run the copy module
new_module_args.update(

View File

@ -71,7 +71,7 @@ class ActionModule(ActionBase):
else:
source = self._loader.path_dwim_relative(self._loader.get_basedir(), 'files', source)
remote_checksum = self._remote_checksum(dest, all_vars=task_vars)
remote_checksum = self._remote_checksum(dest, all_vars=task_vars, follow=True)
if remote_checksum == '4':
result['failed'] = True
result['msg'] = "python isn't present on the system. Unable to compute checksum"
@ -93,7 +93,7 @@ class ActionModule(ActionBase):
if copy:
# fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True)
self._fixup_perms2((tmp, tmp_src), remote_user)
# Build temporary module_args.
new_module_args = self._task.args.copy()
new_module_args.update(

View File

@ -62,13 +62,16 @@ class CacheModule(BaseCacheModule):
return None
def get(self, key):
if self.has_expired(key) or key == "":
raise KeyError
""" This checks the in memory cache first as the fact was not expired at 'gather time'
and it would be problematic if the key did expire after some long running tasks and
user gets 'undefined' error in the same play """
if key in self._cache:
return self._cache.get(key)
if self.has_expired(key) or key == "":
raise KeyError
cachefile = "%s/%s" % (self._cache_dir, key)
try:
with codecs.open(cachefile, 'r', encoding='utf-8') as f:
@ -98,7 +101,10 @@ class CacheModule(BaseCacheModule):
else:
f.write(jsonify(value))
finally:
f.close()
try:
f.close()
except UnboundLocalError:
pass
def has_expired(self, key):

View File

@ -309,7 +309,7 @@ class CallbackBase:
self.playbook_on_no_hosts_remaining()
def v2_playbook_on_task_start(self, task, is_conditional):
self.playbook_on_task_start(task, is_conditional)
self.playbook_on_task_start(task.name, is_conditional)
def v2_playbook_on_cleanup_task_start(self, task):
pass #no v1 correspondance

View File

@ -33,6 +33,10 @@ class CallbackModule(CallbackModule_default):
self.last_task = None
self.shown_title = False
def v2_playbook_on_handler_task_start(self, task):
self.super_ref.v2_playbook_on_handler_task_start(task)
self.shown_title = True
def v2_playbook_on_task_start(self, task, is_conditional):
self.last_task = task
self.shown_title = False

View File

@ -78,10 +78,6 @@ except ImportError:
from ansible.plugins.callback import CallbackBase
def to_unicode(ch):
return codecs.unicode_escape_decode(ch)[0]
def is_unicode(ch):
return isinstance(ch, unicode)
@ -106,7 +102,7 @@ class PlainTextSocketAppender(object):
self.INVALID_TOKEN = ("\n\nIt appears the LOGENTRIES_TOKEN "
"parameter you entered is incorrect!\n\n")
# Unicode Line separator character \u2028
self.LINE_SEP = to_unicode('\u2028')
self.LINE_SEP = u'\u2028'
self.verbose = verbose
self._conn = None

View File

@ -63,7 +63,7 @@ class CallbackModule(CallbackBase):
def v2_runner_on_ok(self, result):
self.result_to_tree(result)
def v2_runner_on_failed(self, result):
def v2_runner_on_failed(self, result, ignore_errors=False):
self.result_to_tree(result)
def v2_runner_on_unreachable(self, result):

View File

@ -112,7 +112,7 @@ class ConnectionBase(with_metaclass(ABCMeta, object)):
raise AnsibleError("Internal Error: this connection module does not support running commands via %s" % self._play_context.become_method)
def set_host_overrides(self, host):
def set_host_overrides(self, host, hostvars=None):
'''
An optional method, which can be used to set connection plugin parameters
from variables set on the host (or groups to which the host belongs)

View File

@ -63,7 +63,11 @@ class Connection(ConnectionBase):
raise AnsibleError("%s is not a directory" % self.chroot)
chrootsh = os.path.join(self.chroot, 'bin/sh')
if not is_executable(chrootsh):
# Want to check for a usable bourne shell inside the chroot.
# is_executable() == True is sufficient. For symlinks it
# gets really complicated really fast. So we punt on finding that
# out. As long as it's a symlink we assume that it will work
if not (is_executable(chrootsh) or (os.path.lexists(chrootsh) and os.path.islink(chrootsh))):
raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot)
self.chroot_cmd = distutils.spawn.find_executable('chroot')

View File

@ -69,7 +69,6 @@ class Connection(ConnectionBase):
executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else None
display.vvv(u"EXEC {0}".format(cmd), host=self._play_context.remote_addr)
# FIXME: cwd= needs to be set to the basedir of the playbook
display.debug("opening command with Popen()")
if isinstance(cmd, (text_type, binary_type)):

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