Compare commits

..

570 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

changing cast to binary_type instead

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

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

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

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

View File

@ -1,25 +1,4 @@
# This configuration file is used for manual execution of coverage
# as well as for tests run through ansible-test.
[run]
branch = True
# Enable concurrency. This also enables parallel mode, which results in
# multiple coverage files being created. Concurrency allows us to collect
# results from multiple tests simultaneously, as well as supporting multiple
# test runs, such as from integration tests.
concurrency = multiprocessing
parallel = True
# When running tests through ansible-test, this option is overridden by
# the COVERAGE_FILE environment variable. This option is present for
# convenience when running coverage manually from this directory.
data_file = test/results/coverage/coverage
# Don't collect or report code coverage from files matching these patterns.
[report]
omit =
*/python*/dist-packages/*
*/python*/site-packages/*
*/python*/distutils/*
*/pyshared/*
*/pytest
*/python?.?/*
*/site-packages/nose/*

3
.gitattributes vendored
View File

@ -1,4 +1 @@
CHANGELOG.md merge=union
.github/ export-ignore
hacking/ export-ignore
ticket-stubs/ export-ignore

1105
.github/BOTMETA.yml vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +0,0 @@
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->
##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
- Bug Report
- Feature Idea
- Documentation Report
##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
```
##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
##### SUMMARY
<!--- Explain the problem briefly -->
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
<!--- Paste example playbooks or commands between quotes below -->
```yaml
```
<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
<!--- Paste verbatim command output between quotes below -->
```
```

View File

@ -1,37 +0,0 @@
##### SUMMARY
<!--- Describe the change, including rationale and design decisions -->
<!---
If you are fixing an existing issue, please include "Fixes #nnn" in your
commit message and your description; but you should still explain what
the change does.
-->
##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
- Feature Pull Request
- New Module Pull Request
- Bugfix Pull Request
- Docs Pull Request
##### COMPONENT NAME
<!--- Name of the module/plugin/module/task -->
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
```
##### ADDITIONAL INFORMATION
<!---
Include additional information to help people understand the change here.
For bugs that don't have a linked bug report, a step-by-step reproduction
of the problem is helpful.
-->
<!--- Paste verbatim command output below, e.g. before and after your change -->
```
```

77
.gitignore vendored
View File

@ -2,10 +2,9 @@
*.py[co]
build
AUTHORS.TXT
# Emacs backup and autosave files...
# Emacs backup files...
*~
.\#*
\#*
# RPM stuff...
MANIFEST
dist
@ -20,82 +19,42 @@ rpm-build
# Mac OS X stuff...
.DS_Store
# manpage build stuff...
docs/man/man1/ansible*.1
docs/man/man1/ansible*.1.asciidoc.in
docs/man/man1/ansible.1
docs/man/man1/ansible-doc.1
docs/man/man1/ansible-galaxy.1
docs/man/man1/ansible-playbook.1
docs/man/man1/ansible-pull.1
docs/man/man1/ansible-vault.1
docs/man/man3/*
# Sublime stuff
*.sublime-project
*.sublime-workspace
# docsite stuff...
docs/docsite/rst/community_maintained.rst
docs/docsite/rst/config.rst
docs/docsite/rst/core_maintained.rst
docs/docsite/rst/list_of_*.rst
docs/docsite/rst/*_module.rst
docs/docsite/rst/modules_by_category.rst
docs/docsite/rst/network_maintained.rst
docs/docsite/rst/partner_maintained.rst
docs/docsite/rst/playbooks_keywords.rst
docs/docsite/rst/playbooks_directives.rst
docs/docsite/*.html
docs/docsite/rst/ansible.rst
docs/docsite/rst/ansible-*.rst
docs/docsite/_static/*.gif
docs/docsite/_static/*.png
docs/docsite/_static/websupport.js
docs/docsite/searchindex.js
docs/docsite/htmlout
docs/docsite/_build
docs/api/rst/
docs/api/_build/
docsite/rst/modules_by_category.rst
docsite/rst/list_of_*.rst
docsite/rst/*_module.rst
docsite/*.html
docsite/_static/*.gif
docsite/_static/*.png
docsite/_static/websupport.js
docsite/searchindex.js
docsite/htmlout
# deb building stuff...
/debian/
debian/
deb-build
# Vim swap files
*.swp
*.swo
credentials.yml
# test output
*.retry
*.out
.coverage
.tox
.cache
results.xml
coverage.xml
/test/units/cover-html
/test/integration/targets/*/backup/
/test/cache/*
# Development
/test/develop
venv
Vagrantfile
.vagrant
ansible.egg-info/
/shippable/
/test/integration/cloud-config-azure.yml
# Release directory
packaging/release/ansible_release
/.cache/
/test/results/coverage/*=coverage.*
/test/results/coverage/coverage*
/test/results/reports/coverage*.xml
/test/results/reports/coverage*/
/test/results/bot/*.json
/test/results/junit/*.xml
/test/results/logs/*.log
/test/results/data/*.json
/test/integration/inventory.remote
/test/integration/inventory.networking
/test/integration/inventory.winrm
/test/integration/cloud-config-aws.yml
# python 'rope' stuff
.ropeproject
# local 'ack' config files
.ackrc
# default 'coverage html' results
htmlcov/
# default 'coverage' tool data
.coverage
# ansible-test coverage results
test/units/.coverage.*
/test/integration/cloud-config-azure.yml

6
.gitmodules vendored
View File

@ -0,0 +1,6 @@
[submodule "lib/ansible/modules/core"]
path = lib/ansible/modules/core
url = https://github.com/ansible/ansible-modules-core
[submodule "lib/ansible/modules/extras"]
path = lib/ansible/modules/extras
url = https://github.com/ansible/ansible-modules-extras

View File

@ -1,36 +0,0 @@
Michael DeHaan <michael.dehaan@gmail.com> <michael@ansibleworks.com>
Michael DeHaan <michael.dehaan@gmail.com> Michael DeHaan <Michael DeHaan michael@ansibleworks.com>
Michael DeHaan <michael.dehaan@gmail.com> <michael@ansible.com>
<bcoca@ansible.com> <brian.coca+git@gmail.com>
<bcoca@ansible.com> <briancoca+ansible@gmail.com>
<bcoca@ansible.com> <briancoca+dev@gmail.com>
<bcoca@ansible.com> <bcoca@tablethotels.com>
<a.badger@gmail.com> <toshio@fedoraproject.org>
James Tanner <tanner.jc@gmail.com>
<jimi@sngx.net> <jcammarata@ansibleworks.com>
Michael Scherer <misc@zarb.org> mscherer <misc@zarb.org>
<misc@zarb.org> <misc@ephaone.org>
<misc@zarb.org> <mscherer@users.noreply.github.com>
James Martin <jmartin@ansible.com> <jmartin@basho.com>
James Laska <jlaska@ansible.com> <jlaska@redhat.com>
James Laska <jlaska@ansible.com> <jlaska@ansibleworks.com>
James Laska <jlaska@ansible.com> <jlaska@James-Laskas-MacBook-Pro.local>
James Laska <jlaska@ansible.com> <jlaska@users.noreply.github.com>
Jason McKerr <mckerrj@gmail.com> <jmckerr@jmckerr-OSX.local>
Chris Houseknecht <chouseknecht@ansible.com>
Chris Houseknecht <chouseknecht@ansible.com> chouseknecht <chouse@ansible.com>
Chris Houseknecht <chouseknecht@ansible.com> Chris Houseknecht <chouse@ansibleworks.com>
Stephen Fromm <sfromm@gmail.com> Stephen Fromm <stephenf@nero.net>
Sandra Wills <docschick@ansible.com> Sandra Wills <swills@ansible.com>
Will Thames <will@thames.id.au> willthames <will@thames.id.au>
Will Thames <will@thames.id.au> Will Thames <willdthames@gmail.com>
Will Thames <will@thames.id.au> willthames <willdthames@gmail.com>
Will Thames <will@thames.id.au> willthames <will@musc.org.uk>
Will Thames <will@thames.id.au> u348095 <will.thames@suncorp.com.au>
Will Thames <will@thames.id.au> willthames <will.thames@suncorp.com.au>
Will Thames <will@thames.id.au> Will Thames <will.thames@xvt.com.au>
nitzmahone <mdavis@ansible.com> Matt Davis <nitzmahone@users.noreply.github.com>
Dag Wieers <dag@wieers.com> Dag Wieërs <dag@wieers.com>
Dag Wieers <dag@wieers.com> Dag Wieers <dag.wieers@gmail.com>
Rene Moser <mail@renemoser.net> René Moser <mail@renemoser.net>
Rene Moser <mail@renemoser.net> Rene Moser <rene.moser@swisstxt.ch>

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
sudo: false
language: python
matrix:
include:
- env: TOXENV=py24 INTEGRATION=no
- env: TOXENV=py26 INTEGRATION=yes
python: 2.6
- env: TOXENV=py27 INTEGRATION=yes
python: 2.7
- env: TOXENV=py34 INTEGRATION=no
python: 3.4
- env: TOXENV=py35 INTEGRATION=no
python: 3.5
addons:
apt:
sources:
- deadsnakes
packages:
- python2.4
install:
- pip install tox PyYAML Jinja2 sphinx
script:
# urllib2's defaults are not secure enough for us
- ./test/code-smell/replace-urlopen.sh .
- ./test/code-smell/use-compat-six.sh lib
- ./test/code-smell/boilerplate.sh
- ./test/code-smell/required-and-default-attributes.sh
- if test x"$TOXENV" != x'py24' ; then tox ; fi
- if test x"$TOXENV" = x'py24' ; then python2.4 -V && python2.4 -m compileall -fq -x 'module_utils/(a10|rax|openstack|ec2|gce).py' lib/ansible/module_utils ; fi
#- make -C docsite all
- if test x"$INTEGRATION" = x'yes' ; then source ./hacking/env-setup && cd test/integration/ && make parsing && make test_var_precedence && make unicode ; fi
after_success:
- coveralls

View File

@ -1,19 +0,0 @@
extends: default
rules:
braces: {max-spaces-inside: 1, level: error}
brackets: {max-spaces-inside: 1, level: error}
colons: {max-spaces-after: -1, level: error}
commas: {max-spaces-after: -1, level: error}
comments: disable
comments-indentation: disable
document-start: disable
empty-lines: {max: 3, level: error}
hyphens: {level: error}
indentation: disable
key-duplicates: enable
line-length: disable
new-line-at-end-of-file: disable
new-lines: {type: unix}
trailing-spaces: disable
truthy: disable

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,15 @@ Language
* While not all components of Ansible must be in Python, core contributions to the Ansible repo must be written in Python. This is to maximize the ability of everyone to contribute.
* If you want to write non-Python ansible modules or inventory scripts, that's fine, but they are not going to get merged in most likely. Sorry!!
PEP 8 and basic style checks
PEP8 and basic style checks
===========================
* [PEP 8](https://www.python.org/dev/peps/pep-0008/) is a great Python style guide, which you should read.
* PEP 8 must not be strictly followed in all aspects, but most of it is good advice
* PEP8 is a great Python style guide, which you should read.
* PEP8 must not be strictly followed in all aspects, but most of it is good advice
* In particular, we don't really care about line lengths. Buy a bigger monitor!
* To run checks for things we care about, use [ansible-test](http://docs.ansible.com/ansible/dev_guide/testing_pep8.html#running-locally).
* To run checks for things we care about, run "make pep8"
* Similarly, additional checks can be made with "make pyflakes"
* There is no need to submit code changes for PEP 8 and pyflakes fixes, as these break attribution history. Project leadership will make these periodically.
* There is no need to submit code changes for pep8 and pyflake fixes, as these break attribution history. Project leadership will make these periodically.
* Do not submit pull requests that simply adjust whitespace in the code
Testing
@ -101,22 +101,22 @@ Use the dict constructor where possible when allocating dictionaries:
# this:
foo = dict(
a=12,
b=34,
a = 12,
b = 34
)
Do not line up variables
Line up variables
# not this
a = 12
foosball = 34
xyz = 'dog'
# this
a = 12
foosball = 34
xyz = 'dog'
# this
a = 12
foosball = 34
xyz = 'dog'
Don't use line continuations:
# no
@ -131,13 +131,13 @@ Don't use line continuations:
Spacing:
# no
x = [1,2,3]
x=[1,2,3]
# no
x = [ 1, 2, 3 ]
x = [1,2,3]
# yes
x = [1, 2, 3]
x = [ 1, 2, 3 ]
Spacing continued:
@ -277,8 +277,7 @@ To test if something is a string, consider that it may be unicode.
if type(x) == str:
# yes
from ansible.module_utils.six import string_types
if isinstance(x, string_types):
if isinstance(x, basestring):
Cleverness
==========
@ -314,3 +313,7 @@ This was not meant to be a scary document, so we hope it wasn't, but we also hop
If you have questions about this document, please ask on the ansible-devel mailing list.
Thank you!

View File

@ -1,29 +1,27 @@
# WELCOME TO ANSIBLE GITHUB
Welcome To Ansible GitHub
=========================
Hi! Nice to see you here!
If you'd like to ask a question
===============================
## QUESTIONS ?
Please see [this web page ](http://docs.ansible.com/community.html) for community information, which includes pointers on how to ask questions on the [mailing lists](http://docs.ansible.com/community.html#mailing-list-information) and IRC.
Please see the [community page](http://docs.ansible.com/community.html) for information on how to ask questions on the [mailing lists](http://docs.ansible.com/community.html#mailing-list-information) and IRC.
The github issue tracker is not the best place for questions for various reasons, but both IRC and the mailing list are very helpful places for those things, and that page has the pointers to those.
The GitHub issue tracker is not the best place for questions for various reasons, but both IRC and the mailing list are very helpful places for those things, as the community page explains best.
If you'd like to contribute code
================================
Please see [this web page](http://docs.ansible.com/community.html) for information about the contribution process. Important license agreement information is also included on that page.
## CONTRIBUTING ?
If you'd like to file a bug
===========================
Please see the [community page](http://docs.ansible.com/community.html) for information regarding the contribution process. Important license agreement information is also included on that page.
I'd also read the community page above, but in particular, make sure you copy [this issue template](https://github.com/ansible/ansible/blob/devel/ISSUE_TEMPLATE.md) into your ticket description. We have a friendly neighborhood bot that will remind you if you forget :) This template helps us organize tickets faster and prevents asking some repeated questions, so it's very helpful to us and we appreciate your help with it.
## BUG TO REPORT ?
First and foremost, also check the [community page](http://docs.ansible.com/community.html).
You can report bugs or make enhancement requests at the [Ansible GitHub issue page](http://github.com/ansible/ansible/issues/new) by filling out the issue template that will be presented.
Also please make sure you are testing on the latest released version of Ansible or the development branch. You can find the latest releases and development branch at:
- https://github.com/ansible/ansible/releases
- https://github.com/ansible/ansible/archive/devel.tar.gz
Also please make sure you are testing on the latest released version of Ansible or the development branch.
Thanks!

39
ISSUE_TEMPLATE.md Normal file
View File

@ -0,0 +1,39 @@
##### Issue Type:
Can you help us out in labelling this by telling us what kind of ticket this this? You can say:
- Bug Report
- Feature Idea
- Feature Pull Request
- New Module Pull Request
- Bugfix Pull Request
- Documentation Report
- Docs Pull Request
##### Ansible Version:
Let us know what version of Ansible you are using. Please supply the verbatim output from running “ansible --version”. If you're filing a ticket on a version of Ansible which is not the latest, we'd greatly appreciate it if you could retest on the latest version first. We don't expect you to test against the development branch most of the time, but we may ask for that if you have cycles. Thanks!
##### Ansible Configuration:
What have you changed about your Ansible installation? What configuration settings have you changed/added/removed? Compare your /etc/ansible/ansible.cfg against a clean version from Github and let us know what's different.
##### Environment:
What OS are you running Ansible from and what OS are you managing? Examples include RHEL 5/6, Centos 5/6, Ubuntu 12.04/13.10, *BSD, Solaris. If this is a generic feature request or it doesn’t apply, just say “N/A”. Not all tickets may be about operating system related things and we understand that.
##### Summary:
Please summarize your request in this space. You will earn bonus points for being succinct, but please add enough detail so we can understand the request. Thanks!
##### Steps To Reproduce:
If this is a bug ticket, please enter the steps you use to reproduce the problem in the space below. If this is a feature request, please enter the steps you would use to use the feature. If an example playbook is useful, please include a short reproducer inline, indented by four spaces. If a longer one is necessary, linking to one uploaded to gist.github.com would be great. Much appreciated!
##### Expected Results:
Please enter your expected results in this space. When running the steps supplied above in the previous section, what did you expect to happen? If showing example output, please indent your output by four spaces so it will render correctly in GitHub's viewer thingy.
##### Actual Results:
Please enter your actual results in this space. When running the steps supplied above, what actually happened? If you are showing example output, please indent your output by four spaces so it will render correctly in GitHub. Thanks again!

View File

@ -1,21 +1,24 @@
prune v2
prune docsite
prune ticket_stubs
prune packaging
prune test
prune hacking
include README.md COPYING
include SYMLINK_CACHE.json
include requirements.txt
include .coveragerc
include .yamllint
include examples/hosts
include examples/ansible.cfg
recursive-include lib/ansible/module_utils/powershell *
include lib/ansible/module_utils/powershell.ps1
recursive-include lib/ansible/modules *
recursive-include lib/ansible/galaxy/data *
recursive-include docs *
recursive-include packaging *
recursive-include test *
include Makefile
include VERSION
include MANIFEST.in
include CHANGELOG.md
include contrib/README.md
recursive-include contrib/inventory *
exclude lib/ansible/modules/core/.git*
exclude lib/ansible/modules/extras/.git*
prune lib/ansible/modules/core/.git
prune lib/ansible/modules/extras/.git

View File

@ -1,70 +0,0 @@
# Module Maintainer Guidelines
Thank you for being a maintainer of one Ansible's community modules! This guide provides module maintainers an overview of their responsibilities, resources for additional information, and links to helpful tools.
In addition to the information below, module maintainers should be familiar with:
* General Ansible community development practices (http://docs.ansible.com/ansible/community.html)
* Documentation on module development (http://docs.ansible.com/ansible/developing_modules.html)
* Any namespace-specific module guidelines (identified as GUIDELINES.md in the appropriate file tree).
***
# Maintainer Responsibilities
When you contribute a new module to the [ansible/ansible](https://github.com/ansible/ansible) repository, you become the maintainer for that module once it has been merged. Maintainership empowers you with the authority to accept, reject, or request revisions to pull requests on your module -- but as they say, "with great power comes great responsibility."
Maintainers of Ansible modules are expected to provide feedback, responses, or actions on pull requests or issues to the module(s) they maintain in a reasonably timely manner.
It is also recommended that you occasionally revisit the [contribution guidelines](https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md), as they are continually refined. Occasionally, you may be requested to update your module to move it closer to the general accepted standard requirements. We hope for this to be infrequent, and will always be a request with a fair amount of lead time (ie: not by tomorrow!).
Finally, following the [ansible-devel](https://groups.google.com/forum/#!forum/ansible-devel) mailing list can be a great way to participate in the broader Ansible community, and a place where you can influence the overall direction, quality, and goals of Ansible and its modules. If you're not on this relatively low-volume list, please join us here: https://groups.google.com/forum/#!forum/ansible-devel
The Ansible community hopes that you will find that maintaining your module is as rewarding for you as having the module is for the wider community.
***
# Pull Requests, Issues, and Workflow
## Pull Requests
Module pull requests are located in the [main Ansible repository](https://github.com/ansible/ansible/pulls).
Because of the high volume of pull requests, notification of PRs to specific modules are routed by an automated bot to the appropriate maintainer for handling. It is recommended that you set an appropriate notification process to receive notifications which mention your GitHub ID.
## Issues
Issues for modules, including bug reports, documentation bug reports, and feature requests, are tracked in the [ansible repository](https://github.com/ansible/ansible/issues).
Issues for modules are routed to their maintainers via an automated process. This process is still being refined, and currently depends upon the issue creator to provide adequate details (specifically, providing the proper module name) in order to route it correctly. If you are a maintainer of a specific module, it is recommended that you periodically search module issues for issues which mention your module's name (or some variation on that name), as well as setting an appropriate notification process for receiving notification of mentions of your GitHub ID.
## PR Workflow
Automated routing of pull requests is handled by a tool called [Ansibullbot](https://github.com/ansible/ansibullbot). (You could say that he moooo-ves things around.)
Being moderately familiar with how the workflow behind the bot operates can be helpful to you, and -- should things go awry -- your feedback can be helpful to the folks that continually help Ansibullbot to evolve.
A detailed explanation of the PR workflow can be seen here: https://github.com/ansible/community/blob/master/PR-FLOW.md
***
# Extras maintainers list
The full list of maintainers for modules is located here: https://github.com/ansible/ansibullbot/blob/master/MAINTAINERS.txt
## Changing Maintainership
Communities change over time, and no one maintains a module forever. If you'd like to propose an additional maintainer for your module, please submit a PR to the maintainers file with the Github username of the new maintainer.
If you'd like to step down as a maintainer, please submit a PR to the maintainers file removing your Github ID from the module in question. If that would leave the module with no maintainers, put "ansible" as the maintainer. This will indicate that the module is temporarily without a maintainer, and the Ansible community team will search for a new maintainer.
***
# Tools and other Resources
## Useful tools
* https://ansible.sivel.net/pr/byfile.html -- a full list of all open Pull Requests, organized by file.
## Other Resources
* Ansibullbot: https://github.com/ansible/ansibullbot
* Triage / pull request workflow and information, including definitions for Labels in GitHub: https://github.com/ansible/community/blob/master/PR-FLOW.md

144
Makefile
View File

@ -1,3 +1,4 @@
#!/usr/bin/make
# WARN: gmake syntax
########################################################
# Makefile for Ansible
@ -9,7 +10,7 @@
# make deb-src -------------- produce a DEB source
# make deb ------------------ produce a DEB
# make docs ----------------- rebuild the manpages (results are checked in)
# make tests ---------------- run the tests (see https://docs.ansible.com/ansible/dev_guide/testing_units.html for requirements)
# make tests ---------------- run the tests (see test/README.md for requirements)
# make pyflakes, make pep8 -- source code checks
########################################################
@ -21,15 +22,13 @@ OS = $(shell uname -s)
# Manpages are currently built with asciidoc -- would like to move to markdown
# This doesn't evaluate until it's called. The -D argument is the
# directory of the target file ($@), kinda like `dirname`.
MANPAGES ?= $(patsubst %.asciidoc.in,%,$(wildcard ./docs/man/man1/ansible*.1.asciidoc.in))
MANPAGES := docs/man/man1/ansible.1 docs/man/man1/ansible-playbook.1 docs/man/man1/ansible-pull.1 docs/man/man1/ansible-doc.1 docs/man/man1/ansible-galaxy.1 docs/man/man1/ansible-vault.1
ifneq ($(shell which a2x 2>/dev/null),)
ASCII2MAN = a2x -L -D $(dir $@) -d manpage -f manpage $<
ASCII2HTMLMAN = a2x -L -D docs/html/man/ -d manpage -f xhtml
ASCII2MAN = a2x -D $(dir $@) -d manpage -f manpage $<
ASCII2HTMLMAN = a2x -D docs/html/man/ -d manpage -f xhtml
else
ASCII2MAN = @echo "ERROR: AsciiDoc 'a2x' command is not installed but is required to build $(MANPAGES)" && exit 1
endif
GENERATE_CLI = docs/bin/generate_man.py
PYTHON=python
SITELIB = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
@ -48,12 +47,10 @@ else
GITINFO = ""
endif
ifeq ($(shell echo $(OS) | egrep -c 'Darwin|FreeBSD|OpenBSD|DragonFly'),1)
ifeq ($(shell echo $(OS) | egrep -c 'Darwin|FreeBSD|OpenBSD'),1)
DATE := $(shell date -j -r $(shell git log -n 1 --format="%at") +%Y%m%d%H%M)
CPUS ?= $(shell sysctl hw.ncpu|awk '{print $$2}')
else
DATE := $(shell date --utc --date="$(GIT_DATE)" +%Y%m%d%H%M)
CPUS ?= $(shell nproc)
endif
# DEB build parameters
@ -70,7 +67,7 @@ ifeq ($(OFFICIAL),yes)
DEBUILD_OPTS += -k$(DEBSIGN_KEYID)
endif
else
DEB_RELEASE = 100.git$(DATE)$(GITINFO)
DEB_RELEASE = 0.git$(DATE)$(GITINFO)
# Do not sign unofficial builds
DEBUILD_OPTS += -uc -us
DPUT_OPTS += -u
@ -80,23 +77,13 @@ DEB_PPA ?= ppa
# Choose the desired Ubuntu release: lucid precise saucy trusty
DEB_DIST ?= unstable
# pbuilder parameters
PBUILDER_ARCH ?= amd64
PBUILDER_CACHE_DIR = /var/cache/pbuilder
PBUILDER_BIN ?= pbuilder
PBUILDER_OPTS ?= --debootstrapopts --variant=buildd --architecture $(PBUILDER_ARCH) --debbuildopts -b
# RPM build parameters
RPMSPECDIR= packaging/rpm
RPMSPEC = $(RPMSPECDIR)/ansible.spec
RPMDIST = $(shell rpm --eval '%{?dist}')
RPMRELEASE = $(RELEASE)
ifneq ($(OFFICIAL),yes)
RPMRELEASE = 100.git$(DATE)$(GITINFO)
endif
ifeq ($(PUBLISH),nightly)
# https://fedoraproject.org/wiki/Packaging:Versioning#Snapshots
RPMRELEASE = $(RELEASE).$(DATE)git.$(GIT_HASH)
RPMRELEASE = 0.git$(DATE)$(GITINFO)
endif
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
@ -104,40 +91,20 @@ RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
MOCK_BIN ?= mock
MOCK_CFG ?=
# ansible-test parameters
ANSIBLE_TEST ?= test/runner/ansible-test
TEST_FLAGS ?=
NOSETESTS ?= nosetests
# ansible-test units parameters (make test / make test-py3)
PYTHON_VERSION ?= $(shell python2 -c 'import sys; print("%s.%s" % sys.version_info[:2])')
PYTHON3_VERSION ?= $(shell python3 -c 'import sys; print("%s.%s" % sys.version_info[:2])')
# ansible-test integration parameters (make integration)
IMAGE ?= centos7
TARGET ?=
NOSETESTS3 ?= nosetests-3.4
########################################################
.PHONY: all
all: clean python
.PHONY: tests
tests:
$(ANSIBLE_TEST) units -v --python $(PYTHON_VERSION) $(TEST_FLAGS)
PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches
.PHONY: tests-py3
tests-py3:
$(ANSIBLE_TEST) units -v --python $(PYTHON3_VERSION) $(TEST_FLAGS)
PYTHONPATH=./lib $(NOSETESTS3) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches
.PHONY: tests-nonet
tests-nonet:
$(ANSIBLE_TEST) units -v --python $(PYTHON_VERSION) $(TEST_FLAGS) --exclude test/units/modules/network/
.PHONY: integration
integration:
$(ANSIBLE_TEST) integration -v --docker $(IMAGE) $(TARGET) $(TEST_FLAGS)
.PHONY: authors
authors:
sh hacking/authors.sh
@ -151,19 +118,19 @@ authors:
%.1: %.1.asciidoc VERSION
$(ASCII2MAN)
.PHONY: loc
loc:
sloccount lib library bin
.PHONY: pep8
pep8:
$(ANSIBLE_TEST) sanity --test pep8 --python $(PYTHON_VERSION) $(TEST_FLAGS)
@echo "#############################################"
@echo "# Running PEP8 Compliance Tests"
@echo "#############################################"
-pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 lib/ bin/
# -pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 --filename "*" library/
.PHONY: pyflakes
pyflakes:
pyflakes lib/ansible/*.py lib/ansible/*/*.py bin/*
.PHONY: clean
clean:
@echo "Cleaning up distutils stuff"
rm -rf build
@ -171,7 +138,6 @@ clean:
rm -rf lib/ansible.egg-info/
@echo "Cleaning up byte compiled python stuff"
find . -type f -regex ".*\.py[co]$$" -delete
find . -type d -name "__pycache__" -delete
@echo "Cleaning up editor backup files"
find . -type f -not -path ./test/units/inventory_test_data/group_vars/noparse/all.yml~ \( -name "*~" -or -name "#*" \) -delete
find . -type f \( -name "*.swp" \) -delete
@ -179,15 +145,8 @@ clean:
find ./docs/man -type f -name "*.xml" -delete
find ./docs/man -type f -name "*.asciidoc" -delete
find ./docs/man/man3 -type f -name "*.3" -delete
rm -f ./docs/man/man1/*
@echo "Cleaning up output from test runs"
rm -rf test/test_data
rm -rf shippable/
rm -rf logs/
rm -rf .cache/
rm -f test/units/.coverage*
rm -f test/results/*/*
find test/ -type f -name '*.retry' -delete
@echo "Cleaning up RPM building stuff"
rm -rf MANIFEST rpm-build
@echo "Cleaning up Debian building stuff"
@ -197,49 +156,39 @@ clean:
rm -rf docs/js
@echo "Cleaning up authors file"
rm -f AUTHORS.TXT
@echo "Cleaning up docsite"
$(MAKE) -C docs/docsite clean
$(MAKE) -C docs/api clean
find . -type f -name '*.pyc' -delete
.PHONY: python
python:
$(PYTHON) setup.py build
.PHONY: install
install:
$(PYTHON) setup.py install
.PHONY: sdist
sdist: clean docs
$(PYTHON) setup.py sdist
.PHONY: sdist_upload
sdist_upload: clean docs
$(PYTHON) setup.py sdist upload 2>&1 |tee upload.log
.PHONY: rpmcommon
rpmcommon: sdist
rpmcommon: $(MANPAGES) sdist
@mkdir -p rpm-build
@cp dist/*.gz rpm-build/
@sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}$(REPOTAG)#' $(RPMSPEC) >rpm-build/$(NAME).spec
@sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)%{?dist}#' $(RPMSPEC) >rpm-build/$(NAME).spec
.PHONY: mock-srpm
mock-srpm: /etc/mock/$(MOCK_CFG).cfg rpmcommon
$(MOCK_BIN) -r $(MOCK_CFG) $(MOCK_ARGS) --resultdir rpm-build/ --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build/
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build/ --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build/
@echo "#############################################"
@echo "Ansible SRPM is built:"
@echo rpm-build/*.src.rpm
@echo "#############################################"
.PHONY: mock-rpm
mock-rpm: /etc/mock/$(MOCK_CFG).cfg mock-srpm
$(MOCK_BIN) -r $(MOCK_CFG) $(MOCK_ARGS) --resultdir rpm-build/ --rebuild rpm-build/$(NAME)-*.src.rpm
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build/ --rebuild rpm-build/$(NAME)-*.src.rpm
@echo "#############################################"
@echo "Ansible RPM is built:"
@echo rpm-build/*.noarch.rpm
@echo "#############################################"
.PHONY: srpm
srpm: rpmcommon
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
--define "_builddir %{_topdir}" \
@ -254,7 +203,6 @@ srpm: rpmcommon
@echo " rpm-build/$(RPMNVR).src.rpm"
@echo "#############################################"
.PHONY: rpm
rpm: rpmcommon
@rpmbuild --define "_topdir %(pwd)/rpm-build" \
--define "_builddir %{_topdir}" \
@ -271,7 +219,6 @@ rpm: rpmcommon
@echo " rpm-build/$(RPMNVR).noarch.rpm"
@echo "#############################################"
.PHONY: debian
debian: sdist
@for DIST in $(DEB_DIST) ; do \
mkdir -p deb-build/$${DIST} ; \
@ -280,25 +227,7 @@ debian: sdist
sed -ie "s|%VERSION%|$(VERSION)|g;s|%RELEASE%|$(DEB_RELEASE)|;s|%DIST%|$${DIST}|g;s|%DATE%|$(DEB_DATE)|g" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
done
.PHONY: deb
deb: deb-src
@for DIST in $(DEB_DIST) ; do \
PBUILDER_OPTS="$(PBUILDER_OPTS) --distribution $${DIST} --basetgz $(PBUILDER_CACHE_DIR)/$${DIST}-$(PBUILDER_ARCH)-base.tgz --buildresult $(CURDIR)/deb-build/$${DIST}" ; \
$(PBUILDER_BIN) create $${PBUILDER_OPTS} --othermirror "deb http://archive.ubuntu.com/ubuntu $${DIST} universe" ; \
$(PBUILDER_BIN) update $${PBUILDER_OPTS} ; \
$(PBUILDER_BIN) build $${PBUILDER_OPTS} deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}.dsc ; \
done
@echo "#############################################"
@echo "Ansible DEB artifacts:"
@for DIST in $(DEB_DIST) ; do \
echo deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \
done
@echo "#############################################"
# Build package outside of pbuilder, with locally installed dependencies.
# Install BuildRequires as noted in packaging/debian/control.
.PHONY: local_deb
local_deb: debian
deb: debian
@for DIST in $(DEB_DIST) ; do \
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -b) ; \
done
@ -309,7 +238,6 @@ local_deb: debian
done
@echo "#############################################"
.PHONY: deb-src
deb-src: debian
@for DIST in $(DEB_DIST) ; do \
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -S) ; \
@ -321,39 +249,19 @@ deb-src: debian
done
@echo "#############################################"
.PHONY: deb-upload
deb-upload: deb
@for DIST in $(DEB_DIST) ; do \
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_amd64.changes ; \
done
.PHONY: deb-src-upload
deb-src-upload: deb-src
@for DIST in $(DEB_DIST) ; do \
$(DPUT_BIN) $(DPUT_OPTS) $(DEB_PPA) deb-build/$${DIST}/$(NAME)_$(VERSION)-$(DEB_RELEASE)~$${DIST}_source.changes ; \
done
.PHONY: epub
epub:
(cd docs/docsite/; CPUS=$(CPUS) make epub)
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
.PHONY: webdocs
webdocs:
(cd docs/docsite/; CPUS=$(CPUS) make docs)
.PHONY: generate_asciidoc
generate_asciidoc: lib/ansible/cli/*.py
mkdir -p ./docs/man/man1/ ; \
PYTHONPATH=./lib $(GENERATE_CLI) --template-file=docs/templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py
docs: generate_asciidoc
make $(MANPAGES)
.PHONY: alldocs
alldocs: docs webdocs
version:
@echo $(VERSION)
webdocs: $(MANPAGES)
(cd docsite/; make docs)
docs: $(MANPAGES)

View File

@ -1,5 +1,6 @@
[![PyPI version](https://img.shields.io/pypi/v/ansible.svg)](https://pypi.python.org/pypi/ansible)
[![Build Status](https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=devel)](https://app.shippable.com/projects/573f79d02a8192902e20e34b)
[![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)
Ansible
@ -7,11 +8,13 @@ Ansible
Ansible is a radically simple IT automation system. It handles configuration-management, application deployment, cloud provisioning, ad-hoc task-execution, and multinode orchestration - including trivializing things like zero downtime rolling updates with load balancers.
Read the documentation and more at https://ansible.com/
Read the documentation and more at http://ansible.com/
Many users run straight from the development branch (it's generally fine to do so), but you might also wish to consume a release.
Many users run straight from the development branch (it's generally fine to do so), but you might also wish to consume a release.
You can find instructions [here](https://docs.ansible.com/ansible/intro_installation.html) for a variety of platforms.
You can find instructions [here](http://docs.ansible.com/intro_getting_started.html) for a variety of platforms. If you decide to go with the development branch, be sure to run `git submodule update --init --recursive` after doing a checkout.
If you want to download a tarball of a release, go to [releases.ansible.com](http://releases.ansible.com/ansible), though most users use `yum` (using the EPEL instructions linked above), `apt` (using the PPA instructions linked above), or `pip install ansible`.
Design Principles
=================
@ -25,15 +28,15 @@ Design Principles
* Allow module development in any dynamic language, not just Python
* Be usable as non-root
* Be the easiest IT automation system to use, ever.
Get Involved
============
* Read [Community Information](https://docs.ansible.com/community.html) for all kinds of ways to contribute to and interact with the project, including mailing list information and how to submit bug reports and code to Ansible.
* Read [Community Information](http://docs.ansible.com/community.html) for all kinds of ways to contribute to and interact with the project, including mailing list information and how to submit bug reports and code to Ansible.
* All code submissions are done through pull requests. Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason. If submitting a large code change (other than modules), it's probably a good idea to join ansible-devel and talk about what you would like to do or add first and to avoid duplicate efforts. This not only helps everyone know what's going on, it also helps save time and effort if we decide some changes are needed.
* Users list: [ansible-project](https://groups.google.com/group/ansible-project)
* Development list: [ansible-devel](https://groups.google.com/group/ansible-devel)
* Announcement list: [ansible-announce](https://groups.google.com/group/ansible-announce) - read only
* Users list: [ansible-project](http://groups.google.com/group/ansible-project)
* Development list: [ansible-devel](http://groups.google.com/group/ansible-devel)
* Announcement list: [ansible-announce](http://groups.google.com/group/ansible-announce) - read only
* irc.freenode.net: #ansible
Branch Info
@ -41,20 +44,15 @@ Branch Info
* Releases are named after Led Zeppelin songs. (Releases prior to 2.0 were named after Van Halen songs.)
* The devel branch corresponds to the release actively under development.
* For releases 1.8 - 2.2, modules are kept in different repos, you'll want to follow [core](https://github.com/ansible/ansible-modules-core) and [extras](https://github.com/ansible/ansible-modules-extras)
* As of 1.8, modules are kept in different repos, you'll want to follow [core](https://github.com/ansible/ansible-modules-core) and [extras](https://github.com/ansible/ansible-modules-extras)
* Various release-X.Y branches exist for previous releases.
* We'd love to have your contributions, read [Community Information](https://docs.ansible.com/community.html) for notes on how to get started.
* We'd love to have your contributions, read [Community Information](http://docs.ansible.com/community.html) for notes on how to get started.
Authors
=======
Ansible was created by [Michael DeHaan](https://github.com/mpdehaan) (michael.dehaan/gmail/com) and has contributions from over 1000 users (and growing). Thanks everyone!
Ansible is sponsored by [Ansible, Inc](https://ansible.com)
Licence
=======
GNU
Click on the [Link](COPYING) to see the full text.
Ansible is sponsored by [Ansible, Inc](http://ansible.com)

View File

@ -1,87 +1,70 @@
Ansible Releases at a Glance
============================
VERSION RELEASE CODE NAME
++++++++++++++++++++++++++++++
Active Development
++++++++++++++++++
2.1 "TBD" - in progress
Released
++++++++
2.0.1 "Over the Hills and Far Away" TBD
2.0.0 "Over the Hills and Far Away" 01-12-2015
1.9.4 "Dancing In the Streets" 10-09-2015
1.9.3 "Dancing In the Streets" 09-03-2015
1.9.2 "Dancing In the Streets" 06-24-2015
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
2.5.0 TBD "Kashmir"
2.4.2 11-29-2017 "Dancing Days"
2.4.1 10-25-2017 "Dancing Days"
2.4.0 09-19-2017 "Dancing Days"
2.3.2 08-04-2017 "Ramble On"
2.3.1 06-01-2017 "Ramble On"
2.3.0 04-12-2017 "Ramble On"
2.2.3 05-09-2017 "The Battle of Evermore"
2.2.2 03-27-2017 "The Battle of Evermore"
2.2.1 01-16-2017 "The Battle of Evermore"
2.2.0 11-01-2016 "The Battle of Evermore"
2.1.6 06-01-2017 "The Song Remains the Same"
2.1.5 03-27-2017 "The Song Remains the Same"
2.1.4 01-16-2017 "The Song Remains the Same"
2.1.3 11-04-2016 "The Song Remains the Same"
2.1.2 09-29-2016 "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 11-25-2014 "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,28 +0,0 @@
*******************************
Roadmap For Ansible by Red Hat
*******************************
This document is now the location for published Ansible Core roadmaps.
The roadmap will be updated by version. Based on team and community feedback, an initial roadmap will be published for a major or minor version (2.0, 2.1). Subminor versions will generally not have roadmaps published.
2.1 was the first time Ansible has published this and asked for feedback in this manner. So feedback on the roadmap and the new process is quite welcome. The team is aiming for further transparency and better inclusion of both community desires and submissions.
These roadmaps are the team's *best guess* roadmaps based on the Ansible team's experience and are also based on requests and feedback from the community. There are things that may not make it on due to time constraints, lack of community maintainers, etc. And there may be things that got missed, so each roadmap is published both as an idea of what is upcoming in Ansible, and as a medium for seeking further feedback from the community. Here are the good places for you to submit feedback:
- Add to the agenda of a `Core IRC Meetings <https://github.com/ansible/community/blob/master/meetings/README.md>`_ (preferred)
- Ansible's google-group: ansible-devel
- AnsibleFest conferences.
- IRC freenode channel: #ansible-devel (this one may have things lost in lots of conversation, so a caution).
=======================================
Current 2.4 Roadmap is open for comment
=======================================
We are starting to build the 2.4 Roadmap. We are seeking community feedback! That document is here:
- `2.4 Roadmap Document <docs/docsite/rst/roadmap/ROADMAP_2_4.rst>`_
**Old Roadmaps are found here:**
- `2.3 Roadmap Document <docs/docsite/rst/roadmap/ROADMAP_2_3.rst>`_
- `2.2 Roadmap Document <docs/docsite/rst/roadmap/ROADMAP_2_2.rst>`_
- `2.1 Roadmap Document <docs/docsite/rst/roadmap/ROADMAP_2_1.rst>`_

View File

@ -1 +1 @@
2.4.2.0 1
2.0.1.0 0.2.network

File diff suppressed because it is too large Load Diff

View File

@ -33,18 +33,16 @@ except Exception:
pass
import os
import shutil
import sys
import traceback
import ansible.constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
from ansible.utils.display import Display
from ansible.module_utils._text import to_text
from ansible.utils.unicode import to_unicode
########################################
# OUTPUT OF LAST RESORT
### OUTPUT OF LAST RESORT ###
class LastResort(object):
def display(self, msg):
print(msg, file=sys.stderr)
@ -66,77 +64,52 @@ if __name__ == '__main__':
display.debug("starting run")
sub = None
target = me.split('-')
if target[-1][0].isdigit():
# Remove any version or python version info as downstreams
# sometimes add that
target = target[:-1]
if len(target) > 1:
sub = target[1]
myclass = "%sCLI" % sub.capitalize()
elif target[0] == 'ansible':
sub = 'adhoc'
myclass = 'AdHocCLI'
else:
raise AnsibleError("Unknown Ansible alias: %s" % me)
try:
mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
except ImportError as e:
# ImportError members have changed in py3
if 'msg' in dir(e):
msg = e.msg
if me.find('-') != -1:
target = me.split('-')
if len(target) > 1:
sub = target[1]
myclass = "%sCLI" % sub.capitalize()
mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
elif me == 'ansible':
from ansible.cli.adhoc import AdHocCLI as mycli
else:
msg = e.message
if msg.endswith(' %s' % sub):
raise AnsibleError("Unknown Ansible alias: %s" % me)
except ImportError as e:
if e.message.endswith(' %s' % sub):
raise AnsibleError("Ansible sub-program not implemented: %s" % me)
else:
raise
try:
args = [to_text(a, errors='surrogate_or_strict') for a in sys.argv]
except UnicodeError:
display.error('Command line args are not in utf-8, unable to continue. Ansible currently only understands utf-8')
display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc()))
exit_code = 6
else:
cli = mycli(args)
cli.parse()
exit_code = cli.run()
cli = mycli(sys.argv)
cli.parse()
sys.exit(cli.run())
except AnsibleOptionsError as e:
cli.parser.print_help()
display.error(to_text(e), wrap_text=False)
exit_code = 5
display.error(to_unicode(e), wrap_text=False)
sys.exit(5)
except AnsibleParserError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 4
display.error(to_unicode(e), wrap_text=False)
sys.exit(4)
# TQM takes care of these, but leaving comment to reserve the exit codes
# except AnsibleHostUnreachable as e:
# display.error(str(e))
# exit_code = 3
# sys.exit(3)
# except AnsibleHostFailed as e:
# display.error(str(e))
# exit_code = 2
# sys.exit(2)
except AnsibleError as e:
display.error(to_text(e), wrap_text=False)
exit_code = 1
display.error(to_unicode(e), wrap_text=False)
sys.exit(1)
except KeyboardInterrupt:
display.error("User interrupted execution")
exit_code = 99
sys.exit(99)
except Exception as e:
have_cli_options = cli is not None and cli.options is not None
display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
display.error("Unexpected Exception: %s" % to_unicode(e), wrap_text=False)
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
log_only = False
display.display(u"the full traceback was:\n\n%s" % to_unicode(traceback.format_exc()))
else:
display.display("to see the full traceback, use -vvv")
log_only = True
display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only)
exit_code = 250
finally:
# Remove ansible tempdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
sys.exit(exit_code)
sys.exit(250)

View File

@ -1 +0,0 @@
ansible

View File

@ -1,388 +0,0 @@
#!/usr/bin/env python
# (c) 2017, Ansible, Inc. <support@ansible.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
########################################################
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
__requires__ = ['ansible']
try:
import pkg_resources
except Exception:
pass
import fcntl
import os
import shlex
import signal
import socket
import sys
import time
import traceback
import datetime
import errno
from ansible import constants as C
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle
from ansible.module_utils.connection import send_data, recv_data
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import connection_loader
from ansible.utils.path import unfrackpath, makedirs_safe
from ansible.errors import AnsibleConnectionFailure
from ansible.utils.display import Display
def do_fork():
'''
Does the required double fork for a daemon process. Based on
http://code.activestate.com/recipes/66012-fork-a-daemon-process-on-unix/
'''
try:
pid = os.fork()
if pid > 0:
return pid
# This is done as a 'good practice' for daemons, but we need to keep the cwd
# leaving it here as a note that we KNOW its good practice but are not doing it on purpose.
# os.chdir("/")
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
if C.DEFAULT_LOG_PATH != '':
out_file = open(C.DEFAULT_LOG_PATH, 'ab+')
err_file = open(C.DEFAULT_LOG_PATH, 'ab+', 0)
else:
out_file = open('/dev/null', 'ab+')
err_file = open('/dev/null', 'ab+', 0)
os.dup2(out_file.fileno(), sys.stdout.fileno())
os.dup2(err_file.fileno(), sys.stderr.fileno())
os.close(sys.stdin.fileno())
return pid
except OSError as e:
sys.exit(1)
except OSError as e:
sys.exit(1)
class Server():
def __init__(self, socket_path, play_context):
self.socket_path = socket_path
self.play_context = play_context
display.display(
'creating new control socket for host %s:%s as user %s' %
(play_context.remote_addr, play_context.port, play_context.remote_user),
log_only=True
)
display.display('control socket path is %s' % socket_path, log_only=True)
display.display('current working directory is %s' % os.getcwd(), log_only=True)
self._start_time = datetime.datetime.now()
display.display("using connection plugin %s" % self.play_context.connection, log_only=True)
self.connection = connection_loader.get(play_context.connection, play_context, sys.stdin)
self.connection._connect()
if not self.connection.connected:
raise AnsibleConnectionFailure('unable to connect to remote host %s' % self._play_context.remote_addr)
connection_time = datetime.datetime.now() - self._start_time
display.display('connection established to %s in %s' % (play_context.remote_addr, connection_time), log_only=True)
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.bind(self.socket_path)
self.socket.listen(1)
display.display('local socket is set to listening', log_only=True)
def run(self):
try:
while True:
signal.signal(signal.SIGALRM, self.connect_timeout)
signal.signal(signal.SIGTERM, self.handler)
signal.alarm(C.PERSISTENT_CONNECT_TIMEOUT)
(s, addr) = self.socket.accept()
display.display('incoming request accepted on persistent socket', log_only=True)
signal.alarm(0)
while True:
data = recv_data(s)
if not data:
break
signal.signal(signal.SIGALRM, self.command_timeout)
signal.alarm(self.play_context.timeout)
op = to_text(data.split(b':')[0])
display.display('socket operation is %s' % op, log_only=True)
method = getattr(self, 'do_%s' % op, None)
rc = 255
stdout = stderr = ''
if not method:
stderr = 'Invalid action specified'
else:
rc, stdout, stderr = method(data)
signal.alarm(0)
display.display('socket operation completed with rc %s' % rc, log_only=True)
send_data(s, to_bytes(rc))
send_data(s, to_bytes(stdout))
send_data(s, to_bytes(stderr))
s.close()
except Exception as e:
# socket.accept() will raise EINTR if the socket.close() is called
if e.errno != errno.EINTR:
display.display(traceback.format_exc(), log_only=True)
finally:
# when done, close the connection properly and cleanup
# the socket file so it can be recreated
self.shutdown()
end_time = datetime.datetime.now()
delta = end_time - self._start_time
display.display('shutdown local socket, connection was active for %s secs' % delta, log_only=True)
def connect_timeout(self, signum, frame):
display.display('persistent connection idle timeout triggered, timeout value is %s secs' % C.PERSISTENT_CONNECT_TIMEOUT, log_only=True)
self.shutdown()
def command_timeout(self, signum, frame):
display.display('command timeout triggered, timeout value is %s secs' % self.play_context.timeout, log_only=True)
self.shutdown()
def handler(self, signum, frame):
display.display('signal handler called with signal %s' % signum, log_only=True)
self.shutdown()
def shutdown(self):
display.display('shutdown persistent connection requested', log_only=True)
if not os.path.exists(self.socket_path):
display.display('persistent connection is not active', log_only=True)
return
try:
if self.socket:
display.display('closing local listener', log_only=True)
self.socket.close()
if self.connection:
display.display('closing the connection', log_only=True)
self.connection.close()
except:
pass
finally:
if os.path.exists(self.socket_path):
display.display('removing the local control socket', log_only=True)
os.remove(self.socket_path)
display.display('shutdown complete', log_only=True)
def do_EXEC(self, data):
cmd = data.split(b'EXEC: ')[1]
return self.connection.exec_command(cmd)
def do_PUT(self, data):
(op, src, dst) = shlex.split(to_native(data))
return self.connection.fetch_file(src, dst)
def do_FETCH(self, data):
(op, src, dst) = shlex.split(to_native(data))
return self.connection.put_file(src, dst)
def do_CONTEXT(self, data):
pc_data = data.split(b'CONTEXT: ', 1)[1]
if PY3:
pc_data = cPickle.loads(pc_data, encoding='bytes')
else:
pc_data = cPickle.loads(pc_data)
pc = PlayContext()
pc.deserialize(pc_data)
try:
self.connection.update_play_context(pc)
except AttributeError:
pass
return (0, 'ok', '')
def do_RUN(self, data):
timeout = self.play_context.timeout
while bool(timeout):
if os.path.exists(self.socket_path):
break
time.sleep(1)
timeout -= 1
socket_bytes = to_bytes(self.socket_path, errors='surrogate_or_strict')
return 0, b'\n#SOCKET_PATH#: %s\n' % socket_bytes, ''
def communicate(sock, data):
send_data(sock, data)
rc = int(recv_data(sock), 10)
stdout = recv_data(sock)
stderr = recv_data(sock)
return (rc, stdout, stderr)
def main():
# Need stdin as a byte stream
if PY3:
stdin = sys.stdin.buffer
else:
stdin = sys.stdin
try:
# read the play context data via stdin, which means depickling it
# FIXME: as noted above, we will probably need to deserialize the
# connection loader here as well at some point, otherwise this
# won't find role- or playbook-based connection plugins
cur_line = stdin.readline()
init_data = b''
while cur_line.strip() != b'#END_INIT#':
if cur_line == b'':
raise Exception("EOF found before init data was complete")
init_data += cur_line
cur_line = stdin.readline()
if PY3:
pc_data = cPickle.loads(init_data, encoding='bytes')
else:
pc_data = cPickle.loads(init_data)
pc = PlayContext()
pc.deserialize(pc_data)
except Exception as e:
# FIXME: better error message/handling/logging
sys.stderr.write(traceback.format_exc())
sys.exit("FAIL: %s" % e)
ssh = connection_loader.get('ssh', class_only=True)
cp = ssh._create_control_path(pc.remote_addr, pc.port, pc.remote_user, pc.connection)
# create the persistent connection dir if need be and create the paths
# which we will be using later
tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR)
makedirs_safe(tmp_path)
lock_path = unfrackpath("%s/.ansible_pc_lock" % tmp_path)
socket_path = unfrackpath(cp % dict(directory=tmp_path))
# if the socket file doesn't exist, spin up the daemon process
lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
fcntl.lockf(lock_fd, fcntl.LOCK_EX)
if not os.path.exists(socket_path):
pid = do_fork()
if pid == 0:
rc = 0
try:
server = Server(socket_path, pc)
except AnsibleConnectionFailure as exc:
display.display('connecting to host %s returned an error' % pc.remote_addr, log_only=True)
display.display(str(exc), log_only=True)
rc = 1
except Exception as exc:
display.display('failed to create control socket for host %s' % pc.remote_addr, log_only=True)
display.display(traceback.format_exc(), log_only=True)
rc = 1
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
os.close(lock_fd)
if rc == 0:
server.run()
sys.exit(rc)
else:
display.display('re-using existing socket for %s@%s:%s' % (pc.remote_user, pc.remote_addr, pc.port), log_only=True)
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
os.close(lock_fd)
timeout = pc.timeout
while bool(timeout):
if os.path.exists(socket_path):
display.vvvv('connected to local socket in %s' % (pc.timeout - timeout), pc.remote_addr)
break
time.sleep(1)
timeout -= 1
else:
raise AnsibleConnectionFailure('timeout waiting for local socket', pc.remote_addr)
# now connect to the daemon process
# FIXME: if the socket file existed but the daemonized process was killed,
# the connection will timeout here. Need to make this more resilient.
while True:
data = stdin.readline()
if data == b'':
break
if data.strip() == b'':
continue
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
connect_retry_timeout = C.PERSISTENT_CONNECT_RETRY_TIMEOUT
while bool(connect_retry_timeout):
try:
sock.connect(socket_path)
break
except socket.error:
time.sleep(1)
connect_retry_timeout -= 1
else:
display.display('connect retry timeout expired, unable to connect to control socket', pc.remote_addr, pc.remote_user, log_only=True)
display.display('persistent_connect_retry_timeout is %s secs' % (C.PERSISTENT_CONNECT_RETRY_TIMEOUT), pc.remote_addr, pc.remote_user, log_only=True)
sys.stderr.write('failed to connect to control socket')
sys.exit(255)
# send the play_context back into the connection so the connection
# can handle any privilege escalation activities
pc_data = b'CONTEXT: %s' % init_data
communicate(sock, pc_data)
rc, stdout, stderr = communicate(sock, data.strip())
sys.stdout.write(to_native(stdout))
sys.stderr.write(to_native(stderr))
sock.close()
break
sys.exit(rc)
if __name__ == '__main__':
display = Display()
main()

View File

@ -1 +0,0 @@
ansible

View File

@ -1 +0,0 @@
ansible

View File

@ -1,26 +1,17 @@
contrib
-------
Files here provide an extension mechanism for Ansible similar to plugins. They are not maintained by the Ansible core team or installed with Ansible.
inventory
=========
Before 2.4 introduced inventory plugins, inventory scripts were the only way to provide sources that were not built into Ansible. Inventory scripts allow you to store your hosts, groups, and variables in any way you like.
Inventory scripts allow you to store your hosts, groups, and variables in any way
you like. Examples include discovering inventory from EC2 or pulling it from
Cobbler. These could also be used to interface with LDAP or database.
Starting with Ansible version 2.4, they are enabled via the 'script' inventory plugin.
Examples of use include discovering inventory from EC2 or pulling it from Cobbler. These could also be used to interface with LDAP or the database.
`chmod +x` an inventory plugin and either name it `/etc/ansible/hosts` or use `ansible -i /path/to/inventory/script`. You might also need to copy a configuration file with the same name and/or set environment variables. The scripts or configuration files can provide more details.
vault
=====
If the file passed to `--vault-password-file` has the executable bit set, Ansible will execute it and use the stdout of that execution as 'the secret'.
Vault scripts provided here use this facility to retrieve the vault secret from a number of sources.
chmod +x an inventory plugin and either name it /etc/ansible/hosts or use ansible
with -i to designate the path to the script. You might also need to copy a configuration
file with the same name and/or set environment variables, the scripts or configuration
files have more details.
contributions welcome
=====================
Send in pull requests to add scripts of your own. The sky is the limit!
Send in pull requests to add plugins of your own. The sky is the limit!

View File

@ -2,7 +2,7 @@
#
# Define an Abiquo user with access to Abiquo API which will be used to
# perform required queries to obtain information to generate the Ansible
# perform required queries to obtain infromation to generate the Ansible
# inventory output.
#
[auth]

View File

@ -18,7 +18,7 @@ This script generates an Ansible hosts file with these host groups:
ABQ_xxx: Defines a hosts itself by Abiquo VM name label
all: Contains all hosts defined in Abiquo user's enterprise
virtualdatecenter: Creates a host group for each virtualdatacenter containing all hosts defined on it
virtualdatecenter: Creates a host group for each virtualdatacenter containing all hosts defined on it
virtualappliance: Creates a host group for each virtualappliance containing all hosts defined on it
imagetemplate: Creates a host group for each image template containing all hosts using it
@ -53,68 +53,64 @@ except ImportError:
from ansible.module_utils.urls import open_url
def api_get(link, config):
try:
if link is None:
url = config.get('api', 'uri') + config.get('api', 'login_path')
headers = {"Accept": config.get('api', 'login_type')}
if link == None:
url = config.get('api','uri') + config.get('api','login_path')
headers = {"Accept": config.get('api','login_type')}
else:
url = link['href'] + '?limit=0'
headers = {"Accept": link['type']}
result = open_url(url, headers=headers, url_username=config.get('auth', 'apiuser').replace('\n', ''),
url_password=config.get('auth', 'apipass').replace('\n', ''))
result = open_url(url, headers=headers, url_username=config.get('auth','apiuser').replace('\n', ''),
url_password=config.get('auth','apipass').replace('\n', ''))
return json.loads(result.read())
except:
return None
def save_cache(data, config):
''' saves item to cache '''
dpath = config.get('cache', 'cache_dir')
dpath = config.get('cache','cache_dir')
try:
cache = open('/'.join([dpath, 'inventory']), 'w')
cache = open('/'.join([dpath,'inventory']), 'w')
cache.write(json.dumps(data))
cache.close()
except IOError as e:
pass # not really sure what to do here
pass # not really sure what to do here
def get_cache(cache_item, config):
''' returns cached item '''
dpath = config.get('cache', 'cache_dir')
dpath = config.get('cache','cache_dir')
inv = {}
try:
cache = open('/'.join([dpath, 'inventory']), 'r')
cache = open('/'.join([dpath,'inventory']), 'r')
inv = cache.read()
cache.close()
except IOError as e:
pass # not really sure what to do here
pass # not really sure what to do here
return inv
def cache_available(config):
''' checks if we have a 'fresh' cache available for item requested '''
if config.has_option('cache', 'cache_dir'):
dpath = config.get('cache', 'cache_dir')
if config.has_option('cache','cache_dir'):
dpath = config.get('cache','cache_dir')
try:
existing = os.stat('/'.join([dpath, 'inventory']))
existing = os.stat( '/'.join([dpath,'inventory']))
except:
# cache doesn't exist or isn't accessible
return False
if config.has_option('cache', 'cache_max_age'):
maxage = config.get('cache', 'cache_max_age')
if (int(time.time()) - int(existing.st_mtime)) <= int(maxage):
if ((int(time.time()) - int(existing.st_mtime)) <= int(maxage)):
return True
return False
def generate_inv_from_api(enterprise_entity, config):
def generate_inv_from_api(enterprise_entity,config):
try:
inventory['all'] = {}
inventory['all']['children'] = []
@ -122,58 +118,54 @@ def generate_inv_from_api(enterprise_entity, config):
inventory['_meta'] = {}
inventory['_meta']['hostvars'] = {}
enterprise = api_get(enterprise_entity, config)
vms_entity = next(link for link in enterprise['links'] if link['rel'] == 'virtualmachines')
vms = api_get(vms_entity, config)
enterprise = api_get(enterprise_entity,config)
vms_entity = next(link for link in (enterprise['links']) if (link['rel']=='virtualmachines'))
vms = api_get(vms_entity,config)
for vmcollection in vms['collection']:
for link in vmcollection['links']:
if link['rel'] == 'virtualappliance':
vm_vapp = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
elif link['rel'] == 'virtualdatacenter':
vm_vdc = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
elif link['rel'] == 'virtualmachinetemplate':
vm_template = link['title'].replace('[', '').replace(']', '').replace(' ', '_')
vm_vapp = next(link for link in (vmcollection['links']) if (link['rel']=='virtualappliance'))['title'].replace('[','').replace(']','').replace(' ','_')
vm_vdc = next(link for link in (vmcollection['links']) if (link['rel']=='virtualdatacenter'))['title'].replace('[','').replace(']','').replace(' ','_')
vm_template = next(link for link in (vmcollection['links']) if (link['rel']=='virtualmachinetemplate'))['title'].replace('[','').replace(']','').replace(' ','_')
# From abiquo.ini: Only adding to inventory VMs with public IP
if config.getboolean('defaults', 'public_ip_only') is True:
if (config.getboolean('defaults', 'public_ip_only')) == True:
for link in vmcollection['links']:
if link['type'] == 'application/vnd.abiquo.publicip+json' and link['rel'] == 'ip':
vm_nic = link['title']
break
if (link['type']=='application/vnd.abiquo.publicip+json' and link['rel']=='ip'):
vm_nic = link['title']
break
else:
vm_nic = None
vm_nic = None
# Otherwise, assigning defined network interface IP address
else:
for link in vmcollection['links']:
if link['rel'] == config.get('defaults', 'default_net_interface'):
vm_nic = link['title']
break
if (link['rel']==config.get('defaults', 'default_net_interface')):
vm_nic = link['title']
break
else:
vm_nic = None
vm_nic = None
vm_state = True
# From abiquo.ini: Only adding to inventory VMs deployed
if config.getboolean('defaults', 'deployed_only') is True and vmcollection['state'] == 'NOT_ALLOCATED':
if ((config.getboolean('defaults', 'deployed_only') == True) and (vmcollection['state'] == 'NOT_ALLOCATED')):
vm_state = False
if vm_nic is not None and vm_state:
if vm_vapp not in inventory:
if not vm_nic == None and vm_state:
if not vm_vapp in inventory.keys():
inventory[vm_vapp] = {}
inventory[vm_vapp]['children'] = []
inventory[vm_vapp]['hosts'] = []
if vm_vdc not in inventory:
if not vm_vdc in inventory.keys():
inventory[vm_vdc] = {}
inventory[vm_vdc]['hosts'] = []
inventory[vm_vdc]['children'] = []
if vm_template not in inventory:
if not vm_template in inventory.keys():
inventory[vm_template] = {}
inventory[vm_template]['children'] = []
inventory[vm_template]['hosts'] = []
if config.getboolean('defaults', 'get_metadata') is True:
meta_entity = next(link for link in vmcollection['links'] if link['rel'] == 'metadata')
if config.getboolean('defaults', 'get_metadata') == True:
meta_entity = next(link for link in (vmcollection['links']) if (link['rel']=='metadata'))
try:
metadata = api_get(meta_entity, config)
if (config.getfloat("api", "version") >= 3.0):
metadata = api_get(meta_entity,config)
if (config.getfloat("api","version") >= 3.0):
vm_metadata = metadata['metadata']
else:
vm_metadata = metadata['metadata']['metadata']
@ -191,8 +183,7 @@ def generate_inv_from_api(enterprise_entity, config):
return inventory
except Exception as e:
# Return empty hosts output
return {'all': {'hosts': []}, '_meta': {'hostvars': {}}}
return { 'all': {'hosts': []}, '_meta': { 'hostvars': {} } }
def get_inventory(enterprise, config):
''' Reads the inventory from cache or Abiquo api '''
@ -202,12 +193,11 @@ def get_inventory(enterprise, config):
else:
default_group = os.path.basename(sys.argv[0]).rstrip('.py')
# MAKE ABIQUO API CALLS #
inv = generate_inv_from_api(enterprise, config)
inv = generate_inv_from_api(enterprise,config)
save_cache(inv, config)
return json.dumps(inv)
if __name__ == '__main__':
inventory = {}
enterprise = {}
@ -220,8 +210,8 @@ if __name__ == '__main__':
break
try:
login = api_get(None, config)
enterprise = next(link for link in login['links'] if link['rel'] == 'enterprise')
login = api_get(None,config)
enterprise = next(link for link in (login['links']) if (link['rel']=='enterprise'))
except Exception as e:
enterprise = None

View File

@ -82,6 +82,7 @@ class LibcloudInventory(object):
print(data_to_print)
def is_cache_valid(self):
''' Determines if the cache files have expired, or if it is still valid '''
@ -94,6 +95,7 @@ class LibcloudInventory(object):
return False
def read_settings(self):
''' Reads the settings from the libcloud.ini file '''
@ -106,17 +108,17 @@ class LibcloudInventory(object):
raise ValueError('libcloud.ini file must contain a [driver] section')
if config.has_option('driver', 'provider'):
self.provider = config.get('driver', 'provider')
self.provider = config.get('driver','provider')
else:
raise ValueError('libcloud.ini does not have a provider defined')
if config.has_option('driver', 'key'):
self.key = config.get('driver', 'key')
self.key = config.get('driver','key')
else:
raise ValueError('libcloud.ini does not have a key defined')
if config.has_option('driver', 'secret'):
self.secret = config.get('driver', 'secret')
self.secret = config.get('driver','secret')
else:
raise ValueError('libcloud.ini does not have a secret defined')
@ -131,7 +133,7 @@ class LibcloudInventory(object):
if config.has_option('driver', 'path'):
self.path = config.get('driver', 'path')
if config.has_option('driver', 'api_version'):
self.api_version = config.get('driver', 'api_version')
self.api_version = config.get('driver', 'api_version')
Driver = get_driver(getattr(Provider, self.provider))
@ -143,6 +145,7 @@ class LibcloudInventory(object):
self.cache_path_cache = cache_path + "/ansible-libcloud.cache"
self.cache_path_index = cache_path + "/ansible-libcloud.index"
self.cache_max_age = config.getint('cache', 'cache_max_age')
def parse_cli_args(self):
'''
@ -151,16 +154,17 @@ class LibcloudInventory(object):
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on libcloud supported providers')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
help='List instances (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific instance')
help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to libcloud supported providers (default: False - use cache files)')
help='Force refresh of cache by making API requests to libcloud supported providers (default: False - use cache files)')
self.args = parser.parse_args()
def do_api_calls_update_cache(self):
'''
Do API calls to a location, and save data in cache files
'''
Do API calls to a location, and save data in cache files
'''
self.get_nodes()
@ -168,6 +172,7 @@ class LibcloudInventory(object):
self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index)
def get_nodes(self):
'''
Gets the list of all nodes
@ -176,6 +181,7 @@ class LibcloudInventory(object):
for node in self.conn.list_nodes():
self.add_node(node)
def get_node(self, node_id):
'''
Gets details about a specific node
@ -183,10 +189,11 @@ class LibcloudInventory(object):
return [node for node in self.conn.list_nodes() if node.id == node_id][0]
def add_node(self, node):
'''
Adds a node to the inventory and index, as long as it is
addressable
addressable
'''
# Only want running instances
@ -218,7 +225,7 @@ class LibcloudInventory(object):
# Inventory: Group by key pair
if node.extra['key_name']:
self.push(self.inventory, self.to_safe('key_' + node.extra['key_name']), dest)
# Inventory: Group by security group, quick thing to handle single sg
if node.extra['security_group']:
self.push(self.inventory, self.to_safe('sg_' + node.extra['security_group'][0]), dest)
@ -237,10 +244,10 @@ class LibcloudInventory(object):
# Need to load index from cache
self.load_index_from_cache()
if self.args.host not in self.index:
if not self.args.host in self.index:
# try updating the cache
self.do_api_calls_update_cache()
if self.args.host not in self.index:
if not self.args.host in self.index:
# host migh not exist anymore
return self.json_format_dict({}, True)
@ -248,7 +255,8 @@ class LibcloudInventory(object):
node = self.get_node(node_id)
instance_vars = {}
for key, value in vars(node).items():
for key in vars(instance):
value = getattr(instance, key)
key = self.to_safe('ec2_' + key)
# Handle complex types
@ -275,12 +283,13 @@ class LibcloudInventory(object):
else:
pass
# TODO Product codes if someone finds them useful
# print(key)
# print(type(value))
# print(value)
#print(key)
#print(type(value))
#print(value)
return self.json_format_dict(instance_vars, True)
def push(self, my_dict, key, element):
'''
Pushed an element onto an array that may not have been defined in
@ -288,10 +297,11 @@ class LibcloudInventory(object):
'''
if key in my_dict:
my_dict[key].append(element)
my_dict[key].append(element);
else:
my_dict[key] = [element]
def get_inventory_from_cache(self):
'''
Reads the inventory from the cache file and returns it as a JSON
@ -302,6 +312,7 @@ class LibcloudInventory(object):
json_inventory = cache.read()
return json_inventory
def load_index_from_cache(self):
'''
Reads the index from the cache file sets self.index
@ -311,6 +322,7 @@ class LibcloudInventory(object):
json_index = cache.read()
self.index = json.loads(json_index)
def write_to_cache(self, data, filename):
'''
Writes data in JSON format to a file
@ -321,6 +333,7 @@ class LibcloudInventory(object):
cache.write(json_data)
cache.close()
def to_safe(self, word):
'''
Converts 'bad' characters in a string to underscores so they can be
@ -329,6 +342,7 @@ class LibcloudInventory(object):
return re.sub("[^A-Za-z0-9\-]", "_", word)
def json_format_dict(self, data, pretty=False):
'''
Converts a dict to a JSON object and dumps it as a formatted
@ -340,9 +354,8 @@ class LibcloudInventory(object):
else:
return json.dumps(data)
def main():
LibcloudInventory()
if __name__ == '__main__':
main()
main()

View File

@ -1,20 +0,0 @@
# Ansible Apstra AOS external inventory script settings
# Dynamic Inventory script parameter can be provided using this file
# Or by using Environment Variables:
# - AOS_SERVER, AOS_PORT, AOS_USERNAME, AOS_PASSWORD, AOS_BLUEPRINT
#
# This file takes precedence over the Environment Variables
#
[aos]
# aos_server = 172.20.62.3
# port = 8888
# username = admin
# password = admin
## Blueprint Mode
# to use the inventory in mode Blueprint, you need to define the blueprint name you want to use
# blueprint = my-blueprint-l2
# blueprint_interface = true

View File

@ -1,593 +0,0 @@
#!/usr/bin/env python
#
# (c) 2017 Apstra Inc, <community@apstra.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/>.
#
"""
Apstra AOS external inventory script
====================================
Ansible has a feature where instead of reading from /etc/ansible/hosts
as a text file, it can query external programs to obtain the list
of hosts, groups the hosts are in, and even variables to assign to each host.
To use this:
- copy this file over /etc/ansible/hosts and chmod +x the file.
- Copy both files (.py and .ini) in your preferred directory
More information about Ansible Dynamic Inventory here
http://unix.stackexchange.com/questions/205479/in-ansible-dynamic-inventory-json-can-i-render-hostvars-based-on-the-hostname
2 modes are currently, supported: **device based** or **blueprint based**:
- For **Device based**, the list of device is taken from the global device list
the serial ID will be used as the inventory_hostname
- For **Blueprint based**, the list of device is taken from the given blueprint
the Node name will be used as the inventory_hostname
Input parameters parameter can be provided using either with the ini file or by using Environment Variables:
The following list of Environment Variables are supported: AOS_SERVER, AOS_PORT, AOS_USERNAME, AOS_PASSWORD, AOS_BLUEPRINT
The config file takes precedence over the Environment Variables
Tested with Apstra AOS 1.1
This script has been inspired by the cobbler.py inventory. thanks
Author: Damien Garros (@dgarros)
Version: 0.2.0
"""
import json
import os
import re
import sys
try:
import argparse
HAS_ARGPARSE = True
except ImportError:
HAS_ARGPARSE = False
try:
from apstra.aosom.session import Session
HAS_AOS_PYEZ = True
except ImportError:
HAS_AOS_PYEZ = False
from ansible.module_utils.six.moves import configparser
"""
##
Expected output format in Device mode
{
"Cumulus": {
"hosts": [
"52540073956E",
"52540022211A"
],
"vars": {}
},
"EOS": {
"hosts": [
"5254001CAFD8",
"525400DDDF72"
],
"vars": {}
},
"Generic Model": {
"hosts": [
"525400E5486D"
],
"vars": {}
},
"Ubuntu GNU/Linux": {
"hosts": [
"525400E5486D"
],
"vars": {}
},
"VX": {
"hosts": [
"52540073956E",
"52540022211A"
],
"vars": {}
},
"_meta": {
"hostvars": {
"5254001CAFD8": {
"agent_start_time": "2017-02-03T00:49:16.000000Z",
"ansible_ssh_host": "172.20.52.6",
"aos_hcl_model": "Arista_vEOS",
"aos_server": "",
"aos_version": "AOS_1.1.1_OB.5",
"comm_state": "on",
"device_start_time": "2017-02-03T00:47:58.454480Z",
"domain_name": "",
"error_message": "",
"fqdn": "localhost",
"hostname": "localhost",
"hw_model": "vEOS",
"hw_version": "",
"is_acknowledged": false,
"mgmt_ifname": "Management1",
"mgmt_ipaddr": "172.20.52.6",
"mgmt_macaddr": "52:54:00:1C:AF:D8",
"os_arch": "x86_64",
"os_family": "EOS",
"os_version": "4.16.6M",
"os_version_info": {
"build": "6M",
"major": "4",
"minor": "16"
},
"serial_number": "5254001CAFD8",
"state": "OOS-QUARANTINED",
"vendor": "Arista"
},
"52540022211A": {
"agent_start_time": "2017-02-03T00:45:22.000000Z",
"ansible_ssh_host": "172.20.52.7",
"aos_hcl_model": "Cumulus_VX",
"aos_server": "172.20.52.3",
"aos_version": "AOS_1.1.1_OB.5",
"comm_state": "on",
"device_start_time": "2017-02-03T00:45:11.019189Z",
"domain_name": "",
"error_message": "",
"fqdn": "cumulus",
"hostname": "cumulus",
"hw_model": "VX",
"hw_version": "",
"is_acknowledged": false,
"mgmt_ifname": "eth0",
"mgmt_ipaddr": "172.20.52.7",
"mgmt_macaddr": "52:54:00:22:21:1a",
"os_arch": "x86_64",
"os_family": "Cumulus",
"os_version": "3.1.1",
"os_version_info": {
"build": "1",
"major": "3",
"minor": "1"
},
"serial_number": "52540022211A",
"state": "OOS-QUARANTINED",
"vendor": "Cumulus"
},
"52540073956E": {
"agent_start_time": "2017-02-03T00:45:19.000000Z",
"ansible_ssh_host": "172.20.52.8",
"aos_hcl_model": "Cumulus_VX",
"aos_server": "172.20.52.3",
"aos_version": "AOS_1.1.1_OB.5",
"comm_state": "on",
"device_start_time": "2017-02-03T00:45:11.030113Z",
"domain_name": "",
"error_message": "",
"fqdn": "cumulus",
"hostname": "cumulus",
"hw_model": "VX",
"hw_version": "",
"is_acknowledged": false,
"mgmt_ifname": "eth0",
"mgmt_ipaddr": "172.20.52.8",
"mgmt_macaddr": "52:54:00:73:95:6e",
"os_arch": "x86_64",
"os_family": "Cumulus",
"os_version": "3.1.1",
"os_version_info": {
"build": "1",
"major": "3",
"minor": "1"
},
"serial_number": "52540073956E",
"state": "OOS-QUARANTINED",
"vendor": "Cumulus"
},
"525400DDDF72": {
"agent_start_time": "2017-02-03T00:49:07.000000Z",
"ansible_ssh_host": "172.20.52.5",
"aos_hcl_model": "Arista_vEOS",
"aos_server": "",
"aos_version": "AOS_1.1.1_OB.5",
"comm_state": "on",
"device_start_time": "2017-02-03T00:47:46.929921Z",
"domain_name": "",
"error_message": "",
"fqdn": "localhost",
"hostname": "localhost",
"hw_model": "vEOS",
"hw_version": "",
"is_acknowledged": false,
"mgmt_ifname": "Management1",
"mgmt_ipaddr": "172.20.52.5",
"mgmt_macaddr": "52:54:00:DD:DF:72",
"os_arch": "x86_64",
"os_family": "EOS",
"os_version": "4.16.6M",
"os_version_info": {
"build": "6M",
"major": "4",
"minor": "16"
},
"serial_number": "525400DDDF72",
"state": "OOS-QUARANTINED",
"vendor": "Arista"
},
"525400E5486D": {
"agent_start_time": "2017-02-02T18:44:42.000000Z",
"ansible_ssh_host": "172.20.52.4",
"aos_hcl_model": "Generic_Server_1RU_1x10G",
"aos_server": "172.20.52.3",
"aos_version": "AOS_1.1.1_OB.5",
"comm_state": "on",
"device_start_time": "2017-02-02T21:11:25.188734Z",
"domain_name": "",
"error_message": "",
"fqdn": "localhost",
"hostname": "localhost",
"hw_model": "Generic Model",
"hw_version": "pc-i440fx-trusty",
"is_acknowledged": false,
"mgmt_ifname": "eth0",
"mgmt_ipaddr": "172.20.52.4",
"mgmt_macaddr": "52:54:00:e5:48:6d",
"os_arch": "x86_64",
"os_family": "Ubuntu GNU/Linux",
"os_version": "14.04 LTS",
"os_version_info": {
"build": "",
"major": "14",
"minor": "04"
},
"serial_number": "525400E5486D",
"state": "OOS-QUARANTINED",
"vendor": "Generic Manufacturer"
}
}
},
"all": {
"hosts": [
"5254001CAFD8",
"52540073956E",
"525400DDDF72",
"525400E5486D",
"52540022211A"
],
"vars": {}
},
"vEOS": {
"hosts": [
"5254001CAFD8",
"525400DDDF72"
],
"vars": {}
}
}
"""
def fail(msg):
sys.stderr.write("%s\n" % msg)
sys.exit(1)
class AosInventory(object):
def __init__(self):
""" Main execution path """
if not HAS_AOS_PYEZ:
raise Exception('aos-pyez is not installed. Please see details here: https://github.com/Apstra/aos-pyez')
if not HAS_ARGPARSE:
raise Exception('argparse is not installed. Please install the argparse library or upgrade to python-2.7')
# Initialize inventory
self.inventory = dict() # A list of groups and the hosts in that group
self.inventory['_meta'] = dict()
self.inventory['_meta']['hostvars'] = dict()
# Read settings and parse CLI arguments
self.read_settings()
self.parse_cli_args()
# ----------------------------------------------------
# Open session to AOS
# ----------------------------------------------------
aos = Session(server=self.aos_server,
port=self.aos_server_port,
user=self.aos_username,
passwd=self.aos_password)
aos.login()
# Save session information in variables of group all
self.add_var_to_group('all', 'aos_session', aos.session)
# Add the AOS server itself in the inventory
self.add_host_to_group("all", 'aos')
self.add_var_to_host("aos", "ansible_ssh_host", self.aos_server)
self.add_var_to_host("aos", "ansible_ssh_pass", self.aos_password)
self.add_var_to_host("aos", "ansible_ssh_user", self.aos_username)
# ----------------------------------------------------
# Build the inventory
# 2 modes are supported: device based or blueprint based
# - For device based, the list of device is taken from the global device list
# the serial ID will be used as the inventory_hostname
# - For Blueprint based, the list of device is taken from the given blueprint
# the Node name will be used as the inventory_hostname
# ----------------------------------------------------
if self.aos_blueprint:
bp = aos.Blueprints[self.aos_blueprint]
if bp.exists is False:
fail("Unable to find the Blueprint: %s" % self.aos_blueprint)
for dev_name, dev_id in bp.params['devices'].value.items():
self.add_host_to_group('all', dev_name)
device = aos.Devices.find(uid=dev_id)
if 'facts' in device.value.keys():
self.add_device_facts_to_var(dev_name, device)
# Define admin State and Status
if 'user_config' in device.value.keys():
if 'admin_state' in device.value['user_config'].keys():
self.add_var_to_host(dev_name, 'admin_state', device.value['user_config']['admin_state'])
self.add_device_status_to_var(dev_name, device)
# Go over the contents data structure
for node in bp.contents['system']['nodes']:
if node['display_name'] == dev_name:
self.add_host_to_group(node['role'], dev_name)
# Check for additional attribute to import
attributes_to_import = [
'loopback_ip',
'asn',
'role',
'position',
]
for attr in attributes_to_import:
if attr in node.keys():
self.add_var_to_host(dev_name, attr, node[attr])
# if blueprint_interface is enabled in the configuration
# Collect links information
if self.aos_blueprint_int:
interfaces = dict()
for link in bp.contents['system']['links']:
# each link has 2 sides [0,1], and it's unknown which one match this device
# at first we assume, first side match(0) and peer is (1)
peer_id = 1
for side in link['endpoints']:
if side['display_name'] == dev_name:
# import local information first
int_name = side['interface']
# init dict
interfaces[int_name] = dict()
if 'ip' in side.keys():
interfaces[int_name]['ip'] = side['ip']
if 'interface' in side.keys():
interfaces[int_name]['name'] = side['interface']
if 'display_name' in link['endpoints'][peer_id].keys():
interfaces[int_name]['peer'] = link['endpoints'][peer_id]['display_name']
if 'ip' in link['endpoints'][peer_id].keys():
interfaces[int_name]['peer_ip'] = link['endpoints'][peer_id]['ip']
if 'type' in link['endpoints'][peer_id].keys():
interfaces[int_name]['peer_type'] = link['endpoints'][peer_id]['type']
else:
# if we haven't match the first time, prepare the peer_id
# for the second loop iteration
peer_id = 0
self.add_var_to_host(dev_name, 'interfaces', interfaces)
else:
for device in aos.Devices:
# If not reacheable, create by key and
# If reacheable, create by hostname
self.add_host_to_group('all', device.name)
# populate information for this host
self.add_device_status_to_var(device.name, device)
if 'user_config' in device.value.keys():
for key, value in device.value['user_config'].items():
self.add_var_to_host(device.name, key, value)
# Based on device status online|offline, collect facts as well
if device.value['status']['comm_state'] == 'on':
if 'facts' in device.value.keys():
self.add_device_facts_to_var(device.name, device)
# Check if device is associated with a blueprint
# if it's create a new group
if 'blueprint_active' in device.value['status'].keys():
if 'blueprint_id' in device.value['status'].keys():
bp = aos.Blueprints.find(uid=device.value['status']['blueprint_id'])
if bp:
self.add_host_to_group(bp.name, device.name)
# ----------------------------------------------------
# Convert the inventory and return a JSON String
# ----------------------------------------------------
data_to_print = ""
data_to_print += self.json_format_dict(self.inventory, True)
print(data_to_print)
def read_settings(self):
""" Reads the settings from the apstra_aos.ini file """
config = configparser.ConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/apstra_aos.ini')
# Default Values
self.aos_blueprint = False
self.aos_blueprint_int = True
self.aos_username = 'admin'
self.aos_password = 'admin'
self.aos_server_port = 8888
# Try to reach all parameters from File, if not available try from ENV
try:
self.aos_server = config.get('aos', 'aos_server')
except:
if 'AOS_SERVER' in os.environ.keys():
self.aos_server = os.environ['AOS_SERVER']
pass
try:
self.aos_server_port = config.get('aos', 'port')
except:
if 'AOS_PORT' in os.environ.keys():
self.aos_server_port = os.environ['AOS_PORT']
pass
try:
self.aos_username = config.get('aos', 'username')
except:
if 'AOS_USERNAME' in os.environ.keys():
self.aos_username = os.environ['AOS_USERNAME']
pass
try:
self.aos_password = config.get('aos', 'password')
except:
if 'AOS_PASSWORD' in os.environ.keys():
self.aos_password = os.environ['AOS_PASSWORD']
pass
try:
self.aos_blueprint = config.get('aos', 'blueprint')
except:
if 'AOS_BLUEPRINT' in os.environ.keys():
self.aos_blueprint = os.environ['AOS_BLUEPRINT']
pass
try:
if config.get('aos', 'blueprint_interface') in ['false', 'no']:
self.aos_blueprint_int = False
except:
pass
def parse_cli_args(self):
""" Command line argument processing """
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Apstra AOS')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
self.args = parser.parse_args()
def json_format_dict(self, data, pretty=False):
""" Converts a dict to a JSON object and dumps it as a formatted string """
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
def add_host_to_group(self, group, host):
# Cleanup group name first
clean_group = self.cleanup_group_name(group)
# Check if the group exist, if not initialize it
if clean_group not in self.inventory.keys():
self.inventory[clean_group] = {}
self.inventory[clean_group]['hosts'] = []
self.inventory[clean_group]['vars'] = {}
self.inventory[clean_group]['hosts'].append(host)
def add_var_to_host(self, host, var, value):
# Check if the host exist, if not initialize it
if host not in self.inventory['_meta']['hostvars'].keys():
self.inventory['_meta']['hostvars'][host] = {}
self.inventory['_meta']['hostvars'][host][var] = value
def add_var_to_group(self, group, var, value):
# Cleanup group name first
clean_group = self.cleanup_group_name(group)
# Check if the group exist, if not initialize it
if clean_group not in self.inventory.keys():
self.inventory[clean_group] = {}
self.inventory[clean_group]['hosts'] = []
self.inventory[clean_group]['vars'] = {}
self.inventory[clean_group]['vars'][var] = value
def add_device_facts_to_var(self, device_name, device):
# Populate variables for this host
self.add_var_to_host(device_name,
'ansible_ssh_host',
device.value['facts']['mgmt_ipaddr'])
self.add_var_to_host(device_name, 'id', device.id)
# self.add_host_to_group('all', device.name)
for key, value in device.value['facts'].items():
self.add_var_to_host(device_name, key, value)
if key == 'os_family':
self.add_host_to_group(value, device_name)
elif key == 'hw_model':
self.add_host_to_group(value, device_name)
def cleanup_group_name(self, group_name):
"""
Clean up group name by :
- Replacing all non-alphanumeric caracter by underscore
- Converting to lowercase
"""
rx = re.compile('\W+')
clean_group = rx.sub('_', group_name).lower()
return clean_group
def add_device_status_to_var(self, device_name, device):
if 'status' in device.value.keys():
for key, value in device.value['status'].items():
self.add_var_to_host(device.name, key, value)
# Run the script
if __name__ == '__main__':
AosInventory()

View File

@ -1,22 +0,0 @@
#
# Configuration file for azure_rm.py
#
[azure]
# Control which resource groups are included. By default all resources groups are included.
# Set resource_groups to a comma separated list of resource groups names.
#resource_groups=
# 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
# Control grouping with the following boolean flags. Valid values: yes, no, true, false, True, False, 0, 1.
group_by_resource_group=yes
group_by_location=yes
group_by_security_group=yes
group_by_tag=yes

View File

@ -1,859 +0,0 @@
#!/usr/bin/env python
#
# Copyright (c) 2016 Matt Davis, <mdavis@ansible.com>
# Chris Houseknecht, <house@redhat.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/>.
#
'''
Azure External Inventory Script
===============================
Generates dynamic inventory by making API requests to the Azure Resource
Manager using the Azure Python SDK. For instruction on installing the
Azure Python SDK see http://azure-sdk-for-python.readthedocs.org/
Authentication
--------------
The order of precedence is command line arguments, environment variables,
and finally the [default] profile found in ~/.azure/credentials.
If using a credentials file, it should be an ini formatted file with one or
more sections, which we refer to as profiles. The script looks for a
[default] section, if a profile is not specified either on the command line
or with an environment variable. The keys in a profile will match the
list of command line arguments below.
For command line arguments and environment variables specify a profile found
in your ~/.azure/credentials file, or a service principal or Active Directory
user.
Command line arguments:
- profile
- client_id
- secret
- subscription_id
- tenant
- ad_user
- password
- cloud_environment
Environment variables:
- AZURE_PROFILE
- AZURE_CLIENT_ID
- AZURE_SECRET
- AZURE_SUBSCRIPTION_ID
- AZURE_TENANT
- AZURE_AD_USER
- AZURE_PASSWORD
- AZURE_CLOUD_ENVIRONMENT
Run for Specific Host
-----------------------
When run for a specific host using the --host option, a resource group is
required. For a specific host, this script returns the following variables:
{
"ansible_host": "XXX.XXX.XXX.XXX",
"computer_name": "computer_name2",
"fqdn": null,
"id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Compute/virtualMachines/object-name",
"image": {
"offer": "CentOS",
"publisher": "OpenLogic",
"sku": "7.1",
"version": "latest"
},
"location": "westus",
"mac_address": "00-00-5E-00-53-FE",
"name": "object-name",
"network_interface": "interface-name",
"network_interface_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/networkInterfaces/object-name1",
"network_security_group": null,
"network_security_group_id": null,
"os_disk": {
"name": "object-name",
"operating_system_type": "Linux"
},
"plan": null,
"powerstate": "running",
"private_ip": "172.26.3.6",
"private_ip_alloc_method": "Static",
"provisioning_state": "Succeeded",
"public_ip": "XXX.XXX.XXX.XXX",
"public_ip_alloc_method": "Static",
"public_ip_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/publicIPAddresses/object-name",
"public_ip_name": "object-name",
"resource_group": "galaxy-production",
"security_group": "object-name",
"security_group_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/networkSecurityGroups/object-name",
"tags": {
"db": "database"
},
"type": "Microsoft.Compute/virtualMachines",
"virtual_machine_size": "Standard_DS4"
}
Groups
------
When run in --list mode, instances are grouped by the following categories:
- azure
- location
- resource_group
- security_group
- tag key
- tag key_value
Control groups using azure_rm.ini or set environment variables:
AZURE_GROUP_BY_RESOURCE_GROUP=yes
AZURE_GROUP_BY_LOCATION=yes
AZURE_GROUP_BY_SECURITY_GROUP=yes
AZURE_GROUP_BY_TAG=yes
Select hosts within specific resource groups by assigning a comma separated list to:
AZURE_RESOURCE_GROUPS=resource_group_a,resource_group_b
Select hosts for specific tag key by assigning a comma separated list of tag keys to:
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
If you don't need the powerstate, you can improve performance by turning off powerstate fetching:
AZURE_INCLUDE_POWERSTATE=no
azure_rm.ini
------------
As mentioned above, you can control execution using environment variables or a .ini file. A sample
azure_rm.ini is included. The name of the .ini file is the basename of the inventory script (in this case
'azure_rm') with a .ini extension. It also assumes the .ini file is alongside the script. To specify
a different path for the .ini file, define the AZURE_INI_PATH environment variable:
export AZURE_INI_PATH=/path/to/custom.ini
Powerstate:
-----------
The powerstate attribute indicates whether or not a host is running. If the value is 'running', the machine is
up. If the value is anything other than 'running', the machine is down, and will be unreachable.
Examples:
---------
Execute /bin/uname on all instances in the galaxy-qa resource group
$ 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.py --host my_instance_host_name --pretty
Use with a playbook
$ ansible-playbook -i contrib/inventory/azure_rm.py my_playbook.yml --limit galaxy-qa
Insecure Platform Warning
-------------------------
If you receive InsecurePlatformWarning from urllib3, install the
requests security packages:
pip install requests[security]
author:
- Chris Houseknecht (@chouseknecht)
- Matt Davis (@nitzmahone)
Company: Ansible by Red Hat
Version: 1.0.0
'''
import argparse
import json
import os
import re
import sys
import inspect
try:
# python2
import ConfigParser as cp
except ImportError:
# python3
import configparser as cp
from packaging.version import Version
from os.path import expanduser
import ansible.module_utils.six.moves.urllib.parse as urlparse
HAS_AZURE = True
HAS_AZURE_EXC = None
try:
from msrestazure.azure_exceptions import CloudError
from msrestazure import azure_cloud
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 import NetworkManagementClient
from azure.mgmt.resource.resources import ResourceManagementClient
from azure.mgmt.compute import ComputeManagementClient
except ImportError as exc:
HAS_AZURE_EXC = exc
HAS_AZURE = False
AZURE_CREDENTIAL_ENV_MAPPING = dict(
profile='AZURE_PROFILE',
subscription_id='AZURE_SUBSCRIPTION_ID',
client_id='AZURE_CLIENT_ID',
secret='AZURE_SECRET',
tenant='AZURE_TENANT',
ad_user='AZURE_AD_USER',
password='AZURE_PASSWORD',
cloud_environment='AZURE_CLOUD_ENVIRONMENT',
)
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',
group_by_security_group='AZURE_GROUP_BY_SECURITY_GROUP',
group_by_tag='AZURE_GROUP_BY_TAG'
)
AZURE_MIN_VERSION = "2.0.0"
def azure_id_to_dict(id):
pieces = re.sub(r'^\/', '', id).split('/')
result = {}
index = 0
while index < len(pieces) - 1:
result[pieces[index]] = pieces[index + 1]
index += 1
return result
class AzureRM(object):
def __init__(self, args):
self._args = args
self._cloud_environment = None
self._compute_client = None
self._resource_client = None
self._network_client = None
self.debug = False
if args.debug:
self.debug = True
self.credentials = self._get_credentials(args)
if not self.credentials:
self.fail("Failed to get credentials. Either pass as parameters, set environment variables, "
"or define a profile in ~/.azure/credentials.")
# if cloud_environment specified, look up/build Cloud object
raw_cloud_env = self.credentials.get('cloud_environment')
if not raw_cloud_env:
self._cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD # SDK default
else:
# try to look up "well-known" values via the name attribute on azure_cloud members
all_clouds = [x[1] for x in inspect.getmembers(azure_cloud) if isinstance(x[1], azure_cloud.Cloud)]
matched_clouds = [x for x in all_clouds if x.name == raw_cloud_env]
if len(matched_clouds) == 1:
self._cloud_environment = matched_clouds[0]
elif len(matched_clouds) > 1:
self.fail("Azure SDK failure: more than one cloud matched for cloud_environment name '{0}'".format(raw_cloud_env))
else:
if not urlparse.urlparse(raw_cloud_env).scheme:
self.fail("cloud_environment must be an endpoint discovery URL or one of {0}".format([x.name for x in all_clouds]))
try:
self._cloud_environment = azure_cloud.get_cloud_from_metadata_endpoint(raw_cloud_env)
except Exception as e:
self.fail("cloud_environment {0} could not be resolved: {1}".format(raw_cloud_env, e.message))
if self.credentials.get('subscription_id', None) is None:
self.fail("Credentials did not include a subscription_id value.")
self.log("setting subscription_id")
self.subscription_id = self.credentials['subscription_id']
if self.credentials.get('client_id') is not None and \
self.credentials.get('secret') is not None and \
self.credentials.get('tenant') is not None:
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
secret=self.credentials['secret'],
tenant=self.credentials['tenant'],
cloud_environment=self._cloud_environment)
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
tenant = self.credentials.get('tenant')
if not tenant:
tenant = 'common'
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'],
self.credentials['password'],
tenant=tenant,
cloud_environment=self._cloud_environment)
else:
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
"Credentials must include client_id, secret and tenant or ad_user and password.")
def log(self, msg):
if self.debug:
print(msg + u'\n')
def fail(self, msg):
raise Exception(msg)
def _get_profile(self, profile="default"):
path = expanduser("~")
path += "/.azure/credentials"
try:
config = cp.ConfigParser()
config.read(path)
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()
for key in AZURE_CREDENTIAL_ENV_MAPPING:
try:
credentials[key] = config.get(profile, key, raw=True)
except:
pass
if credentials.get('client_id') is not None or credentials.get('ad_user') is not None:
return credentials
return None
def _get_env_credentials(self):
env_credentials = dict()
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
env_credentials[attribute] = os.environ.get(env_variable, None)
if env_credentials['profile'] is not None:
credentials = self._get_profile(env_credentials['profile'])
return credentials
if env_credentials['client_id'] is not None or env_credentials['ad_user'] is not None:
return env_credentials
return None
def _get_credentials(self, params):
# Get authentication credentials.
# Precedence: cmd line parameters-> environment variables-> default profile in ~/.azure/credentials.
self.log('Getting credentials')
arg_credentials = dict()
for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items():
arg_credentials[attribute] = getattr(params, attribute)
# try module params
if arg_credentials['profile'] is not None:
self.log('Retrieving credentials with profile parameter.')
credentials = self._get_profile(arg_credentials['profile'])
return credentials
if arg_credentials['client_id'] is not None:
self.log('Received credentials from parameters.')
return arg_credentials
if arg_credentials['ad_user'] is not None:
self.log('Received credentials from parameters.')
return arg_credentials
# try environment
env_credentials = self._get_env_credentials()
if env_credentials:
self.log('Received credentials from env.')
return env_credentials
# try default profile from ~./azure/credentials
default_credentials = self._get_profile()
if default_credentials:
self.log('Retrieved default profile credentials from ~/.azure/credentials.')
return default_credentials
return None
def _register(self, key):
try:
# We have to perform the one-time registration here. Otherwise, we receive an error the first
# time we attempt to use the requested client.
resource_client = self.rm_client
resource_client.providers.register(key)
except Exception as exc:
self.log("One-time registration of {0} failed - {1}".format(key, str(exc)))
self.log("You might need to register {0} using an admin account".format(key))
self.log(("To register a provider using the Python CLI: "
"https://docs.microsoft.com/azure/azure-resource-manager/"
"resource-manager-common-deployment-errors#noregisteredproviderfound"))
@property
def network_client(self):
self.log('Getting network client')
if not self._network_client:
self._network_client = NetworkManagementClient(
self.azure_credentials,
self.subscription_id,
base_url=self._cloud_environment.endpoints.resource_manager,
api_version='2017-06-01'
)
self._register('Microsoft.Network')
return self._network_client
@property
def rm_client(self):
self.log('Getting resource manager client')
if not self._resource_client:
self._resource_client = ResourceManagementClient(
self.azure_credentials,
self.subscription_id,
base_url=self._cloud_environment.endpoints.resource_manager,
api_version='2017-05-10'
)
return self._resource_client
@property
def compute_client(self):
self.log('Getting compute client')
if not self._compute_client:
self._compute_client = ComputeManagementClient(
self.azure_credentials,
self.subscription_id,
base_url=self._cloud_environment.endpoints.resource_manager,
api_version='2017-03-30'
)
self._register('Microsoft.Compute')
return self._compute_client
class AzureInventory(object):
def __init__(self):
self._args = self._parse_cli_args()
try:
rm = AzureRM(self._args)
except Exception as e:
sys.exit("{0}".format(str(e)))
self._compute_client = rm.compute_client
self._network_client = rm.network_client
self._resource_client = rm.rm_client
self._security_groups = None
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
self.group_by_security_group = True
self.group_by_tag = True
self.include_powerstate = True
self._inventory = dict(
_meta=dict(
hostvars=dict()
),
azure=[]
)
self._get_settings()
if self._args.resource_groups:
self.resource_groups = self._args.resource_groups.split(',')
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
self.get_inventory()
print(self._json_format_dict(pretty=self._args.pretty))
sys.exit(0)
def _parse_cli_args(self):
# Parse command line arguments
parser = argparse.ArgumentParser(
description='Produce an Ansible Inventory file for an Azure subscription')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
parser.add_argument('--debug', action='store_true', default=False,
help='Send debug messages to STDOUT')
parser.add_argument('--host', action='store',
help='Get all information about an instance')
parser.add_argument('--pretty', action='store_true', default=False,
help='Pretty print JSON output(default: False)')
parser.add_argument('--profile', action='store',
help='Azure profile contained in ~/.azure/credentials')
parser.add_argument('--subscription_id', action='store',
help='Azure Subscription Id')
parser.add_argument('--client_id', action='store',
help='Azure Client Id ')
parser.add_argument('--secret', action='store',
help='Azure Client Secret')
parser.add_argument('--tenant', action='store',
help='Azure Tenant Id')
parser.add_argument('--ad_user', action='store',
help='Active Directory User')
parser.add_argument('--password', action='store',
help='password')
parser.add_argument('--cloud_environment', action='store',
help='Azure Cloud Environment name or metadata discovery URL')
parser.add_argument('--resource-groups', action='store',
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()
def get_inventory(self):
if len(self.resource_groups) > 0:
# get VMs for requested resource groups
for resource_group in self.resource_groups:
try:
virtual_machines = self._compute_client.virtual_machines.list(resource_group)
except Exception as exc:
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc)))
if self._args.host or self.tags:
selected_machines = self._selected_machines(virtual_machines)
self._load_machines(selected_machines)
else:
self._load_machines(virtual_machines)
else:
# get all VMs within the subscription
try:
virtual_machines = self._compute_client.virtual_machines.list_all()
except Exception as exc:
sys.exit("Error: fetching virtual machines - {0}".format(str(exc)))
if self._args.host or self.tags or self.locations:
selected_machines = self._selected_machines(virtual_machines)
self._load_machines(selected_machines)
else:
self._load_machines(virtual_machines)
def _load_machines(self, machines):
for machine in machines:
id_dict = azure_id_to_dict(machine.id)
# TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets
# fixed, we should remove the .lower(). Opened Issue
# #574: https://github.com/Azure/azure-sdk-for-python/issues/574
resource_group = id_dict['resourceGroups'].lower()
if self.group_by_security_group:
self._get_security_groups(resource_group)
host_vars = dict(
ansible_host=None,
private_ip=None,
private_ip_alloc_method=None,
public_ip=None,
public_ip_name=None,
public_ip_id=None,
public_ip_alloc_method=None,
fqdn=None,
location=machine.location,
name=machine.name,
type=machine.type,
id=machine.id,
tags=machine.tags,
network_interface_id=None,
network_interface=None,
resource_group=resource_group,
mac_address=None,
plan=(machine.plan.name if machine.plan else None),
virtual_machine_size=machine.hardware_profile.vm_size,
computer_name=(machine.os_profile.computer_name if machine.os_profile else None),
provisioning_state=machine.provisioning_state,
)
host_vars['os_disk'] = dict(
name=machine.storage_profile.os_disk.name,
operating_system_type=machine.storage_profile.os_disk.os_type.value
)
if self.include_powerstate:
host_vars['powerstate'] = self._get_powerstate(resource_group, machine.name)
if machine.storage_profile.image_reference:
host_vars['image'] = dict(
offer=machine.storage_profile.image_reference.offer,
publisher=machine.storage_profile.image_reference.publisher,
sku=machine.storage_profile.image_reference.sku,
version=machine.storage_profile.image_reference.version
)
# Add windows details
if machine.os_profile is not None and machine.os_profile.windows_configuration is not None:
host_vars['windows_auto_updates_enabled'] = \
machine.os_profile.windows_configuration.enable_automatic_updates
host_vars['windows_timezone'] = machine.os_profile.windows_configuration.time_zone
host_vars['windows_rm'] = None
if machine.os_profile.windows_configuration.win_rm is not None:
host_vars['windows_rm'] = dict(listeners=None)
if machine.os_profile.windows_configuration.win_rm.listeners is not None:
host_vars['windows_rm']['listeners'] = []
for listener in machine.os_profile.windows_configuration.win_rm.listeners:
host_vars['windows_rm']['listeners'].append(dict(protocol=listener.protocol,
certificate_url=listener.certificate_url))
for interface in machine.network_profile.network_interfaces:
interface_reference = self._parse_ref_id(interface.id)
network_interface = self._network_client.network_interfaces.get(
interface_reference['resourceGroups'],
interface_reference['networkInterfaces'])
if network_interface.primary:
if self.group_by_security_group and \
self._security_groups[resource_group].get(network_interface.id, None):
host_vars['security_group'] = \
self._security_groups[resource_group][network_interface.id]['name']
host_vars['security_group_id'] = \
self._security_groups[resource_group][network_interface.id]['id']
host_vars['network_interface'] = network_interface.name
host_vars['network_interface_id'] = network_interface.id
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
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(
public_ip_reference['resourceGroups'],
public_ip_reference['publicIPAddresses'])
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
host_vars['public_ip_id'] = public_ip_address.id
if public_ip_address.dns_settings:
host_vars['fqdn'] = public_ip_address.dns_settings.fqdn
self._add_host(host_vars)
def _selected_machines(self, virtual_machines):
selected_machines = []
for machine in virtual_machines:
if self._args.host and self._args.host == machine.name:
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):
''' For a given resource_group build a mapping of network_interface.id to security_group name '''
if not self._security_groups:
self._security_groups = dict()
if not self._security_groups.get(resource_group):
self._security_groups[resource_group] = dict()
for group in self._network_client.network_security_groups.list(resource_group):
if group.network_interfaces:
for interface in group.network_interfaces:
self._security_groups[resource_group][interface.id] = dict(
name=group.name,
id=group.id
)
def _get_powerstate(self, resource_group, name):
try:
vm = self._compute_client.virtual_machines.get(resource_group,
name,
expand='instanceview')
except Exception as exc:
sys.exit("Error: fetching instanceview for host {0} - {1}".format(name, str(exc)))
return next((s.code.replace('PowerState/', '')
for s in vm.instance_view.statuses if s.code.startswith('PowerState')), None)
def _add_host(self, vars):
host_name = self._to_safe(vars['name'])
resource_group = self._to_safe(vars['resource_group'])
security_group = None
if vars.get('security_group'):
security_group = self._to_safe(vars['security_group'])
if self.group_by_resource_group:
if not self._inventory.get(resource_group):
self._inventory[resource_group] = []
self._inventory[resource_group].append(host_name)
if self.group_by_location:
if not self._inventory.get(vars['location']):
self._inventory[vars['location']] = []
self._inventory[vars['location']].append(host_name)
if self.group_by_security_group and security_group:
if not self._inventory.get(security_group):
self._inventory[security_group] = []
self._inventory[security_group].append(host_name)
self._inventory['_meta']['hostvars'][host_name] = vars
self._inventory['azure'].append(host_name)
if self.group_by_tag and vars.get('tags'):
for key, value in vars['tags'].items():
safe_key = self._to_safe(key)
safe_value = safe_key + '_' + self._to_safe(value)
if not self._inventory.get(safe_key):
self._inventory[safe_key] = []
if not self._inventory.get(safe_value):
self._inventory[safe_value] = []
self._inventory[safe_key].append(host_name)
self._inventory[safe_value].append(host_name)
def _json_format_dict(self, pretty=False):
# convert inventory to json
if pretty:
return json.dumps(self._inventory, sort_keys=True, indent=2)
else:
return json.dumps(self._inventory)
def _get_settings(self):
# Load settings from the .ini, if it exists. Otherwise,
# look for environment values.
file_settings = self._load_settings()
if file_settings:
for key in AZURE_CONFIG_SETTINGS:
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):
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', 'locations') and env_settings.get(key):
values = env_settings.get(key).split(',')
if len(values) > 0:
setattr(self, key, values)
elif env_settings.get(key, None) is not None:
val = self._to_boolean(env_settings[key])
setattr(self, key, val)
def _parse_ref_id(self, reference):
response = {}
keys = reference.strip('/').split('/')
for index in range(len(keys)):
if index < len(keys) - 1 and index % 2 == 0:
response[keys[index]] = keys[index + 1]
return response
def _to_boolean(self, value):
if value in ['Yes', 'yes', 1, 'True', 'true', True]:
result = True
elif value in ['No', 'no', 0, 'False', 'false', False]:
result = False
else:
result = True
return result
def _get_env_settings(self):
env_settings = dict()
for attribute, env_variable in AZURE_CONFIG_SETTINGS.items():
env_settings[attribute] = os.environ.get(env_variable, None)
return env_settings
def _load_settings(self):
basename = os.path.splitext(os.path.basename(__file__))[0]
default_path = os.path.join(os.path.dirname(__file__), (basename + '.ini'))
path = os.path.expanduser(os.path.expandvars(os.environ.get('AZURE_INI_PATH', default_path)))
config = None
settings = None
try:
config = cp.ConfigParser()
config.read(path)
except:
pass
if config is not None:
settings = dict()
for key in AZURE_CONFIG_SETTINGS:
try:
settings[key] = config.get('azure', key, raw=True)
except:
pass
return settings
def _tags_match(self, tag_obj, tag_args):
'''
Return True if the tags object from a VM contains the requested tag values.
:param tag_obj: Dictionary of string:string pairs
:param tag_args: List of strings in the form key=value
:return: boolean
'''
if not tag_obj:
return False
matches = 0
for arg in tag_args:
arg_key = arg
arg_value = None
if re.search(r':', arg):
arg_key, arg_value = arg.split(':')
if arg_value and tag_obj.get(arg_key, None) == arg_value:
matches += 1
elif not arg_value and tag_obj.get(arg_key, None) is not None:
matches += 1
if matches == len(tag_args):
return True
return False
def _to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
regex = "[^A-Za-z0-9\_"
if not self.replace_dash_in_groups:
regex += "\-"
return re.sub(regex + "]", "_", word)
def main():
if not HAS_AZURE:
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>={0}' --upgrade`) - {1}".format(AZURE_MIN_VERSION, HAS_AZURE_EXC))
AzureInventory()
if __name__ == '__main__':
main()

View File

@ -1,39 +0,0 @@
# Copyright 2016 Doalitic.
#
# 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/>.
# The Brook.io inventory script has the following dependencies:
# 1. A working Brook.io account
# See https://brook.io
# 2. A valid token generated through the 'API token' panel of Brook.io
# 3. The libbrook python libray.
# See https://github.com/doalitic/libbrook
#
# Author: Francisco Ros <fjros@doalitic.com>
[brook]
# Valid API token (required).
# E.g. 'Aed342a12A60433697281FeEe1a4037C'
#
api_token =
# Project id within Brook.io, as obtained from the project settings (optional). If provided, the
# generated inventory will just include the hosts that belong to such project. Otherwise, it will
# include all hosts in projects the requesting user has access to. The response includes groups
# 'project_x', being 'x' the project name.
# E.g. '2e8e099e1bc34cc0979d97ac34e9577b'
#
project_id =

View File

@ -1,264 +0,0 @@
#!/usr/bin/env python
# Copyright 2016 Doalitic.
#
# 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/>.
"""
Brook.io external inventory script
==================================
Generates inventory that Ansible can understand by making API requests to Brook.io via the libbrook
library. Hence, such dependency must be installed in the system to run this script.
The default configuration file is named 'brook.ini' and is located alongside this script. You can
choose any other file by setting the BROOK_INI_PATH environment variable.
If param 'project_id' is left blank in 'brook.ini', the inventory includes all the instances in
projects where the requesting user belongs. Otherwise, only instances from the given project are
included, provided the requesting user belongs to it.
The following variables are established for every host. They can be retrieved from the hostvars
dictionary.
- brook_pid: str
- brook_name: str
- brook_description: str
- brook_project: str
- brook_template: str
- brook_region: str
- brook_zone: str
- brook_status: str
- brook_tags: list(str)
- brook_internal_ips: list(str)
- brook_external_ips: list(str)
- brook_created_at
- brook_updated_at
- ansible_ssh_host
Instances are grouped by the following categories:
- tag:
A group is created for each tag. E.g. groups 'tag_foo' and 'tag_bar' are created if there exist
instances with tags 'foo' and/or 'bar'.
- project:
A group is created for each project. E.g. group 'project_test' is created if a project named
'test' exist.
- status:
A group is created for each instance state. E.g. groups 'status_RUNNING' and 'status_PENDING'
are created if there are instances in running and pending state.
Examples:
Execute uname on all instances in project 'test'
$ ansible -i brook.py project_test -m shell -a "/bin/uname -a"
Install nginx on all debian web servers tagged with 'www'
$ ansible -i brook.py tag_www -m apt -a "name=nginx state=present"
Run site.yml playbook on web servers
$ ansible-playbook -i brook.py site.yml -l tag_www
Support:
This script is tested on Python 2.7 and 3.4. It may work on other versions though.
Author: Francisco Ros <fjros@doalitic.com>
Version: 0.2
"""
import sys
import os
try:
from ConfigParser import SafeConfigParser as ConfigParser
except ImportError:
from configparser import ConfigParser
try:
import json
except ImportError:
import simplejson as json
try:
import libbrook
except:
sys.exit('Brook.io inventory script requires libbrook. See https://github.com/doalitic/libbrook')
class BrookInventory:
_API_ENDPOINT = 'https://api.brook.io'
def __init__(self):
self._configure_from_file()
self.client = self.get_api_client()
self.inventory = self.get_inventory()
def _configure_from_file(self):
"""Initialize from .ini file.
Configuration file is assumed to be named 'brook.ini' and to be located on the same
directory than this file, unless the environment variable BROOK_INI_PATH says otherwise.
"""
brook_ini_default_path = \
os.path.join(os.path.dirname(os.path.realpath(__file__)), 'brook.ini')
brook_ini_path = os.environ.get('BROOK_INI_PATH', brook_ini_default_path)
config = ConfigParser(defaults={
'api_token': '',
'project_id': ''
})
config.read(brook_ini_path)
self.api_token = config.get('brook', 'api_token')
self.project_id = config.get('brook', 'project_id')
if not self.api_token:
sys.exit('You must provide (at least) your Brook.io API token to generate the dynamic '
'inventory.')
def get_api_client(self):
"""Authenticate user via the provided credentials and return the corresponding API client.
"""
# Get JWT token from API token
#
unauthenticated_client = libbrook.ApiClient(host=self._API_ENDPOINT)
auth_api = libbrook.AuthApi(unauthenticated_client)
api_token = libbrook.AuthTokenRequest()
api_token.token = self.api_token
jwt = auth_api.auth_token(token=api_token)
# Create authenticated API client
#
return libbrook.ApiClient(host=self._API_ENDPOINT,
header_name='Authorization',
header_value='Bearer %s' % jwt.token)
def get_inventory(self):
"""Generate Ansible inventory.
"""
groups = dict()
meta = dict()
meta['hostvars'] = dict()
instances_api = libbrook.InstancesApi(self.client)
projects_api = libbrook.ProjectsApi(self.client)
templates_api = libbrook.TemplatesApi(self.client)
# If no project is given, get all projects the requesting user has access to
#
if not self.project_id:
projects = [project.id for project in projects_api.index_projects()]
else:
projects = [self.project_id]
# Build inventory from instances in all projects
#
for project_id in projects:
project = projects_api.show_project(project_id=project_id)
for instance in instances_api.index_instances(project_id=project_id):
# Get template used for this instance if known
template = templates_api.show_template(template_id=instance.template) if instance.template else None
# Update hostvars
try:
meta['hostvars'][instance.name] = \
self.hostvars(project, instance, template, instances_api)
except libbrook.rest.ApiException:
continue
# Group by project
project_group = 'project_%s' % project.name
if project_group in groups:
groups[project_group].append(instance.name)
else:
groups[project_group] = [instance.name]
# Group by status
status_group = 'status_%s' % meta['hostvars'][instance.name]['brook_status']
if status_group in groups:
groups[status_group].append(instance.name)
else:
groups[status_group] = [instance.name]
# Group by tags
tags = meta['hostvars'][instance.name]['brook_tags']
for tag in tags:
tag_group = 'tag_%s' % tag
if tag_group in groups:
groups[tag_group].append(instance.name)
else:
groups[tag_group] = [instance.name]
groups['_meta'] = meta
return groups
def hostvars(self, project, instance, template, api):
"""Return the hostvars dictionary for the given instance.
Raise libbrook.rest.ApiException if it cannot retrieve all required information from the
Brook.io API.
"""
hostvars = instance.to_dict()
hostvars['brook_pid'] = hostvars.pop('pid')
hostvars['brook_name'] = hostvars.pop('name')
hostvars['brook_description'] = hostvars.pop('description')
hostvars['brook_project'] = hostvars.pop('project')
hostvars['brook_template'] = hostvars.pop('template')
hostvars['brook_region'] = hostvars.pop('region')
hostvars['brook_zone'] = hostvars.pop('zone')
hostvars['brook_created_at'] = hostvars.pop('created_at')
hostvars['brook_updated_at'] = hostvars.pop('updated_at')
del hostvars['id']
del hostvars['key']
del hostvars['provider']
del hostvars['image']
# Substitute identifiers for names
#
hostvars['brook_project'] = project.name
hostvars['brook_template'] = template.name if template else None
# Retrieve instance state
#
status = api.status_instance(project_id=project.id, instance_id=instance.id)
hostvars.update({'brook_status': status.state})
# Retrieve instance tags
#
tags = api.instance_tags(project_id=project.id, instance_id=instance.id)
hostvars.update({'brook_tags': tags})
# Retrieve instance addresses
#
addresses = api.instance_addresses(project_id=project.id, instance_id=instance.id)
internal_ips = [address.address for address in addresses if address.scope == 'internal']
external_ips = [address.address for address in addresses
if address.address and address.scope == 'external']
hostvars.update({'brook_internal_ips': internal_ips})
hostvars.update({'brook_external_ips': external_ips})
try:
hostvars.update({'ansible_ssh_host': external_ips[0]})
except IndexError:
raise libbrook.rest.ApiException(status='502', reason='Instance without public IP')
return hostvars
# Run the script
#
brook = BrookInventory()
print(json.dumps(brook.inventory))

View File

@ -1,33 +0,0 @@
[cloudforms]
# the version of CloudForms ; currently not used, but tested with
version = 4.1
# This should be the hostname of the CloudForms server
url = https://cfme.example.com
# This will more than likely need to be a local CloudForms username
username = <set your username here>
# The password for said username
password = <set your password here>
# True = verify SSL certificate / False = trust anything
ssl_verify = True
# limit the number of vms returned per request
limit = 100
# purge the CloudForms actions from hosts
purge_actions = True
# Clean up group names (from tags and other groupings so Ansible doesn't complain)
clean_group_keys = True
# Explode tags into nested groups / subgroups
nest_tags = False
[cache]
# Maximum time to trust the cache in seconds
max_age = 600

View File

@ -1,461 +0,0 @@
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
#
# Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>
#
# This script 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 it. If not, see <http://www.gnu.org/licenses/>.
#
# This is loosely based on the foreman inventory script
# -- Josh Preston <jpreston@redhat.com>
#
from __future__ import print_function
import argparse
import ConfigParser
import os
import re
from time import time
import requests
from requests.auth import HTTPBasicAuth
import warnings
try:
import json
except ImportError:
import simplejson as json
class CloudFormsInventory(object):
def __init__(self):
"""
Main execution path
"""
self.inventory = dict() # A list of groups and the hosts in that group
self.hosts = dict() # Details about hosts in the inventory
# Parse CLI arguments
self.parse_cli_args()
# Read settings
self.read_settings()
# Cache
if self.args.refresh_cache or not self.is_cache_valid():
self.update_cache()
else:
self.load_inventory_from_cache()
self.load_hosts_from_cache()
data_to_print = ""
# Data to print
if self.args.host:
if self.args.debug:
print("Fetching host [%s]" % self.args.host)
data_to_print += self.get_host_info(self.args.host)
else:
self.inventory['_meta'] = {'hostvars': {}}
for hostname in self.hosts:
self.inventory['_meta']['hostvars'][hostname] = {
'cloudforms': self.hosts[hostname],
}
# include the ansible_ssh_host in the top level
if 'ansible_ssh_host' in self.hosts[hostname]:
self.inventory['_meta']['hostvars'][hostname]['ansible_ssh_host'] = self.hosts[hostname]['ansible_ssh_host']
data_to_print += self.json_format_dict(self.inventory, self.args.pretty)
print(data_to_print)
def is_cache_valid(self):
"""
Determines if the cache files have expired, or if it is still valid
"""
if self.args.debug:
print("Determining if cache [%s] is still valid (< %s seconds old)" % (self.cache_path_hosts, self.cache_max_age))
if os.path.isfile(self.cache_path_hosts):
mod_time = os.path.getmtime(self.cache_path_hosts)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if os.path.isfile(self.cache_path_inventory):
if self.args.debug:
print("Cache is still valid!")
return True
if self.args.debug:
print("Cache is stale or does not exist.")
return False
def read_settings(self):
"""
Reads the settings from the cloudforms.ini file
"""
config = ConfigParser.SafeConfigParser()
config_paths = [
os.path.dirname(os.path.realpath(__file__)) + '/cloudforms.ini',
"/etc/ansible/cloudforms.ini",
]
env_value = os.environ.get('CLOUDFORMS_INI_PATH')
if env_value is not None:
config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))
if self.args.debug:
for config_path in config_paths:
print("Reading from configuration file [%s]" % config_path)
config.read(config_paths)
# CloudForms API related
if config.has_option('cloudforms', 'url'):
self.cloudforms_url = config.get('cloudforms', 'url')
else:
self.cloudforms_url = None
if not self.cloudforms_url:
warnings.warn("No url specified, expected something like 'https://cfme.example.com'")
if config.has_option('cloudforms', 'username'):
self.cloudforms_username = config.get('cloudforms', 'username')
else:
self.cloudforms_username = None
if not self.cloudforms_username:
warnings.warn("No username specified, you need to specify a CloudForms username.")
if config.has_option('cloudforms', 'password'):
self.cloudforms_pw = config.get('cloudforms', 'password')
else:
self.cloudforms_pw = None
if not self.cloudforms_pw:
warnings.warn("No password specified, you need to specify a password for the CloudForms user.")
if config.has_option('cloudforms', 'ssl_verify'):
self.cloudforms_ssl_verify = config.getboolean('cloudforms', 'ssl_verify')
else:
self.cloudforms_ssl_verify = True
if config.has_option('cloudforms', 'version'):
self.cloudforms_version = config.get('cloudforms', 'version')
else:
self.cloudforms_version = None
if config.has_option('cloudforms', 'limit'):
self.cloudforms_limit = config.getint('cloudforms', 'limit')
else:
self.cloudforms_limit = 100
if config.has_option('cloudforms', 'purge_actions'):
self.cloudforms_purge_actions = config.getboolean('cloudforms', 'purge_actions')
else:
self.cloudforms_purge_actions = True
if config.has_option('cloudforms', 'clean_group_keys'):
self.cloudforms_clean_group_keys = config.getboolean('cloudforms', 'clean_group_keys')
else:
self.cloudforms_clean_group_keys = True
if config.has_option('cloudforms', 'nest_tags'):
self.cloudforms_nest_tags = config.getboolean('cloudforms', 'nest_tags')
else:
self.cloudforms_nest_tags = False
# Ansible related
try:
group_patterns = config.get('ansible', 'group_patterns')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
group_patterns = "[]"
self.group_patterns = eval(group_patterns)
# Cache related
try:
cache_path = os.path.expanduser(config.get('cache', 'path'))
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
cache_path = '.'
(script, ext) = os.path.splitext(os.path.basename(__file__))
self.cache_path_hosts = cache_path + "/%s.hosts" % script
self.cache_path_inventory = cache_path + "/%s.inventory" % script
self.cache_max_age = config.getint('cache', 'max_age')
if self.args.debug:
print("CloudForms settings:")
print("cloudforms_url = %s" % self.cloudforms_url)
print("cloudforms_username = %s" % self.cloudforms_username)
print("cloudforms_pw = %s" % self.cloudforms_pw)
print("cloudforms_ssl_verify = %s" % self.cloudforms_ssl_verify)
print("cloudforms_version = %s" % self.cloudforms_version)
print("cloudforms_limit = %s" % self.cloudforms_limit)
print("cloudforms_purge_actions = %s" % self.cloudforms_purge_actions)
print("Cache settings:")
print("cache_max_age = %s" % self.cache_max_age)
print("cache_path_hosts = %s" % self.cache_path_hosts)
print("cache_path_inventory = %s" % self.cache_path_inventory)
def parse_cli_args(self):
"""
Command line argument processing
"""
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on CloudForms managed VMs')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty print JSON output (default: False)')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to CloudForms (default: False - use cache files)')
parser.add_argument('--debug', action='store_true', default=False, help='Show debug output while running (default: False)')
self.args = parser.parse_args()
def _get_json(self, url):
"""
Make a request and return the JSON
"""
results = []
ret = requests.get(url,
auth=HTTPBasicAuth(self.cloudforms_username, self.cloudforms_pw),
verify=self.cloudforms_ssl_verify)
ret.raise_for_status()
try:
results = json.loads(ret.text)
except ValueError:
warnings.warn("Unexpected response from {0} ({1}): {2}".format(self.cloudforms_url, ret.status_code, ret.reason))
results = {}
if self.args.debug:
print("=======================================================================")
print("=======================================================================")
print("=======================================================================")
print(ret.text)
print("=======================================================================")
print("=======================================================================")
print("=======================================================================")
return results
def _get_hosts(self):
"""
Get all hosts by paging through the results
"""
limit = self.cloudforms_limit
page = 0
last_page = False
results = []
while not last_page:
offset = page * limit
ret = self._get_json("%s/api/vms?offset=%s&limit=%s&expand=resources,tags,hosts,&attributes=ipaddresses" % (self.cloudforms_url, offset, limit))
results += ret['resources']
if ret['subcount'] < limit:
last_page = True
page += 1
return results
def update_cache(self):
"""
Make calls to cloudforms and save the output in a cache
"""
self.groups = dict()
self.hosts = dict()
if self.args.debug:
print("Updating cache...")
for host in self._get_hosts():
# Ignore VMs that are not powered on
if host['power_state'] != 'on':
if self.args.debug:
print("Skipping %s because power_state = %s" % (host['name'], host['power_state']))
continue
# purge actions
if self.cloudforms_purge_actions and 'actions' in host:
del host['actions']
# Create ansible groups for tags
if 'tags' in host:
# Create top-level group
if 'tags' not in self.inventory:
self.inventory['tags'] = dict(children=[], vars={}, hosts=[])
if not self.cloudforms_nest_tags:
# don't expand tags, just use them in a safe way
for group in host['tags']:
# Add sub-group, as a child of top-level
safe_key = self.to_safe(group['name'])
if safe_key:
if self.args.debug:
print("Adding sub-group '%s' to parent 'tags'" % safe_key)
if safe_key not in self.inventory['tags']['children']:
self.push(self.inventory['tags'], 'children', safe_key)
self.push(self.inventory, safe_key, host['name'])
if self.args.debug:
print("Found tag [%s] for host which will be mapped to [%s]" % (group['name'], safe_key))
else:
# expand the tags into nested groups / sub-groups
# Create nested groups for tags
safe_parent_tag_name = 'tags'
for tag in host['tags']:
tag_hierarchy = tag['name'][1:].split('/')
if self.args.debug:
print("Working on list %s" % tag_hierarchy)
for tag_name in tag_hierarchy:
if self.args.debug:
print("Working on tag_name = %s" % tag_name)
safe_tag_name = self.to_safe(tag_name)
if self.args.debug:
print("Using sanitized name %s" % safe_tag_name)
# Create sub-group
if safe_tag_name not in self.inventory:
self.inventory[safe_tag_name] = dict(children=[], vars={}, hosts=[])
# Add sub-group, as a child of top-level
if safe_parent_tag_name:
if self.args.debug:
print("Adding sub-group '%s' to parent '%s'" % (safe_tag_name, safe_parent_tag_name))
if safe_tag_name not in self.inventory[safe_parent_tag_name]['children']:
self.push(self.inventory[safe_parent_tag_name], 'children', safe_tag_name)
# Make sure the next one uses this one as it's parent
safe_parent_tag_name = safe_tag_name
# Add the host to the last tag
self.push(self.inventory[safe_parent_tag_name], 'hosts', host['name'])
# Set ansible_ssh_host to the first available ip address
if 'ipaddresses' in host and host['ipaddresses'] and isinstance(host['ipaddresses'], list):
host['ansible_ssh_host'] = host['ipaddresses'][0]
# Create additional groups
for key in ('location', 'type', 'vendor'):
safe_key = self.to_safe(host[key])
# Create top-level group
if key not in self.inventory:
self.inventory[key] = dict(children=[], vars={}, hosts=[])
# Create sub-group
if safe_key not in self.inventory:
self.inventory[safe_key] = dict(children=[], vars={}, hosts=[])
# Add sub-group, as a child of top-level
if safe_key not in self.inventory[key]['children']:
self.push(self.inventory[key], 'children', safe_key)
if key in host:
# Add host to sub-group
self.push(self.inventory[safe_key], 'hosts', host['name'])
self.hosts[host['name']] = host
self.push(self.inventory, 'all', host['name'])
if self.args.debug:
print("Saving cached data")
self.write_to_cache(self.hosts, self.cache_path_hosts)
self.write_to_cache(self.inventory, self.cache_path_inventory)
def get_host_info(self, host):
"""
Get variables about a specific host
"""
if not self.hosts or len(self.hosts) == 0:
# Need to load cache from cache
self.load_hosts_from_cache()
if host not in self.hosts:
if self.args.debug:
print("[%s] not found in cache." % host)
# try updating the cache
self.update_cache()
if host not in self.hosts:
if self.args.debug:
print("[%s] does not exist after cache update." % host)
# host might not exist anymore
return self.json_format_dict({}, self.args.pretty)
return self.json_format_dict(self.hosts[host], self.args.pretty)
def push(self, d, k, v):
"""
Safely puts a new entry onto an array.
"""
if k in d:
d[k].append(v)
else:
d[k] = [v]
def load_inventory_from_cache(self):
"""
Reads the inventory from the cache file sets self.inventory
"""
cache = open(self.cache_path_inventory, 'r')
json_inventory = cache.read()
self.inventory = json.loads(json_inventory)
def load_hosts_from_cache(self):
"""
Reads the cache from the cache file sets self.hosts
"""
cache = open(self.cache_path_hosts, 'r')
json_cache = cache.read()
self.hosts = json.loads(json_cache)
def write_to_cache(self, data, filename):
"""
Writes data in JSON format to a file
"""
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def to_safe(self, word):
"""
Converts 'bad' characters in a string to underscores so they can be used as Ansible groups
"""
if self.cloudforms_clean_group_keys:
regex = "[^A-Za-z0-9\_]"
return re.sub(regex, "_", word.replace(" ", ""))
else:
return word
def json_format_dict(self, data, pretty=False):
"""
Converts a dict to a JSON object and dumps it as a formatted string
"""
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
CloudFormsInventory()

View File

@ -67,11 +67,12 @@ based on the data obtained from CloudStack API:
}
usage: cloudstack.py [--list] [--host HOST] [--project PROJECT] [--domain DOMAIN]
usage: cloudstack.py [--list] [--host HOST] [--project PROJECT]
"""
from __future__ import print_function
import os
import sys
import argparse
@ -96,44 +97,32 @@ class CloudStackInventory(object):
parser.add_argument('--host')
parser.add_argument('--list', action='store_true')
parser.add_argument('--project')
parser.add_argument('--domain')
options = parser.parse_args()
try:
self.cs = CloudStack(**read_config())
except CloudStackException:
except CloudStackException as e:
print("Error: Could not connect to CloudStack API", file=sys.stderr)
domain_id = None
if options.domain:
domain_id = self.get_domain_id(options.domain)
project_id = None
project_id = ''
if options.project:
project_id = self.get_project_id(options.project, domain_id)
project_id = self.get_project_id(options.project)
if options.host:
data = self.get_host(options.host, project_id, domain_id)
data = self.get_host(options.host)
print(json.dumps(data, indent=2))
elif options.list:
data = self.get_list(project_id, domain_id)
data = self.get_list()
print(json.dumps(data, indent=2))
else:
print("usage: --list | --host <hostname> [--project <project>] [--domain <domain_path>]", file=sys.stderr)
print("usage: --list | --host <hostname> [--project <project>]",
file=sys.stderr)
sys.exit(1)
def get_domain_id(self, domain):
domains = self.cs.listDomains(listall=True)
if domains:
for d in domains['domain']:
if d['path'].lower() == domain.lower():
return d['id']
print("Error: Domain %s not found." % domain, file=sys.stderr)
sys.exit(1)
def get_project_id(self, project, domain_id=None):
projects = self.cs.listProjects(domainid=domain_id)
def get_project_id(self, project):
projects = self.cs.listProjects()
if projects:
for p in projects['project']:
if p['name'] == project or p['id'] == project:
@ -141,8 +130,9 @@ class CloudStackInventory(object):
print("Error: Project %s not found." % project, file=sys.stderr)
sys.exit(1)
def get_host(self, name, project_id=None, domain_id=None):
hosts = self.cs.listVirtualMachines(projectid=project_id, domainid=domain_id)
def get_host(self, name, project_id=''):
hosts = self.cs.listVirtualMachines(projectid=project_id)
data = {}
if not hosts:
return data
@ -175,45 +165,37 @@ class CloudStackInventory(object):
})
if nic['isdefault']:
data['default_ip'] = nic['ipaddress']
break
break;
return data
def get_list(self, project_id=None, domain_id=None):
def get_list(self, project_id=''):
data = {
'all': {
'hosts': [],
},
},
'_meta': {
'hostvars': {},
},
}
},
}
groups = self.cs.listInstanceGroups(projectid=project_id, domainid=domain_id)
groups = self.cs.listInstanceGroups(projectid=project_id)
if groups:
for group in groups['instancegroup']:
group_name = group['name']
if group_name and group_name not in data:
if group_name and not group_name in data:
data[group_name] = {
'hosts': []
}
'hosts': []
}
hosts = self.cs.listVirtualMachines(projectid=project_id, domainid=domain_id)
hosts = self.cs.listVirtualMachines(projectid=project_id)
if not hosts:
return data
for host in hosts['virtualmachine']:
host_name = host['displayname']
data['all']['hosts'].append(host_name)
data['_meta']['hostvars'][host_name] = {}
# Make a group per zone
data['_meta']['hostvars'][host_name]['zone'] = host['zonename']
group_name = host['zonename']
if group_name not in data:
data[group_name] = {
'hosts': []
}
data[group_name]['hosts'].append(host_name)
if 'group' in host:
data['_meta']['hostvars'][host_name]['group'] = host['group']
data['_meta']['hostvars'][host_name]['state'] = host['state']
@ -237,7 +219,7 @@ class CloudStackInventory(object):
'netmask': nic['netmask'],
'gateway': nic['gateway'],
'type': nic['type'],
})
})
if nic['isdefault']:
data['_meta']['hostvars'][host_name]['default_ip'] = nic['ipaddress']

View File

@ -5,10 +5,6 @@
host = http://PATH_TO_COBBLER_SERVER/cobbler_api
# If API needs authentication add 'username' and 'password' options here.
#username = foo
#password = bar
# API calls to Cobbler can be slow. For this reason, we cache the results of an API
# call. Set this to the path you want cache files to be written to. Two files
# will be written to this directory:

View File

@ -110,9 +110,9 @@ class CobblerInventory(object):
if self.args.host:
data_to_print += self.get_host_info()
else:
self.inventory['_meta'] = {'hostvars': {}}
self.inventory['_meta'] = { 'hostvars': {} }
for hostname in self.cache:
self.inventory['_meta']['hostvars'][hostname] = {'cobbler': self.cache[hostname]}
self.inventory['_meta']['hostvars'][hostname] = {'cobbler': self.cache[hostname] }
data_to_print += self.json_format_dict(self.inventory, True)
print(data_to_print)
@ -120,9 +120,6 @@ class CobblerInventory(object):
def _connect(self):
if not self.conn:
self.conn = xmlrpclib.Server(self.cobbler_host, allow_none=True)
self.token = None
if self.cobbler_username is not None:
self.token = self.conn.login(self.cobbler_username, self.cobbler_password)
def is_cache_valid(self):
""" Determines if the cache files have expired, or if it is still valid """
@ -143,12 +140,6 @@ class CobblerInventory(object):
config.read(os.path.dirname(os.path.realpath(__file__)) + '/cobbler.ini')
self.cobbler_host = config.get('cobbler', 'host')
self.cobbler_username = None
self.cobbler_password = None
if config.has_option('cobbler', 'username'):
self.cobbler_username = config.get('cobbler', 'username')
if config.has_option('cobbler', 'password'):
self.cobbler_password = config.get('cobbler', 'password')
# Cache related
cache_path = config.get('cobbler', 'cache_path')
@ -172,14 +163,12 @@ class CobblerInventory(object):
self._connect()
self.groups = dict()
self.hosts = dict()
if self.token is not None:
data = self.conn.get_systems(self.token)
else:
data = self.conn.get_systems()
data = self.conn.get_systems()
for host in data:
# Get the FQDN for the host and add it to the right groups
dns_name = host['hostname'] # None
dns_name = host['hostname'] #None
ksmeta = None
interfaces = host['interfaces']
# hostname is often empty for non-static IP hosts
@ -190,7 +179,7 @@ class CobblerInventory(object):
if this_dns_name is not None and this_dns_name is not "":
dns_name = this_dns_name
if dns_name == '' or dns_name is None:
if dns_name == '':
continue
status = host['status']
@ -229,11 +218,11 @@ class CobblerInventory(object):
# Need to load index from cache
self.load_cache_from_cache()
if self.args.host not in self.cache:
if not self.args.host in self.cache:
# try updating the cache
self.update_cache()
if self.args.host not in self.cache:
if not self.args.host in self.cache:
# host might not exist anymore
return self.json_format_dict({}, True)

View File

@ -3,8 +3,6 @@
[collins]
# You should not have a trailing slash or collins
# will not properly match the URI
host = http://localhost:9000
username = blake

View File

@ -74,6 +74,7 @@ import re
import sys
from time import time
import traceback
import urllib
try:
import json
@ -81,11 +82,9 @@ except ImportError:
import simplejson as json
from six import iteritems
from six.moves.urllib.parse import urlencode
from ansible.module_utils.urls import open_url
class CollinsDefaults(object):
ASSETS_API_ENDPOINT = '%s/api/assets'
SPECIAL_ATTRIBUTES = set([
@ -118,7 +117,7 @@ class CollinsInventory(object):
self.parse_cli_args()
logging.basicConfig(format=CollinsDefaults.LOG_FORMAT,
filename=self.log_location)
filename=self.log_location)
self.log = logging.getLogger('CollinsInventory')
def _asset_get_attribute(self, asset, attrib):
@ -169,13 +168,14 @@ class CollinsInventory(object):
print(data_to_print)
return successful
def find_assets(self, attributes={}, operation='AND'):
def find_assets(self, attributes = {}, operation = 'AND'):
""" Obtains Collins assets matching the provided attributes. """
# Formats asset search query to locate assets matching attributes, using
# the CQL search feature as described here:
# http://tumblr.github.io/collins/recipes.html
attributes_query = ['='.join(attr_pair) for attr_pair in iteritems(attributes)]
attributes_query = [ '='.join(attr_pair)
for attr_pair in iteritems(attributes) ]
query_parameters = {
'details': ['True'],
'operation': [operation],
@ -190,18 +190,18 @@ class CollinsInventory(object):
# Locates all assets matching the provided query, exhausting pagination.
while True:
if num_retries == self.collins_max_retries:
raise MaxRetriesError("Maximum of %s retries reached; giving up" % self.collins_max_retries)
raise MaxRetriesError("Maximum of %s retries reached; giving up" % \
self.collins_max_retries)
query_parameters['page'] = cur_page
query_url = "%s?%s" % (
(CollinsDefaults.ASSETS_API_ENDPOINT % self.collins_host),
urlencode(query_parameters, doseq=True)
urllib.urlencode(query_parameters, doseq=True)
)
try:
response = open_url(query_url,
timeout=self.collins_timeout_secs,
url_username=self.collins_username,
url_password=self.collins_password,
force_basic_auth=True)
timeout=self.collins_timeout_secs,
url_username=self.collins_username,
url_password=self.collins_password)
json_response = json.loads(response.read())
# Adds any assets found to the array of assets.
assets += json_response['data']['Data']
@ -211,7 +211,8 @@ class CollinsInventory(object):
cur_page += 1
num_retries = 0
except:
self.log.error("Error while communicating with Collins, retrying:\n%s" % traceback.format_exc())
self.log.error("Error while communicating with Collins, retrying:\n%s",
traceback.format_exc())
num_retries += 1
return assets
@ -230,15 +231,19 @@ class CollinsInventory(object):
def read_settings(self):
""" Reads the settings from the collins.ini file """
config_loc = os.getenv('COLLINS_CONFIG', os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
config_loc = os.getenv('COLLINS_CONFIG',
os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
config = ConfigParser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
self.collins_host = config.get('collins', 'host')
self.collins_username = os.getenv('COLLINS_USERNAME', config.get('collins', 'username'))
self.collins_password = os.getenv('COLLINS_PASSWORD', config.get('collins', 'password'))
self.collins_asset_type = os.getenv('COLLINS_ASSET_TYPE', config.get('collins', 'asset_type'))
self.collins_username = os.getenv('COLLINS_USERNAME',
config.get('collins', 'username'))
self.collins_password = os.getenv('COLLINS_PASSWORD',
config.get('collins', 'password'))
self.collins_asset_type = os.getenv('COLLINS_ASSET_TYPE',
config.get('collins', 'asset_type'))
self.collins_timeout_secs = config.getint('collins', 'timeout_secs')
self.collins_max_retries = config.getint('collins', 'max_retries')
@ -262,12 +267,16 @@ class CollinsInventory(object):
parser = argparse.ArgumentParser(
description='Produces an Ansible Inventory file based on Collins')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to Collins '
'(default: False - use cache files)')
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty print all JSON output')
parser.add_argument('--list',
action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host',
action='store', help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache',
action='store_true', default=False,
help='Force refresh of cache by making API requests to Collins ' \
'(default: False - use cache files)')
parser.add_argument('--pretty',
action='store_true', default=False, help='Pretty print all JSON output')
self.args = parser.parse_args()
def update_cache(self):
@ -280,7 +289,8 @@ class CollinsInventory(object):
try:
server_assets = self.find_assets()
except:
self.log.error("Error while locating assets from Collins:\n%s" % traceback.format_exc())
self.log.error("Error while locating assets from Collins:\n%s",
traceback.format_exc())
return False
for asset in server_assets:
@ -304,7 +314,8 @@ class CollinsInventory(object):
if self.prefer_hostnames and self._asset_has_attribute(asset, 'HOSTNAME'):
asset_identifier = self._asset_get_attribute(asset, 'HOSTNAME')
elif 'ADDRESSES' not in asset:
self.log.warning("No IP addresses found for asset '%s', skipping" % asset)
self.log.warning("No IP addresses found for asset '%s', skipping",
asset)
continue
elif len(asset['ADDRESSES']) < ip_index + 1:
self.log.warning(
@ -372,11 +383,11 @@ class CollinsInventory(object):
# Need to load index from cache
self.load_cache_from_cache()
if self.args.host not in self.cache:
if not self.args.host in self.cache:
# try updating the cache
self.update_cache()
if self.args.host not in self.cache:
if not self.args.host in self.cache:
# host might not exist anymore
return self.json_format_dict({}, self.args.pretty)
@ -392,7 +403,7 @@ class CollinsInventory(object):
return True
except:
self.log.error("Error while loading inventory:\n%s",
traceback.format_exc())
traceback.format_exc())
self.inventory = {}
return False
@ -406,7 +417,7 @@ class CollinsInventory(object):
return True
except:
self.log.error("Error while loading host cache:\n%s",
traceback.format_exc())
traceback.format_exc())
self.cache = {}
return False

View File

@ -0,0 +1,37 @@
# Ansible Consul external inventory script settings.
[consul]
# restrict included nodes to those from this datacenter
#datacenter = nyc1
# url of the the consul cluster to query
#url = http://demo.consul.io
url = http://localhost:8500
# suffix added to each service to create a group name e.g Service of 'redis' and
# a suffix of '_servers' will add each address to the group name 'redis_servers'
servers_suffix = _servers
# if specified then the inventory will generate domain names that will resolve
# via Consul's inbuilt DNS.
#domain=consul
# make groups from service tags. the name of the group is derived from the
# service name and the tag name e.g. a service named nginx with tags ['master', 'v1']
# will create groups nginx_master and nginx_v1
tags = true
# looks up the node name at the given path for a list of groups to which the
# node should be added.
kv_groups=ansible/groups
# looks up the node name at the given path for a json dictionary of metadata that
# should be attached as metadata for the node
kv_metadata=ansible/metadata
# looks up the health of each service and adds the node to 'up' and 'down' groups
# based on the service availibility
availability = true
available_suffix = _up
unavailable_suffix = _down

View File

@ -1,37 +0,0 @@
# Ansible Consul external inventory script settings.
[consul]
# restrict included nodes to those from this datacenter
#datacenter = nyc1
# url of the consul cluster to query
#url = http://demo.consul.io
url = http://localhost:8500
# suffix added to each service to create a group name e.g Service of 'redis' and
# a suffix of '_servers' will add each address to the group name 'redis_servers'
servers_suffix = _servers
# if specified then the inventory will generate domain names that will resolve
# via Consul's inbuilt DNS.
#domain=consul
# make groups from service tags. the name of the group is derived from the
# service name and the tag name e.g. a service named nginx with tags ['master', 'v1']
# will create groups nginx_master and nginx_v1
tags = true
# looks up the node name at the given path for a list of groups to which the
# node should be added.
kv_groups=ansible/groups
# looks up the node name at the given path for a json dictionary of metadata that
# should be attached as metadata for the node
kv_metadata=ansible/metadata
# looks up the health of each service and adds the node to 'up' and 'down' groups
# based on the service availibility
availability = true
available_suffix = _up
unavailable_suffix = _down

View File

@ -37,7 +37,7 @@ This script can be run with the switches
--datacenter, to restrict the nodes to a single datacenter
--host to restrict the inventory to a single named node. (requires datacenter config)
The configuration for this plugin is read from a consul_io.ini file located in the
The configuration for this plugin is read from a consul.ini file located in the
same directory as this inventory script. All config options in the config file
are optional except the host and port, which must point to a valid agent or
server running the http api. For more information on enabling the endpoint see.
@ -50,12 +50,6 @@ Other options include:
which restricts the included nodes to those from the given datacenter
'url':
the URL of the Consul cluster. host, port and scheme are derived from the
URL. If not specified, connection configuration defaults to http requests
to localhost on port 8500.
'domain':
if specified then the inventory will generate domain names that will resolve
@ -90,9 +84,9 @@ to retrieve the kv_groups and kv_metadata based on your consul configuration.
This is used to lookup groups for a node in the key value store. It specifies a
path to which each discovered node's name will be added to create a key to query
the key/value store. There it expects to find a comma separated list of group
names to which the node should be added e.g. if the inventory contains node
'nyc-web-1' in datacenter 'nyc-dc1' and kv_groups = 'ansible/groups' then the key
'ansible/groups/nyc-dc1/nyc-web-1' will be queried for a group list. If this query
names to which the node should be added e.g. if the inventory contains
'nyc-web-1' and kv_groups = 'ansible/groups' then the key
'v1/kv/ansible/groups/nyc-web-1' will be queried for a group list. If this query
returned 'test,honeypot' then the node address to both groups.
'kv_metadata':
@ -100,9 +94,7 @@ names to which the node should be added e.g. if the inventory contains node
kv_metadata is used to lookup metadata for each discovered node. Like kv_groups
above it is used to build a path to lookup in the kv store where it expects to
find a json dictionary of metadata entries. If found, each key/value pair in the
dictionary is added to the metadata for the node. eg node 'nyc-web-1' in datacenter
'nyc-dc1' and kv_metadata = 'ansible/metadata', then the key
'ansible/metadata/nyc-dc1/nyc-web-1' should contain '{"databse": "postgres"}'
dictionary is added to the metadata for the node.
'availability':
@ -132,360 +124,306 @@ be used to access the machine.
import os
import re
import argparse
from time import time
import sys
import ConfigParser
import urllib, urllib2, base64
try:
import configparser
import json
except ImportError:
import ConfigParser as configparser
def get_log_filename():
tty_filename = '/dev/tty'
stdout_filename = '/dev/stdout'
if not os.path.exists(tty_filename):
return stdout_filename
if not os.access(tty_filename, os.W_OK):
return stdout_filename
if os.getenv('TEAMCITY_VERSION'):
return stdout_filename
return tty_filename
def setup_logging():
filename = get_log_filename()
import logging.config
logging.config.dictConfig({
'version': 1,
'formatters': {
'simple': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
},
},
'root': {
'level': os.getenv('ANSIBLE_INVENTORY_CONSUL_IO_LOG_LEVEL', 'WARN'),
'handlers': ['console'],
},
'handlers': {
'console': {
'class': 'logging.FileHandler',
'filename': filename,
'formatter': 'simple',
},
},
'loggers': {
'iso8601': {
'qualname': 'iso8601',
'level': 'INFO',
},
},
})
logger = logging.getLogger('consul_io.py')
logger.debug('Invoked with %r', sys.argv)
if os.getenv('ANSIBLE_INVENTORY_CONSUL_IO_LOG_ENABLED'):
setup_logging()
import simplejson as json
try:
import json
except ImportError:
import simplejson as json
try:
import consul
import consul
except ImportError as e:
sys.exit("""failed=True msg='python-consul required for this module.
See http://python-consul.readthedocs.org/en/latest/#installation'""")
print("""failed=True msg='python-consul required for this module. see
http://python-consul.readthedocs.org/en/latest/#installation'""")
sys.exit(1)
from six import iteritems
class ConsulInventory(object):
def __init__(self):
''' Create an inventory based on the catalog of nodes and services
registered in a consul cluster'''
self.node_metadata = {}
self.nodes = {}
self.nodes_by_service = {}
self.nodes_by_tag = {}
self.nodes_by_datacenter = {}
self.nodes_by_kv = {}
self.nodes_by_availability = {}
self.current_dc = None
def __init__(self):
''' Create an inventory based on the catalog of nodes and services
registered in a consul cluster'''
self.node_metadata = {}
self.nodes = {}
self.nodes_by_service = {}
self.nodes_by_tag = {}
self.nodes_by_datacenter = {}
self.nodes_by_kv = {}
self.nodes_by_availability = {}
self.current_dc = None
config = ConsulConfig()
self.config = config
config = ConsulConfig()
self.config = config
self.consul_api = config.get_consul_api()
self.consul_api = config.get_consul_api()
if config.has_config('datacenter'):
if config.has_config('host'):
self.load_data_for_node(config.host, config.datacenter)
else:
self.load_data_for_datacenter(config.datacenter)
else:
self.load_all_data_consul()
if config.has_config('datacenter'):
if config.has_config('host'):
self.load_data_for_node(config.host, config.datacenter)
else:
self.load_data_for_datacenter(config.datacenter)
else:
self.load_all_data_consul()
self.combine_all_results()
print(json.dumps(self.inventory, sort_keys=True, indent=2))
self.combine_all_results()
print(json.dumps(self.inventory, sort_keys=True, indent=2))
def load_all_data_consul(self):
''' cycle through each of the datacenters in the consul catalog and process
the nodes in each '''
self.datacenters = self.consul_api.catalog.datacenters()
for datacenter in self.datacenters:
self.current_dc = datacenter
self.load_data_for_datacenter(datacenter)
def load_all_data_consul(self):
''' cycle through each of the datacenters in the consul catalog and process
the nodes in each '''
self.datacenters = self.consul_api.catalog.datacenters()
for datacenter in self.datacenters:
self.current_dc = datacenter
self.load_data_for_datacenter(datacenter)
def load_availability_groups(self, node, datacenter):
'''check the health of each service on a node and add add the node to either
an 'available' or 'unavailable' grouping. The suffix for each group can be
controlled from the config'''
if self.config.has_config('availability'):
for service_name, service in iteritems(node['Services']):
for node in self.consul_api.health.service(service_name)[1]:
for check in node['Checks']:
if check['ServiceName'] == service_name:
ok = 'passing' == check['Status']
if ok:
suffix = self.config.get_availability_suffix(
def load_availability_groups(self, node, datacenter):
'''check the health of each service on a node and add add the node to either
an 'available' or 'unavailable' grouping. The suffix for each group can be
controlled from the config'''
if self.config.has_config('availability'):
for service_name, service in iteritems(node['Services']):
for node in self.consul_api.health.service(service_name)[1]:
for check in node['Checks']:
if check['ServiceName'] == service_name:
ok = 'passing' == check['Status']
if ok:
suffix = self.config.get_availability_suffix(
'available_suffix', '_available')
else:
suffix = self.config.get_availability_suffix(
else:
suffix = self.config.get_availability_suffix(
'unavailable_suffix', '_unavailable')
self.add_node_to_map(self.nodes_by_availability,
service_name + suffix, node['Node'])
self.add_node_to_map(self.nodes_by_availability,
service_name + suffix, node['Node'])
def load_data_for_datacenter(self, datacenter):
'''processes all the nodes in a particular datacenter'''
index, nodes = self.consul_api.catalog.nodes(dc=datacenter)
for node in nodes:
self.add_node_to_map(self.nodes_by_datacenter, datacenter, node)
self.load_data_for_node(node['Node'], datacenter)
def load_data_for_node(self, node, datacenter):
'''loads the data for a sinle node adding it to various groups based on
metadata retrieved from the kv store and service availability'''
def load_data_for_datacenter(self, datacenter):
'''processes all the nodes in a particular datacenter'''
index, nodes = self.consul_api.catalog.nodes(dc=datacenter)
for node in nodes:
self.add_node_to_map(self.nodes_by_datacenter, datacenter, node)
self.load_data_for_node(node['Node'], datacenter)
index, node_data = self.consul_api.catalog.node(node, dc=datacenter)
node = node_data['Node']
self.add_node_to_map(self.nodes, 'all', node)
self.add_metadata(node_data, "consul_datacenter", datacenter)
self.add_metadata(node_data, "consul_nodename", node['Node'])
def load_data_for_node(self, node, datacenter):
'''loads the data for a sinle node adding it to various groups based on
metadata retrieved from the kv store and service availability'''
self.load_groups_from_kv(node_data)
self.load_node_metadata_from_kv(node_data)
self.load_availability_groups(node_data, datacenter)
index, node_data = self.consul_api.catalog.node(node, dc=datacenter)
node = node_data['Node']
self.add_node_to_map(self.nodes, 'all', node)
self.add_metadata(node_data, "consul_datacenter", datacenter)
self.add_metadata(node_data, "consul_nodename", node['Node'])
for name, service in node_data['Services'].items():
self.load_data_from_service(name, service, node_data)
self.load_groups_from_kv(node_data)
self.load_node_metadata_from_kv(node_data)
self.load_availability_groups(node_data, datacenter)
def load_node_metadata_from_kv(self, node_data):
''' load the json dict at the metadata path defined by the kv_metadata value
and the node name add each entry in the dictionary to the node's
metadata '''
node = node_data['Node']
if self.config.has_config('kv_metadata'):
key = "%s/%s/%s" % (self.config.kv_metadata, self.current_dc, node['Node'])
index, metadata = self.consul_api.kv.get(key)
if metadata and metadata['Value']:
try:
metadata = json.loads(metadata['Value'])
for k, v in metadata.items():
self.add_metadata(node_data, k, v)
except:
pass
for name, service in node_data['Services'].items():
self.load_data_from_service(name, service, node_data)
def load_groups_from_kv(self, node_data):
''' load the comma separated list of groups at the path defined by the
kv_groups config value and the node name add the node address to each
group found '''
node = node_data['Node']
if self.config.has_config('kv_groups'):
key = "%s/%s/%s" % (self.config.kv_groups, self.current_dc, node['Node'])
index, groups = self.consul_api.kv.get(key)
if groups and groups['Value']:
for group in groups['Value'].split(','):
self.add_node_to_map(self.nodes_by_kv, group.strip(), node)
def load_node_metadata_from_kv(self, node_data):
''' load the json dict at the metadata path defined by the kv_metadata value
and the node name add each entry in the dictionary to the the node's
metadata '''
node = node_data['Node']
if self.config.has_config('kv_metadata'):
key = "%s/%s/%s" % (self.config.kv_metadata, self.current_dc, node['Node'])
index, metadata = self.consul_api.kv.get(key)
if metadata and metadata['Value']:
try:
metadata = json.loads(metadata['Value'])
for k,v in metadata.items():
self.add_metadata(node_data, k, v)
except:
pass
def load_data_from_service(self, service_name, service, node_data):
'''process a service registered on a node, adding the node to a group with
the service name. Each service tag is extracted and the node is added to a
tag grouping also'''
self.add_metadata(node_data, "consul_services", service_name, True)
def load_groups_from_kv(self, node_data):
''' load the comma separated list of groups at the path defined by the
kv_groups config value and the node name add the node address to each
group found '''
node = node_data['Node']
if self.config.has_config('kv_groups'):
key = "%s/%s/%s" % (self.config.kv_groups, self.current_dc, node['Node'])
index, groups = self.consul_api.kv.get(key)
if groups and groups['Value']:
for group in groups['Value'].split(','):
self.add_node_to_map(self.nodes_by_kv, group.strip(), node)
if self.is_service("ssh", service_name):
self.add_metadata(node_data, "ansible_ssh_port", service['Port'])
def load_data_from_service(self, service_name, service, node_data):
'''process a service registered on a node, adding the node to a group with
the service name. Each service tag is extracted and the node is added to a
tag grouping also'''
self.add_metadata(node_data, "consul_services", service_name, True)
if self.config.has_config('servers_suffix'):
service_name = service_name + self.config.servers_suffix
if self.is_service("ssh", service_name):
self.add_metadata(node_data, "ansible_ssh_port", service['Port'])
self.add_node_to_map(self.nodes_by_service, service_name, node_data['Node'])
self.extract_groups_from_tags(service_name, service, node_data)
if self.config.has_config('servers_suffix'):
service_name = service_name + self.config.servers_suffix
def is_service(self, target, name):
return name and (name.lower() == target.lower())
self.add_node_to_map(self.nodes_by_service, service_name, node_data['Node'])
self.extract_groups_from_tags(service_name, service, node_data)
def extract_groups_from_tags(self, service_name, service, node_data):
'''iterates each service tag and adds the node to groups derived from the
service and tag names e.g. nginx_master'''
if self.config.has_config('tags') and service['Tags']:
tags = service['Tags']
self.add_metadata(node_data, "consul_%s_tags" % service_name, tags)
for tag in service['Tags']:
tagname = service_name + '_' + tag
self.add_node_to_map(self.nodes_by_tag, tagname, node_data['Node'])
def is_service(self, target, name):
return name and (name.lower() == target.lower())
def combine_all_results(self):
'''prunes and sorts all groupings for combination into the final map'''
self.inventory = {"_meta": {"hostvars": self.node_metadata}}
groupings = [self.nodes, self.nodes_by_datacenter, self.nodes_by_service,
self.nodes_by_tag, self.nodes_by_kv, self.nodes_by_availability]
for grouping in groupings:
for name, addresses in grouping.items():
self.inventory[name] = sorted(list(set(addresses)))
def extract_groups_from_tags(self, service_name, service, node_data):
'''iterates each service tag and adds the node to groups derived from the
service and tag names e.g. nginx_master'''
if self.config.has_config('tags') and service['Tags']:
tags = service['Tags']
self.add_metadata(node_data, "consul_%s_tags" % service_name, tags)
for tag in service['Tags']:
tagname = service_name +'_'+tag
self.add_node_to_map(self.nodes_by_tag, tagname, node_data['Node'])
def add_metadata(self, node_data, key, value, is_list=False):
''' Pushed an element onto a metadata dict for the node, creating
the dict if it doesn't exist '''
key = self.to_safe(key)
node = self.get_inventory_name(node_data['Node'])
def combine_all_results(self):
'''prunes and sorts all groupings for combination into the final map'''
self.inventory = {"_meta": { "hostvars" : self.node_metadata}}
groupings = [self.nodes, self.nodes_by_datacenter, self.nodes_by_service,
self.nodes_by_tag, self.nodes_by_kv, self.nodes_by_availability]
for grouping in groupings:
for name, addresses in grouping.items():
self.inventory[name] = sorted(list(set(addresses)))
if node in self.node_metadata:
metadata = self.node_metadata[node]
else:
metadata = {}
self.node_metadata[node] = metadata
if is_list:
self.push(metadata, key, value)
else:
metadata[key] = value
def add_metadata(self, node_data, key, value, is_list = False):
''' Pushed an element onto a metadata dict for the node, creating
the dict if it doesn't exist '''
key = self.to_safe(key)
node = self.get_inventory_name(node_data['Node'])
def get_inventory_name(self, node_data):
'''return the ip or a node name that can be looked up in consul's dns'''
domain = self.config.domain
if domain:
node_name = node_data['Node']
if self.current_dc:
return '%s.node.%s.%s' % (node_name, self.current_dc, domain)
else:
return '%s.node.%s' % (node_name, domain)
else:
return node_data['Address']
if node in self.node_metadata:
metadata = self.node_metadata[node]
else:
metadata = {}
self.node_metadata[node] = metadata
if is_list:
self.push(metadata, key, value)
else:
metadata[key] = value
def add_node_to_map(self, map, name, node):
self.push(map, name, self.get_inventory_name(node))
def get_inventory_name(self, node_data):
'''return the ip or a node name that can be looked up in consul's dns'''
domain = self.config.domain
if domain:
node_name = node_data['Node']
if self.current_dc:
return '%s.node.%s.%s' % ( node_name, self.current_dc, domain)
else:
return '%s.node.%s' % ( node_name, domain)
else:
return node_data['Address']
def push(self, my_dict, key, element):
''' Pushed an element onto an array that may not have been defined in the
dict '''
key = self.to_safe(key)
if key in my_dict:
my_dict[key].append(element)
else:
my_dict[key] = [element]
def add_node_to_map(self, map, name, node):
self.push(map, name, self.get_inventory_name(node))
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be used
as Ansible groups '''
return re.sub('[^A-Za-z0-9\-\.]', '_', word)
def sanitize_dict(self, d):
def push(self, my_dict, key, element):
''' Pushed an element onto an array that may not have been defined in the
dict '''
key = self.to_safe(key)
if key in my_dict:
my_dict[key].append(element)
else:
my_dict[key] = [element]
new_dict = {}
for k, v in d.items():
if v is not None:
new_dict[self.to_safe(str(k))] = self.to_safe(str(v))
return new_dict
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be used
as Ansible groups '''
return re.sub('[^A-Za-z0-9\-\.]', '_', word)
def sanitize_list(self, seq):
new_seq = []
for d in seq:
new_seq.append(self.sanitize_dict(d))
return new_seq
def sanitize_dict(self, d):
new_dict = {}
for k, v in d.items():
if v != None:
new_dict[self.to_safe(str(k))] = self.to_safe(str(v))
return new_dict
def sanitize_list(self, seq):
new_seq = []
for d in seq:
new_seq.append(self.sanitize_dict(d))
return new_seq
class ConsulConfig(dict):
def __init__(self):
self.read_settings()
self.read_cli_args()
def __init__(self):
self.read_settings()
self.read_cli_args()
def has_config(self, name):
if hasattr(self, name):
return getattr(self, name)
else:
return False
def has_config(self, name):
if hasattr(self, name):
return getattr(self, name)
else:
return False
def read_settings(self):
''' Reads the settings from the consul_io.ini file (or consul.ini for backwards compatibility)'''
config = configparser.SafeConfigParser()
if os.path.isfile(os.path.dirname(os.path.realpath(__file__)) + '/consul_io.ini'):
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul_io.ini')
else:
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul.ini')
def read_settings(self):
''' Reads the settings from the consul.ini file '''
config = ConfigParser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/consul.ini')
config_options = ['host', 'token', 'datacenter', 'servers_suffix',
'tags', 'kv_metadata', 'kv_groups', 'availability',
'unavailable_suffix', 'available_suffix', 'url',
'domain']
for option in config_options:
value = None
if config.has_option('consul', option):
value = config.get('consul', option)
setattr(self, option, value)
config_options = ['host', 'token', 'datacenter', 'servers_suffix',
'tags', 'kv_metadata', 'kv_groups', 'availability',
'unavailable_suffix', 'available_suffix', 'url',
'domain']
for option in config_options:
value = None
if config.has_option('consul', option):
value = config.get('consul', option)
setattr(self, option, value)
def read_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based nodes in a Consul cluster')
def read_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description=
'Produce an Ansible Inventory file based nodes in a Consul cluster')
parser.add_argument('--list', action='store_true',
help='Get all inventory variables from all nodes in the consul cluster')
parser.add_argument('--host', action='store',
help='Get all inventory variables about a specific consul node,'
'requires datacenter set in consul.ini.')
parser.add_argument('--datacenter', action='store',
help='Get all inventory about a specific consul datacenter')
parser.add_argument('--list', action='store_true',
help='Get all inventory variables from all nodes in the consul cluster')
parser.add_argument('--host', action='store',
help='Get all inventory variables about a specific consul node, \
requires datacenter set in consul.ini.')
parser.add_argument('--datacenter', action='store',
help='Get all inventory about a specific consul datacenter')
args = parser.parse_args()
arg_names = ['host', 'datacenter']
args = parser.parse_args()
arg_names = ['host', 'datacenter']
for arg in arg_names:
if getattr(args, arg):
setattr(self, arg, getattr(args, arg))
for arg in arg_names:
if getattr(args, arg):
setattr(self, arg, getattr(args, arg))
def get_availability_suffix(self, suffix, default):
if self.has_config(suffix):
return self.has_config(suffix)
return default
def get_availability_suffix(self, suffix, default):
if self.has_config(suffix):
return self.has_config(suffix)
return default
def get_consul_api(self):
'''get an instance of the api based on the supplied configuration'''
host = 'localhost'
port = 8500
token = None
scheme = 'http'
if hasattr(self, 'url'):
from ansible.module_utils.six.moves.urllib.parse import urlparse
o = urlparse(self.url)
if o.hostname:
host = o.hostname
if o.port:
port = o.port
if o.scheme:
scheme = o.scheme
def get_consul_api(self):
'''get an instance of the api based on the supplied configuration'''
host = 'localhost'
port = 8500
token = None
if hasattr(self, 'token'):
token = self.token
if not token:
token = 'anonymous'
return consul.Consul(host=host, port=port, token=token, scheme=scheme)
if hasattr(self, 'url'):
from urlparse import urlparse
o = urlparse(self.url)
if o.hostname:
host = o.hostname
if o.port:
port = o.port
if hasattr(self, 'token'):
token = self.token
if not token:
token = 'anonymous'
return consul.Consul(host=host, port=port, token=token)
ConsulInventory()

View File

@ -26,9 +26,3 @@ cache_max_age = 300
# Use the private network IP address instead of the public when available.
#
use_private_network = False
# Pass variables to every group, e.g.:
#
# group_variables = { 'ansible_user': 'root' }
#
group_variables = {}

View File

@ -22,7 +22,7 @@ found. You can force this script to use the cache with --force-cache.
----
Configuration is read from `digital_ocean.ini`, then from environment variables,
and then from command-line arguments.
then and command-line arguments.
Most notably, the DigitalOcean API Token must be specified. It can be specified
in the INI file or with the following environment variables:
@ -47,7 +47,7 @@ The following groups are generated from --list:
- size_NAME
- status_STATUS
For each host, the following variables are registered:
When run against a specific host, this script returns the following variables:
- do_backup_ids
- do_created_at
- do_disk
@ -58,7 +58,7 @@ For each host, the following variables are registered:
- do_private_ip_address
- do_kernel - object
- do_locked
- do_memory
- de_memory
- do_name
- do_networks - object
- do_next_backup_window
@ -67,9 +67,7 @@ For each host, the following variables are registered:
- do_size_slug
- do_snapshot_ids - list
- do_status
- do_tags
- do_vcpus
- do_volume_ids
-----
```
@ -138,12 +136,7 @@ import sys
import re
import argparse
from time import time
import ast
try:
import ConfigParser
except ImportError:
import configparser as ConfigParser
import ConfigParser
try:
import json
@ -151,9 +144,11 @@ except ImportError:
import simplejson as json
try:
from dopy.manager import DoManager
from dopy.manager import DoError, DoManager
except ImportError as e:
sys.exit("failed=True msg={}".format(e.message))
print("failed=True msg='`dopy` library required for this script'")
sys.exit(1)
class DigitalOceanInventory(object):
@ -166,14 +161,13 @@ class DigitalOceanInventory(object):
''' Main execution path '''
# DigitalOceanInventory data
self.data = {} # All DigitalOcean data
self.inventory = {} # Ansible Inventory
self.data = {} # All DigitalOcean data
self.inventory = {} # Ansible Inventory
# Define defaults
self.cache_path = '.'
self.cache_max_age = 0
self.use_private_network = False
self.group_variables = {}
# Read settings, environment variables, and CLI arguments
self.read_settings()
@ -182,9 +176,9 @@ class DigitalOceanInventory(object):
# Verify credentials were set
if not hasattr(self, 'api_token'):
sys.stderr.write('''Could not find values for DigitalOcean api_token.
print('''Could not find values for DigitalOcean api_token.
They must be specified via either ini file, command line argument (--api-token),
or environment variables (DO_API_TOKEN)\n''')
or environment variables (DO_API_TOKEN)''')
sys.exit(-1)
# env command, show DigitalOcean credentials
@ -196,11 +190,11 @@ or environment variables (DO_API_TOKEN)\n''')
self.cache_filename = self.cache_path + "/ansible-digital_ocean.cache"
self.cache_refreshed = False
if self.is_cache_valid():
if self.is_cache_valid:
self.load_from_cache()
if len(self.data) == 0:
if self.args.force_cache:
sys.stderr.write('''Cache is empty and --force-cache was specified\n''')
print('''Cache is empty and --force-cache was specified''')
sys.exit(-1)
self.manager = DoManager(None, self.api_token, api_version=2)
@ -243,6 +237,7 @@ or environment variables (DO_API_TOKEN)\n''')
print(json.dumps(json_data))
# That's all she wrote...
###########################################################################
# Script configuration
###########################################################################
@ -264,11 +259,7 @@ or environment variables (DO_API_TOKEN)\n''')
# Private IP Address
if config.has_option('digital_ocean', 'use_private_network'):
self.use_private_network = config.getboolean('digital_ocean', 'use_private_network')
# Group variables
if config.has_option('digital_ocean', 'group_variables'):
self.group_variables = ast.literal_eval(config.get('digital_ocean', 'group_variables'))
self.use_private_network = config.get('digital_ocean', 'use_private_network')
def read_environment(self):
''' Reads the settings from environment variables '''
@ -278,6 +269,7 @@ or environment variables (DO_API_TOKEN)\n''')
if os.getenv("DO_API_KEY"):
self.api_token = os.getenv("DO_API_KEY")
def read_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on DigitalOcean credentials')
@ -286,23 +278,23 @@ or environment variables (DO_API_TOKEN)\n''')
parser.add_argument('--host', action='store', help='Get all Ansible inventory variables about a specific Droplet')
parser.add_argument('--all', action='store_true', help='List all DigitalOcean information as JSON')
parser.add_argument('--droplets', '-d', action='store_true', help='List Droplets as JSON')
parser.add_argument('--droplets','-d', action='store_true', help='List Droplets as JSON')
parser.add_argument('--regions', action='store_true', help='List Regions as JSON')
parser.add_argument('--images', action='store_true', help='List Images as JSON')
parser.add_argument('--sizes', action='store_true', help='List Sizes as JSON')
parser.add_argument('--ssh-keys', action='store_true', help='List SSH keys as JSON')
parser.add_argument('--domains', action='store_true', help='List Domains as JSON')
parser.add_argument('--domains', action='store_true',help='List Domains as JSON')
parser.add_argument('--pretty', '-p', action='store_true', help='Pretty-print results')
parser.add_argument('--pretty','-p', action='store_true', help='Pretty-print results')
parser.add_argument('--cache-path', action='store', help='Path to the cache files (default: .)')
parser.add_argument('--cache-max_age', action='store', help='Maximum age of the cached items (default: 0)')
parser.add_argument('--force-cache', action='store_true', default=False, help='Only use data from the cache')
parser.add_argument('--refresh-cache', '-r', action='store_true', default=False,
parser.add_argument('--refresh-cache','-r', action='store_true', default=False,
help='Force refresh of cache by making API requests to DigitalOcean (default: False - use cache files)')
parser.add_argument('--env', '-e', action='store_true', help='Display DO_API_TOKEN')
parser.add_argument('--api-token', '-a', action='store', help='DigitalOcean API Token')
parser.add_argument('--env','-e', action='store_true', help='Display DO_API_TOKEN')
parser.add_argument('--api-token','-a', action='store', help='DigitalOcean API Token')
self.args = parser.parse_args()
@ -316,19 +308,20 @@ or environment variables (DO_API_TOKEN)\n''')
not self.args.all and not self.args.host):
self.args.list = True
###########################################################################
# Data Management
###########################################################################
def load_from_digital_ocean(self, resource=None):
'''Get JSON from DigitalOcean API'''
if self.args.force_cache and os.path.isfile(self.cache_filename):
if self.args.force_cache:
return
# We always get fresh droplets
if self.is_cache_valid() and not (resource == 'droplets' or resource is None):
if self.is_cache_valid() and not (resource=='droplets' or resource is None):
return
if self.args.refresh_cache:
resource = None
resource=None
if resource == 'droplets' or resource is None:
self.data['droplets'] = self.manager.all_active_droplets()
@ -349,69 +342,56 @@ or environment variables (DO_API_TOKEN)\n''')
self.data['domains'] = self.manager.all_domains()
self.cache_refreshed = True
def build_inventory(self):
'''Build Ansible inventory of droplets'''
self.inventory = {
'all': {
'hosts': [],
'vars': self.group_variables
},
'_meta': {'hostvars': {}}
}
self.inventory = {}
# add all droplets by id and name
for droplet in self.data['droplets']:
# when using private_networking, the API reports the private one in "ip_address".
#when using private_networking, the API reports the private one in "ip_address".
if 'private_networking' in droplet['features'] and not self.use_private_network:
for net in droplet['networks']['v4']:
if net['type'] == 'public':
dest = net['ip_address']
if net['type']=='public':
dest=net['ip_address']
else:
continue
else:
dest = droplet['ip_address']
self.inventory['all']['hosts'].append(dest)
self.inventory[droplet['id']] = [dest]
self.inventory[droplet['name']] = [dest]
self.push(self.inventory, droplet['name'], dest)
self.push(self.inventory, 'region_' + droplet['region']['slug'], dest)
self.push(self.inventory, 'image_' + str(droplet['image']['id']), dest)
self.push(self.inventory, 'size_' + droplet['size']['slug'], dest)
# groups that are always present
for group in ('region_' + droplet['region']['slug'],
'image_' + str(droplet['image']['id']),
'size_' + droplet['size']['slug'],
'distro_' + self.to_safe(droplet['image']['distribution']),
'status_' + droplet['status']):
if group not in self.inventory:
self.inventory[group] = {'hosts': [], 'vars': {}}
self.inventory[group]['hosts'].append(dest)
image_slug = droplet['image']['slug']
if image_slug:
self.push(self.inventory, 'image_' + self.to_safe(image_slug), dest)
else:
image_name = droplet['image']['name']
if image_name:
self.push(self.inventory, 'image_' + self.to_safe(image_name), dest)
# groups that are not always present
for group in (droplet['image']['slug'],
droplet['image']['name']):
if group:
image = 'image_' + self.to_safe(group)
if image not in self.inventory:
self.inventory[image] = {'hosts': [], 'vars': {}}
self.inventory[image]['hosts'].append(dest)
self.push(self.inventory, 'distro_' + self.to_safe(droplet['image']['distribution']), dest)
self.push(self.inventory, 'status_' + droplet['status'], dest)
if droplet['tags']:
for tag in droplet['tags']:
if tag not in self.inventory:
self.inventory[tag] = {'hosts': [], 'vars': {}}
self.inventory[tag]['hosts'].append(dest)
# hostvars
info = self.do_namespace(droplet)
self.inventory['_meta']['hostvars'][dest] = info
def load_droplet_variables_for_host(self):
'''Generate a JSON response to a --host call'''
host = int(self.args.host)
droplet = self.manager.show_droplet(host)
info = self.do_namespace(droplet)
# Put all the information in a 'do_' namespace
info = {}
for k, v in droplet.items():
info['do_'+k] = v
return {'droplet': info}
###########################################################################
# Cache Management
###########################################################################
@ -425,6 +405,7 @@ or environment variables (DO_API_TOKEN)\n''')
return True
return False
def load_from_cache(self):
''' Reads the data from the cache file and assigns it to member variables as Python Objects'''
try:
@ -438,15 +419,17 @@ or environment variables (DO_API_TOKEN)\n''')
self.data = data['data']
self.inventory = data['inventory']
def write_to_cache(self):
''' Writes data in JSON format to a file '''
data = {'data': self.data, 'inventory': self.inventory}
data = { 'data': self.data, 'inventory': self.inventory }
json_data = json.dumps(data, sort_keys=True, indent=2)
cache = open(self.cache_filename, 'w')
cache.write(json_data)
cache.close()
###########################################################################
# Utilities
###########################################################################
@ -458,16 +441,11 @@ or environment variables (DO_API_TOKEN)\n''')
else:
my_dict[key] = [element]
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
return re.sub("[^A-Za-z0-9\-\.]", "_", word)
def do_namespace(self, data):
''' Returns a copy of the dictionary with all the keys put in a 'do_' namespace '''
info = {}
for k, v in data.items():
info['do_' + k] = v
return info
###########################################################################

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +1,134 @@
# This is the configuration file for the Docker inventory script: docker_inventory.py.
# This is the configuration file for the Ansible plugin for Docker inventory.
#
# You can define the following in this file:
# Author: Paul Durivage <paul.durivage@gmail.com>
#
# 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.
# Description:
# This module queries local or remote Docker daemons and generates
# inventory information.
#
# hosts
# If you wish to get inventory from more than one Docker host, define a hosts list.
# This plugin does not support targeting of specific hosts using the --host
# flag. Instead, it it queries the Docker API for each container, running
# or not, and returns this data all once.
#
# For the default host and each host in the hosts list define the following attributes:
# The plugin returns the following custom attributes on Docker containers:
# docker_args
# docker_config
# docker_created
# docker_driver
# docker_exec_driver
# docker_host_config
# docker_hostname_path
# docker_hosts_path
# docker_id
# docker_image
# docker_name
# docker_network_settings
# docker_path
# docker_resolv_conf_path
# docker_state
# docker_volumes
# docker_volumes_rw
#
# host:
# description: The URL or Unix socket path used to connect to the Docker API.
# required: yes
# Requirements:
# The docker-py module: https://github.com/dotcloud/docker-py
#
# tls:
# description: Connect using TLS without verifying the authenticity of the Docker host server.
# default: false
# Notes:
# A config file can be used to configure this inventory module, and there
# are several environment variables that can be set to modify the behavior
# of the plugin at runtime:
# DOCKER_CONFIG_FILE
# DOCKER_HOST
# DOCKER_VERSION
# DOCKER_TIMEOUT
# DOCKER_PRIVATE_SSH_PORT
# DOCKER_DEFAULT_IP
#
# Environment Variables:
# environment variable: DOCKER_CONFIG_FILE
# description:
# - A path to a Docker inventory hosts/defaults file in YAML format
# - A sample file has been provided, colocated with the inventory
# file called 'docker.yml'
# required: false
#
# tls_verify:
# description: Connect using TLS without verifying the authenticity of the Docker host server.
# default: false
# default: Uses docker.docker.Client constructor defaults
# environment variable: DOCKER_HOST
# description:
# - The socket on which to connect to a Docker daemon API
# required: false
#
# cert_path:
# description: Path to the client's TLS certificate file.
# default: null
# default: Uses docker.docker.Client constructor defaults
# environment variable: DOCKER_VERSION
# description:
# - Version of the Docker API to use
# default: Uses docker.docker.Client constructor defaults
# 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
# environment variable: DOCKER_TIMEOUT
# description:
# - Timeout in seconds for connections to Docker daemon API
# default: Uses docker.docker.Client constructor defaults
# required: false
# environment variable: DOCKER_PRIVATE_SSH_PORT
# description:
# - The private port (container port) on which SSH is listening
# for connections
# default: 22
# required: false
# environment variable: DOCKER_DEFAULT_IP
# description:
# - This environment variable overrides the container SSH connection
# IP address (aka, 'ansible_ssh_host')
#
# 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.
#
# 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 by Docker
# required: false
#
# Examples:
# Use the config file:
# DOCKER_CONFIG_FILE=./docker.yml docker.py --list
#
# Connect to docker instance on localhost port 4243
# DOCKER_HOST=tcp://localhost:4243 docker.py --list
#
# 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 --list
#
#
#
# The Docker inventory plugin provides several environment variables that
# may be overridden here. This configuration file always takes precedence
# over environment variables.
#
# Variable precedence is: hosts > defaults > environment
#defaults:
# host: unix:///var/run/docker.sock
# private_ssh_port: 22
# default_ip: 127.0.0.1
#hosts:
---
defaults:
host: unix:///var/run/docker.sock
version: 1.9
timeout: 60
private_ssh_port: 22
default_ip: 127.0.0.1
hosts:
# - host: tcp://10.45.5.16:4243
# version: 1.9
# timeout: 60
# private_ssh_port: 2022
# default_ip: 172.16.3.45
# - host: tcp://localhost:4243
# private_ssh_port: 2029

View File

@ -10,11 +10,9 @@
# AWS regions to make calls to. Set this to 'all' to make request to all regions
# in AWS and merge the results together. Alternatively, set this to a comma
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' and do not
# provide the 'regions_exclude' option. If this is set to 'auto', AWS_REGION or
# AWS_DEFAULT_REGION environment variable will be read to determine the region.
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
regions = all
regions_exclude = us-gov-west-1, cn-north-1
regions_exclude = us-gov-west-1,cn-north-1
# When generating inventory, Ansible needs to know how to address a server.
# Each EC2 instance has a lot of variables associated with it. Here is the list:
@ -31,41 +29,21 @@ regions_exclude = us-gov-west-1, cn-north-1
# in the event of a collision.
destination_variable = public_dns_name
# This allows you to override the inventory_name with an ec2 variable, instead
# of using the destination_variable above. Addressing (aka ansible_ssh_host)
# will still use destination_variable. Tags should be written as 'tag_TAGNAME'.
#hostname_variable = tag_Name
# For server inside a VPC, using DNS names may not make sense. When an instance
# has 'subnet_id' set, this variable is used. If the subnet is public, setting
# this to 'ip_address' will return the public IP address. For instances in a
# private subnet, this should be set to 'private_ip_address', and Ansible must
# be run from within EC2. The key of an EC2 tag may optionally be used; however
# the boto instance variables hold precedence in the event of a collision.
# WARNING: - instances that are in the private vpc, _without_ public ip address
# WARNING: - instances that are in the private vpc, _without_ public ip address
# will not be listed in the inventory until You set:
# vpc_destination_variable = private_ip_address
# vpc_destination_variable = 'private_ip_address'
vpc_destination_variable = ip_address
# The following two settings allow flexible ansible host naming based on a
# python format string and a comma-separated list of ec2 tags. Note that:
#
# 1) If the tags referenced are not present for some instances, empty strings
# will be substituted in the format string.
# 2) This overrides both destination_variable and vpc_destination_variable.
#
#destination_format = {0}.{1}.example.com
#destination_format_tags = Name,environment
# To tag instances on EC2 with the resource records that point to them from
# Route53, set 'route53' to True.
# Route53, uncomment and set 'route53' to True.
route53 = False
# To use Route53 records as the inventory hostnames, uncomment and set
# to equal the domain name you wish to use. You must also have 'route53' (above)
# set to True.
# route53_hostnames = .example.com
# To exclude RDS instances from the inventory, uncomment and set to False.
#rds = False
@ -82,16 +60,13 @@ all_instances = False
# By default, only EC2 instances in the 'running' state are returned. Specify
# EC2 instance states to return as a comma-separated list. This
# option is overridden when 'all_instances' is True.
# option is overriden when 'all_instances' is True.
# instance_states = pending, running, shutting-down, terminated, stopping, stopped
# By default, only RDS instances in the 'available' state are returned. Set
# 'all_rds_instances' to True return all RDS instances regardless of state.
all_rds_instances = False
# Include RDS cluster information (Aurora etc.)
include_rds_clusters = False
# By default, only ElastiCache clusters and nodes in the 'available' state
# are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes'
# to True return all ElastiCache clusters and nodes, regardless of state.
@ -131,11 +106,8 @@ expand_csv_tags = False
group_by_instance_id = True
group_by_region = True
group_by_availability_zone = True
group_by_aws_account = False
group_by_ami_id = True
group_by_instance_type = True
group_by_instance_state = False
group_by_platform = True
group_by_key_pair = True
group_by_vpc_id = True
group_by_security_group = True
@ -161,12 +133,6 @@ group_by_elasticache_replication_group = True
# Filters are key/value pairs separated by '=', to list multiple filters use
# a list separated by commas. See examples below.
# If you want to apply multiple filters simultaneously, set stack_filters to
# True. Default behaviour is to combine the results of all filters. Stacking
# allows the use of multiple conditions to filter down, for example by
# environment and type of host.
stack_filters = False
# Retrieve only instances with (key=value) env=staging tag
# instance_filters = tag:env=staging
@ -178,33 +144,9 @@ stack_filters = False
# You can use wildcards in filter values also. Below will list instances which
# tag Name value matches webservers1*
# (ex. webservers15, webservers1a, webservers123 etc)
# (ex. webservers15, webservers1a, webservers123 etc)
# instance_filters = tag:Name=webservers1*
# An IAM role can be assumed, so all requests are run as that role.
# This can be useful for connecting across different accounts, or to limit user
# access
# iam_role = role-arn
# A boto configuration profile may be used to separate out credentials
# see http://boto.readthedocs.org/en/latest/boto_config_tut.html
# boto_profile = some-boto-profile-name
[credentials]
# The AWS credentials can optionally be specified here. Credentials specified
# here are ignored if the environment variable AWS_ACCESS_KEY_ID or
# AWS_PROFILE is set, or if the boto_profile property above is set.
#
# Supplying AWS credentials here is not recommended, as it introduces
# non-trivial security concerns. When going down this route, please make sure
# to set access permissions for this file correctly, e.g. handle it the same
# way as you would a private SSH key.
#
# Unlike the boto and AWS configure files, this section does not support
# profiles.
#
# aws_access_key_id = AXXXXXXXXXXXXXX
# aws_secret_access_key = XXXXXXXXXXXXXXXXXXX
# aws_security_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXX

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ except:
import simplejson as json
# Options
# ------------------------------
#------------------------------
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
parser.add_option('--list', default=False, dest="list", action="store_true",
@ -48,7 +48,6 @@ parser.add_option('--host', default=None, dest="host",
# helper functions
#
def get_ssh_config():
configs = []
for box in list_running_boxes():
@ -56,8 +55,7 @@ def get_ssh_config():
configs.append(config)
return configs
# list all the running instances in the fleet
#list all the running instances in the fleet
def list_running_boxes():
boxes = []
for line in subprocess.check_output(["fleetctl", "list-machines"]).split('\n'):
@ -67,7 +65,6 @@ def list_running_boxes():
return boxes
def get_a_ssh_config(box_name):
config = {}
config['Host'] = box_name
@ -75,13 +72,12 @@ def get_a_ssh_config(box_name):
config['ansible_python_interpreter'] = '/opt/bin/python'
return config
# List out servers that vagrant has running
# ------------------------------
#------------------------------
if options.list:
ssh_config = get_ssh_config()
hosts = {'coreos': []}
hosts = { 'coreos': []}
for data in ssh_config:
hosts['coreos'].append(data['Host'])
@ -89,22 +85,23 @@ if options.list:
sys.exit(1)
# Get out the host details
# ------------------------------
#------------------------------
elif options.host:
result = {}
ssh_config = get_ssh_config()
details = filter(lambda x: (x['Host'] == options.host), ssh_config)
if len(details) > 0:
# pass through the port, in case it's non standard.
#pass through the port, in case it's non standard.
result = details[0]
result
print(json.dumps(result))
sys.exit(1)
# Print out help
# ------------------------------
#------------------------------
else:
parser.print_help()
sys.exit(1)

View File

@ -1,157 +0,0 @@
# Foreman inventory (https://github.com/theforeman/foreman_ansible_inventory)
#
# This script can be used as an Ansible dynamic inventory.
# The connection parameters are set up via *foreman.ini*
# This is how the script founds the configuration file in
# order of discovery.
#
# * `/etc/ansible/foreman.ini`
# * Current directory of your inventory script.
# * `FOREMAN_INI_PATH` environment variable.
#
# ## Variables and Parameters
#
# The data returned from Foreman for each host is stored in a foreman
# hash so they're available as *host_vars* along with the parameters
# of the host and it's hostgroups:
#
# "foo.example.com": {
# "foreman": {
# "architecture_id": 1,
# "architecture_name": "x86_64",
# "build": false,
# "build_status": 0,
# "build_status_label": "Installed",
# "capabilities": [
# "build",
# "image"
# ],
# "compute_profile_id": 4,
# "hostgroup_name": "webtier/myapp",
# "id": 70,
# "image_name": "debian8.1",
# ...
# "uuid": "50197c10-5ebb-b5cf-b384-a1e203e19e77"
# },
# "foreman_params": {
# "testparam1": "foobar",
# "testparam2": "small",
# ...
# }
#
# and could therefore be used in Ansible like:
#
# - debug: msg="From Foreman host {{ foreman['uuid'] }}"
#
# Which yields
#
# TASK [test_foreman : debug] ****************************************************
# ok: [foo.example.com] => {
# "msg": "From Foreman host 50190bd1-052a-a34a-3c9c-df37a39550bf"
# }
#
# ## Automatic Ansible groups
#
# The inventory will provide a set of groups, by default prefixed by
# 'foreman_'. If you want to customize this prefix, change the
# group_prefix option in /etc/ansible/foreman.ini. The rest of this
# guide will assume the default prefix of 'foreman'
#
# The hostgroup, location, organization, content view, and lifecycle
# environment of each host are created as Ansible groups with a
# foreman_<grouptype> prefix, all lowercase and problematic parameters
# removed. So e.g. the foreman hostgroup
#
# myapp / webtier / datacenter1
#
# would turn into the Ansible group:
#
# foreman_hostgroup_myapp_webtier_datacenter1
#
# If the parameter want_hostcollections is set to true, the
# collections each host is in are created as Ansible groups with a
# foreman_hostcollection prefix, all lowercase and problematic
# parameters removed. So e.g. the Foreman host collection
#
# Patch Window Thursday
#
# would turn into the Ansible group:
#
# foreman_hostcollection_patchwindowthursday
#
# If the parameter host_filters is set, it will be used as the
# "search" parameter for the /api/v2/hosts call. This can be used to
# restrict the list of returned host, as shown below.
#
# Furthermore Ansible groups can be created on the fly using the
# *group_patterns* variable in *foreman.ini* so that you can build up
# hierarchies using parameters on the hostgroup and host variables.
#
# Lets assume you have a host that is built using this nested hostgroup:
#
# myapp / webtier / datacenter1
#
# and each of the hostgroups defines a parameters respectively:
#
# myapp: app_param = myapp
# webtier: tier_param = webtier
# datacenter1: dc_param = datacenter1
#
# The host is also in a subnet called "mysubnet" and provisioned via an image
# then *group_patterns* like:
#
# [ansible]
# group_patterns = ["{app_param}-{tier_param}-{dc_param}",
# "{app_param}-{tier_param}",
# "{app_param}",
# "{subnet_name}-{provision_method}"]
#
# would put the host into the additional Ansible groups:
#
# - myapp-webtier-datacenter1
# - myapp-webtier
# - myapp
# - mysubnet-image
#
# by recursively resolving the hostgroups, getting the parameter keys
# and values and doing a Python *string.format()* like replacement on
# it.
#
[foreman]
url = http://localhost:3000/
user = foreman
password = secret
ssl_verify = True
# Retrieve only hosts from the organization "Web Engineering".
# host_filters = organization="Web Engineering"
# Retrieve only hosts from the organization "Web Engineering" that are
# also in the host collection "Apache Servers".
# host_filters = organization="Web Engineering" and host_collection="Apache Servers"
[ansible]
group_patterns = ["{app}-{tier}-{color}",
"{app}-{color}",
"{app}",
"{tier}"]
group_prefix = foreman_
# Whether to fetch facts from Foreman and store them on the host
want_facts = True
# Whether to create Ansible groups for host collections. Only tested
# with Katello (Red Hat Satellite). Disabled by default to not break
# the script for stand-alone Foreman.
want_hostcollections = False
# Whether to interpret global parameters value as JSON (if possible, else
# take as is). Only tested with Katello (Red Hat Satellite).
# This allows to define lists and dictionaries (and more complicated structures)
# variables by entering them as JSON string in Foreman parameters.
# Disabled by default as the change would else not be backward compatible.
rich_params = False
[cache]
path = .
max_age = 60

View File

@ -1,436 +0,0 @@
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
#
# Copyright (C) 2016 Guido Günther <agx@sigxcpu.org>,
# Daniel Lobato Garcia <dlobatog@redhat.com>
#
# This script 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 it. If not, see <http://www.gnu.org/licenses/>.
#
# This is somewhat based on cobbler inventory
# Stdlib imports
# __future__ imports must occur at the beginning of file
from __future__ import print_function
try:
# Python 2 version
import ConfigParser
except ImportError:
# Python 3 version
import configparser as ConfigParser
import json
import argparse
import copy
import os
import re
import sys
from time import time
from collections import defaultdict
from distutils.version import LooseVersion, StrictVersion
# 3rd party imports
import requests
if LooseVersion(requests.__version__) < LooseVersion('1.1.0'):
print('This script requires python-requests 1.1 as a minimum version')
sys.exit(1)
from requests.auth import HTTPBasicAuth
def json_format_dict(data, pretty=False):
"""Converts a dict to a JSON object and dumps it as a formatted string"""
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
class ForemanInventory(object):
def __init__(self):
self.inventory = defaultdict(list) # A list of groups and the hosts in that group
self.cache = dict() # Details about hosts in the inventory
self.params = dict() # Params of each host
self.facts = dict() # Facts of each host
self.hostgroups = dict() # host groups
self.hostcollections = dict() # host collections
self.session = None # Requests session
self.config_paths = [
"/etc/ansible/foreman.ini",
os.path.dirname(os.path.realpath(__file__)) + '/foreman.ini',
]
env_value = os.environ.get('FOREMAN_INI_PATH')
if env_value is not None:
self.config_paths.append(os.path.expanduser(os.path.expandvars(env_value)))
def read_settings(self):
"""Reads the settings from the foreman.ini file"""
config = ConfigParser.SafeConfigParser()
config.read(self.config_paths)
# Foreman API related
try:
self.foreman_url = config.get('foreman', 'url')
self.foreman_user = config.get('foreman', 'user')
self.foreman_pw = config.get('foreman', 'password')
self.foreman_ssl_verify = config.getboolean('foreman', 'ssl_verify')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e:
print("Error parsing configuration: %s" % e, file=sys.stderr)
return False
# Ansible related
try:
group_patterns = config.get('ansible', 'group_patterns')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
group_patterns = "[]"
self.group_patterns = json.loads(group_patterns)
try:
self.group_prefix = config.get('ansible', 'group_prefix')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.group_prefix = "foreman_"
try:
self.want_facts = config.getboolean('ansible', 'want_facts')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.want_facts = True
try:
self.want_hostcollections = config.getboolean('ansible', 'want_hostcollections')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.want_hostcollections = False
# Do we want parameters to be interpreted if possible as JSON? (no by default)
try:
self.rich_params = config.getboolean('ansible', 'rich_params')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.rich_params = False
try:
self.host_filters = config.get('foreman', 'host_filters')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.host_filters = None
# Cache related
try:
cache_path = os.path.expanduser(config.get('cache', 'path'))
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
cache_path = '.'
(script, ext) = os.path.splitext(os.path.basename(__file__))
self.cache_path_cache = cache_path + "/%s.cache" % script
self.cache_path_inventory = cache_path + "/%s.index" % script
self.cache_path_params = cache_path + "/%s.params" % script
self.cache_path_facts = cache_path + "/%s.facts" % script
self.cache_path_hostcollections = cache_path + "/%s.hostcollections" % script
try:
self.cache_max_age = config.getint('cache', 'max_age')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
self.cache_max_age = 60
return True
def parse_cli_args(self):
"""Command line argument processing"""
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on foreman')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to foreman (default: False - use cache files)')
self.args = parser.parse_args()
def _get_session(self):
if not self.session:
self.session = requests.session()
self.session.auth = HTTPBasicAuth(self.foreman_user, self.foreman_pw)
self.session.verify = self.foreman_ssl_verify
return self.session
def _get_json(self, url, ignore_errors=None, params=None):
if params is None:
params = {}
params['per_page'] = 250
page = 1
results = []
s = self._get_session()
while True:
params['page'] = page
ret = s.get(url, params=params)
if ignore_errors and ret.status_code in ignore_errors:
break
ret.raise_for_status()
json = ret.json()
# /hosts/:id has not results key
if 'results' not in json:
return json
# Facts are returned as dict in results not list
if isinstance(json['results'], dict):
return json['results']
# List of all hosts is returned paginaged
results = results + json['results']
if len(results) >= json['subtotal']:
break
page += 1
if len(json['results']) == 0:
print("Did not make any progress during loop. "
"expected %d got %d" % (json['total'], len(results)),
file=sys.stderr)
break
return results
def _get_hosts(self):
url = "%s/api/v2/hosts" % self.foreman_url
params = {}
if self.host_filters:
params['search'] = self.host_filters
return self._get_json(url, params=params)
def _get_host_data_by_id(self, hid):
url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid)
return self._get_json(url)
def _get_facts_by_id(self, hid):
url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
return self._get_json(url)
def _resolve_params(self, host_params):
"""Convert host params to dict"""
params = {}
for param in host_params:
name = param['name']
if self.rich_params:
try:
params[name] = json.loads(param['value'])
except ValueError:
params[name] = param['value']
else:
params[name] = param['value']
return params
def _get_facts_by_id(self, hid):
url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid)
return self._get_json(url)
def _get_facts(self, host):
"""Fetch all host facts of the host"""
if not self.want_facts:
return {}
ret = self._get_facts_by_id(host['id'])
if len(ret.values()) == 0:
facts = {}
elif len(ret.values()) == 1:
facts = list(ret.values())[0]
else:
raise ValueError("More than one set of facts returned for '%s'" % host)
return facts
def write_to_cache(self, data, filename):
"""Write data in JSON format to a file"""
json_data = json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def _write_cache(self):
self.write_to_cache(self.cache, self.cache_path_cache)
self.write_to_cache(self.inventory, self.cache_path_inventory)
self.write_to_cache(self.params, self.cache_path_params)
self.write_to_cache(self.facts, self.cache_path_facts)
self.write_to_cache(self.hostcollections, self.cache_path_hostcollections)
def to_safe(self, word):
'''Converts 'bad' characters in a string to underscores
so they can be used as Ansible groups
>>> ForemanInventory.to_safe("foo-bar baz")
'foo_barbaz'
'''
regex = "[^A-Za-z0-9\_]"
return re.sub(regex, "_", word.replace(" ", ""))
def update_cache(self):
"""Make calls to foreman and save the output in a cache"""
self.groups = dict()
self.hosts = dict()
for host in self._get_hosts():
dns_name = host['name']
host_data = self._get_host_data_by_id(host['id'])
host_params = host_data.get('all_parameters', {})
# Create ansible groups for hostgroup
group = 'hostgroup'
val = host.get('%s_title' % group) or host.get('%s_name' % group)
if val:
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
self.inventory[safe_key].append(dns_name)
# Create ansible groups for environment, location and organization
for group in ['environment', 'location', 'organization']:
val = host.get('%s_name' % group)
if val:
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
self.inventory[safe_key].append(dns_name)
for group in ['lifecycle_environment', 'content_view']:
val = host.get('content_facet_attributes', {}).get('%s_name' % group)
if val:
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
self.inventory[safe_key].append(dns_name)
params = self._resolve_params(host_params)
# Ansible groups by parameters in host groups and Foreman host
# attributes.
groupby = dict()
for k, v in params.items():
groupby[k] = self.to_safe(str(v))
# The name of the ansible groups is given by group_patterns:
for pattern in self.group_patterns:
try:
key = pattern.format(**groupby)
self.inventory[key].append(dns_name)
except KeyError:
pass # Host not part of this group
if self.want_hostcollections:
hostcollections = host_data.get('host_collections')
if hostcollections:
# Create Ansible groups for host collections
for hostcollection in hostcollections:
safe_key = self.to_safe('%shostcollection_%s' % (self.group_prefix, hostcollection['name'].lower()))
self.inventory[safe_key].append(dns_name)
self.hostcollections[dns_name] = hostcollections
self.cache[dns_name] = host
self.params[dns_name] = params
self.facts[dns_name] = self._get_facts(host)
self.inventory['all'].append(dns_name)
self._write_cache()
def is_cache_valid(self):
"""Determines if the cache is still valid"""
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if (os.path.isfile(self.cache_path_inventory) and
os.path.isfile(self.cache_path_params) and
os.path.isfile(self.cache_path_facts)):
return True
return False
def load_inventory_from_cache(self):
"""Read the index from the cache file sets self.index"""
with open(self.cache_path_inventory, 'r') as fp:
self.inventory = json.load(fp)
def load_params_from_cache(self):
"""Read the index from the cache file sets self.index"""
with open(self.cache_path_params, 'r') as fp:
self.params = json.load(fp)
def load_facts_from_cache(self):
"""Read the index from the cache file sets self.facts"""
if not self.want_facts:
return
with open(self.cache_path_facts, 'r') as fp:
self.facts = json.load(fp)
def load_hostcollections_from_cache(self):
"""Read the index from the cache file sets self.hostcollections"""
if not self.want_hostcollections:
return
with open(self.cache_path_hostcollections, 'r') as fp:
self.hostcollections = json.load(fp)
def load_cache_from_cache(self):
"""Read the cache from the cache file sets self.cache"""
with open(self.cache_path_cache, 'r') as fp:
self.cache = json.load(fp)
def get_inventory(self):
if self.args.refresh_cache or not self.is_cache_valid():
self.update_cache()
else:
self.load_inventory_from_cache()
self.load_params_from_cache()
self.load_facts_from_cache()
self.load_hostcollections_from_cache()
self.load_cache_from_cache()
def get_host_info(self):
"""Get variables about a specific host"""
if not self.cache or len(self.cache) == 0:
# Need to load index from cache
self.load_cache_from_cache()
if self.args.host not in self.cache:
# try updating the cache
self.update_cache()
if self.args.host not in self.cache:
# host might not exist anymore
return json_format_dict({}, True)
return json_format_dict(self.cache[self.args.host], True)
def _print_data(self):
data_to_print = ""
if self.args.host:
data_to_print += self.get_host_info()
else:
self.inventory['_meta'] = {'hostvars': {}}
for hostname in self.cache:
self.inventory['_meta']['hostvars'][hostname] = {
'foreman': self.cache[hostname],
'foreman_params': self.params[hostname],
}
if self.want_facts:
self.inventory['_meta']['hostvars'][hostname]['foreman_facts'] = self.facts[hostname]
data_to_print += json_format_dict(self.inventory, True)
print(data_to_print)
def run(self):
# Read settings and parse CLI arguments
if not self.read_settings():
return False
self.parse_cli_args()
self.get_inventory()
self._print_data()
return True
if __name__ == '__main__':
sys.exit(not ForemanInventory().run())

View File

@ -1,17 +1,13 @@
#!/usr/bin/env python
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import argparse
from ipalib import api
import json
from distutils.version import StrictVersion
def initialize():
'''
This function initializes the FreeIPA/IPA API. This function requires
no arguments. A kerberos key must be present in the users keyring in
no arguments. A kerberos key must be present in the users keyring in
order for this to work.
'''
@ -20,47 +16,35 @@ def initialize():
try:
api.Backend.rpcclient.connect()
except AttributeError:
# FreeIPA < 4.0 compatibility
#FreeIPA < 4.0 compatibility
api.Backend.xmlclient.connect()
return api
def list_groups(api):
'''
This function prints a list of all host groups. This function requires
This function returns a list of all host groups. This function requires
one argument, the FreeIPA/IPA API object.
'''
inventory = {}
hostvars = {}
hostvars={}
meta={}
ipa_version = api.Command.env()['result']['version']
result = api.Command.hostgroup_find()['result']
for hostgroup in result:
# Get direct and indirect members (nested hostgroups) of hostgroup
members = []
if StrictVersion(ipa_version) >= StrictVersion('4.0.0'):
hostgroup_name = hostgroup['cn'][0]
hostgroup = api.Command.hostgroup_show(hostgroup_name)['result']
inventory[hostgroup['cn'][0]] = { 'hosts': [host for host in hostgroup['member_host']]}
if 'member_host' in hostgroup:
members = [host for host in hostgroup['member_host']]
if 'memberindirect_host' in hostgroup:
members += (host for host in hostgroup['memberindirect_host'])
inventory[hostgroup['cn'][0]] = {'hosts': [host for host in members]}
for member in members:
hostvars[member] = {}
for host in hostgroup['member_host']:
hostvars[host] = {}
inventory['_meta'] = {'hostvars': hostvars}
inv_string = json.dumps(inventory, indent=1, sort_keys=True)
print(inv_string)
return None
def parse_args():
'''
This function parses the arguments that were passed in via the command line.
@ -76,11 +60,10 @@ def parse_args():
return parser.parse_args()
def print_host(host):
'''
This function is really a stub, it could return variables to be used in
a playbook. However, at this point there are no variables stored in
This function is really a stub, it could return variables to be used in
a playbook. However, at this point there are no variables stored in
FreeIPA/IPA.
This function expects one string, this hostname to lookup variables for.
@ -90,7 +73,6 @@ def print_host(host):
return None
if __name__ == '__main__':
args = parse_args()

View File

@ -1,3 +1,4 @@
#!/usr/bin/python
# Copyright 2013 Google Inc.
#
# This file is part of Ansible
@ -36,34 +37,11 @@
# exist in your PYTHONPATH and be picked up automatically with an import
# statement in the inventory script. However, you can specify an absolute
# path to the secrets.py file with 'libcloud_secrets' parameter.
# This option will be deprecated in a future release.
libcloud_secrets =
libcloud_secrets =
# If you are not going to use a 'secrets.py' file, you can set the necessary
# authorization parameters here.
gce_service_account_email_address =
gce_service_account_pem_file_path =
gce_project_id =
gce_zone =
gce_service_account_email_address =
gce_service_account_pem_file_path =
gce_project_id =
# Filter inventory based on on state. Leave undefined to return instances regardless of state.
# example: Uncomment to only return inventory in the running or provisioning state
#instance_states = RUNNING,PROVISIONING
[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 =
[cache]
# directory in which cache should be created
cache_path = ~/.ansible/tmp
# The number of seconds a cache file is considered valid. After this many
# seconds, a new API call will be made, and the cache file will be updated.
# To disable the cache, set this value to 0
cache_max_age = 300

View File

@ -40,7 +40,6 @@ based on the data obtained from the libcloud Node object:
- gce_tags
- gce_metadata
- gce_network
- gce_subnetwork
When run in --list mode, instances are grouped by the following categories:
- zone:
@ -70,10 +69,10 @@ Examples:
$ contrib/inventory/gce.py --host my_instance
Author: Eric Johnson <erjohnso@google.com>
Contributors: Matt Hite <mhite@hotmail.com>, Tom Melendez <supertom@google.com>
Version: 0.0.3
Version: 0.0.1
'''
__requires__ = ['pycrypto>=2.6']
try:
import pkg_resources
except ImportError:
@ -83,22 +82,13 @@ except ImportError:
# library is used.
pass
USER_AGENT_PRODUCT = "Ansible-gce_inventory_plugin"
USER_AGENT_VERSION = "v2"
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
USER_AGENT_VERSION="v1"
import sys
import os
import argparse
from time import time
if sys.version_info >= (3, 0):
import configparser
else:
import ConfigParser as configparser
import logging
logging.getLogger('libcloud.common.google').addHandler(logging.NullHandler())
import ConfigParser
try:
import json
@ -110,103 +100,31 @@ try:
from libcloud.compute.providers import get_driver
_ = Provider.GCE
except:
sys.exit("GCE inventory script requires libcloud >= 0.13")
class CloudInventoryCache(object):
def __init__(self, cache_name='ansible-cloud-cache', cache_path='/tmp',
cache_max_age=300):
cache_dir = os.path.expanduser(cache_path)
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
self.cache_path_cache = os.path.join(cache_dir, cache_name)
self.cache_max_age = cache_max_age
def is_valid(self, max_age=None):
''' Determines if the cache files have expired, or if it is still valid '''
if max_age is None:
max_age = self.cache_max_age
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + max_age) > current_time:
return True
return False
def get_all_data_from_cache(self, filename=''):
''' Reads the JSON inventory from the cache file. Returns Python dictionary. '''
data = ''
if not filename:
filename = self.cache_path_cache
with open(filename, 'r') as cache:
data = cache.read()
return json.loads(data)
def write_to_cache(self, data, filename=''):
''' Writes data to file as JSON. Returns True. '''
if not filename:
filename = self.cache_path_cache
json_data = json.dumps(data)
with open(filename, 'w') as cache:
cache.write(json_data)
return True
print("GCE inventory script requires libcloud >= 0.13")
sys.exit(1)
class GceInventory(object):
def __init__(self):
# Cache object
self.cache = None
# dictionary containing inventory read from disk
self.inventory = {}
# 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()
# Cache management
start_inventory_time = time()
cache_used = False
if self.args.refresh_cache or not self.cache.is_valid():
self.do_api_calls_update_cache()
else:
self.load_inventory_from_cache()
cache_used = True
self.inventory['_meta']['stats'] = {'use_cache': True}
self.inventory['_meta']['stats'] = {
'inventory_load_time': time() - start_inventory_time,
'cache_used': cache_used
}
# Just display data for specific host
if self.args.host:
print(self.json_format_dict(
self.inventory['_meta']['hostvars'][self.args.host],
pretty=self.args.pretty))
else:
# Otherwise, assume user wants all instances grouped
zones = self.parse_env_zones()
print(self.json_format_dict(self.inventory,
pretty=self.args.pretty))
print(self.json_format_dict(self.node_to_dict(
self.get_instance(self.args.host)),
pretty=self.args.pretty))
sys.exit(0)
# Otherwise, assume user wants all instances grouped
print(self.json_format_dict(self.group_instances(),
pretty=self.args.pretty))
sys.exit(0)
def get_config(self):
"""
Reads the settings from the gce.ini file.
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.
def get_gce_driver(self):
"""Determine the GCE authorization settings and return a
libcloud driver.
"""
gce_ini_default_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "gce.ini")
@ -216,69 +134,24 @@ class GceInventory(object):
# This provides empty defaults to each key, so that environment
# variable configuration (as opposed to INI configuration) is able
# to work.
config = configparser.SafeConfigParser(defaults={
config = ConfigParser.SafeConfigParser(defaults={
'gce_service_account_email_address': '',
'gce_service_account_pem_file_path': '',
'gce_project_id': '',
'gce_zone': '',
'libcloud_secrets': '',
'inventory_ip_type': '',
'cache_path': '~/.ansible/tmp',
'cache_max_age': '300'
})
if 'gce' not in config.sections():
config.add_section('gce')
if 'inventory' not in config.sections():
config.add_section('inventory')
if 'cache' not in config.sections():
config.add_section('cache')
config.read(gce_ini_path)
#########
# Section added for processing ini settings
#########
# Set the instance_states filter based on config file options
self.instance_states = []
if config.has_option('gce', 'instance_states'):
states = config.get('gce', 'instance_states')
# Ignore if instance_states is an empty string.
if states:
self.instance_states = states.split(',')
# Caching
cache_path = config.get('cache', 'cache_path')
cache_max_age = config.getint('cache', 'cache_max_age')
# TOOD(supertom): support project-specific caches
cache_name = 'ansible-gce.cache'
self.cache = CloudInventoryCache(cache_path=cache_path,
cache_max_age=cache_max_age,
cache_name=cache_name)
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 = self.config.get('gce', 'libcloud_secrets')
secrets_path = config.get('gce', 'libcloud_secrets')
secrets_found = False
try:
import secrets
args = list(secrets.GCE_PARAMS)
kwargs = secrets.GCE_KEYWORD_PARAMS
args = list(getattr(secrets, 'GCE_PARAMS', []))
kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
secrets_found = True
except:
pass
@ -287,7 +160,8 @@ class GceInventory(object):
if not secrets_path.endswith('secrets.py'):
err = "Must specify libcloud secrets file as "
err += "/absolute/path/to/secrets.py"
sys.exit(err)
print(err)
sys.exit(1)
sys.path.append(os.path.dirname(secrets_path))
try:
import secrets
@ -296,23 +170,18 @@ class GceInventory(object):
secrets_found = True
except:
pass
if not secrets_found:
args = [
self.config.get('gce', 'gce_service_account_email_address'),
self.config.get('gce', 'gce_service_account_pem_file_path')
config.get('gce','gce_service_account_email_address'),
config.get('gce','gce_service_account_pem_file_path')
]
kwargs = {'project': self.config.get('gce', 'gce_project_id'),
'datacenter': self.config.get('gce', 'gce_zone')}
kwargs = {'project': config.get('gce', 'gce_project_id')}
# If the appropriate environment variables are set, they override
# other configuration; process those into our args and kwargs.
args[0] = os.environ.get('GCE_EMAIL', args[0])
args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1])
args[1] = os.environ.get('GCE_CREDENTIALS_FILE_PATH', args[1])
kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project'])
kwargs['datacenter'] = os.environ.get('GCE_ZONE', kwargs['datacenter'])
# Retrieve and return the GCE driver.
gce = get_driver(Provider.GCE)(*args, **kwargs)
@ -321,50 +190,31 @@ class GceInventory(object):
)
return gce
def parse_env_zones(self):
'''returns a list of comma separated zones parsed from the GCE_ZONE environment variable.
If provided, this will be used to filter the results of the grouped_instances call'''
import csv
reader = csv.reader([os.environ.get('GCE_ZONE', "")], skipinitialspace=True)
zones = [r for r in reader]
return [z for z in zones[0]]
def parse_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(
description='Produce an Ansible Inventory file based on GCE')
description='Produce an Ansible Inventory file based on GCE')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
help='List instances (default: True)')
parser.add_argument('--host', action='store',
help='Get all information about an instance')
help='Get all information about an instance')
parser.add_argument('--pretty', action='store_true', default=False,
help='Pretty format (default: False)')
parser.add_argument(
'--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests (default: False - use cache files)')
help='Pretty format (default: False)')
self.args = parser.parse_args()
def node_to_dict(self, inst):
md = {}
if inst is None:
return {}
if 'items' in inst.extra['metadata']:
if inst.extra['metadata'].has_key('items'):
for entry in inst.extra['metadata']['items']:
md[entry['key']] = entry['value']
net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
subnet = None
if 'subnetwork' in inst.extra['networkInterfaces'][0]:
subnet = inst.extra['networkInterfaces'][0]['subnetwork'].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,
@ -379,72 +229,31 @@ class GceInventory(object):
'gce_tags': inst.extra['tags'],
'gce_metadata': md,
'gce_network': net,
'gce_subnetwork': subnet,
# Hosts don't have a public name, so we add an IP
'ansible_ssh_host': ssh_host
'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]
}
def load_inventory_from_cache(self):
''' Loads inventory from JSON on disk. '''
def get_instance(self, instance_name):
'''Gets details about a specific instance '''
try:
self.inventory = self.cache.get_all_data_from_cache()
hosts = self.inventory['_meta']['hostvars']
return self.driver.ex_get_node(instance_name)
except Exception as e:
print(
"Invalid inventory file %s. Please rebuild with -refresh-cache option."
% (self.cache.cache_path_cache))
raise
return None
def do_api_calls_update_cache(self):
''' Do API calls and save data in cache. '''
zones = self.parse_env_zones()
data = self.group_instances(zones)
self.cache.write_to_cache(data)
self.inventory = data
def list_nodes(self):
all_nodes = []
params, more_results = {'maxResults': 500}, True
while more_results:
self.driver.connection.gce_params = params
all_nodes.extend(self.driver.list_nodes())
more_results = 'pageToken' in params
return all_nodes
def group_instances(self, zones=None):
def group_instances(self):
'''Group all instances'''
groups = {}
meta = {}
meta["hostvars"] = {}
for node in self.list_nodes():
# This check filters on the desired instance states defined in the
# config file with the instance_states config option.
#
# If the instance_states list is _empty_ then _ALL_ states are returned.
#
# If the instance_states list is _populated_ then check the current
# state against the instance_states list
if self.instance_states and not node.extra['status'] in self.instance_states:
continue
for node in self.driver.list_nodes():
name = node.name
meta["hostvars"][name] = self.node_to_dict(node)
zone = node.extra['zone'].name
# To avoid making multiple requests per zone
# we list all nodes and then filter the results
if zones and zone not in zones:
continue
if zone in groups:
groups[zone].append(name)
else:
groups[zone] = [name]
if groups.has_key(zone): groups[zone].append(name)
else: groups[zone] = [name]
tags = node.extra['tags']
for t in tags:
@ -452,43 +261,26 @@ class GceInventory(object):
tag = t[6:]
else:
tag = 'tag_%s' % t
if tag in groups:
groups[tag].append(name)
else:
groups[tag] = [name]
if groups.has_key(tag): groups[tag].append(name)
else: groups[tag] = [name]
net = node.extra['networkInterfaces'][0]['network'].split('/')[-1]
net = 'network_%s' % net
if net in groups:
groups[net].append(name)
else:
groups[net] = [name]
if groups.has_key(net): groups[net].append(name)
else: groups[net] = [name]
machine_type = node.size
if machine_type in groups:
groups[machine_type].append(name)
else:
groups[machine_type] = [name]
if groups.has_key(machine_type): groups[machine_type].append(name)
else: groups[machine_type] = [name]
image = node.image and node.image or 'persistent_disk'
if image in groups:
groups[image].append(name)
else:
groups[image] = [name]
if groups.has_key(image): groups[image].append(name)
else: groups[image] = [name]
status = node.extra['status']
stat = 'status_%s' % status.lower()
if stat in groups:
groups[stat].append(name)
else:
groups[stat] = [name]
for private_ip in node.private_ips:
groups[private_ip] = [name]
if len(node.public_ips) >= 1:
for public_ip in node.public_ips:
groups[public_ip] = [name]
if groups.has_key(stat): groups[stat].append(name)
else: groups[stat] = [name]
groups["_meta"] = meta
@ -503,6 +295,6 @@ class GceInventory(object):
else:
return json.dumps(data)
# Run the script
if __name__ == '__main__':
GceInventory()
GceInventory()

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from subprocess import Popen, PIPE
from subprocess import Popen,PIPE
import sys
import json
@ -34,4 +34,4 @@ if len(sys.argv) == 2 and sys.argv[1] == '--list':
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
print(json.dumps({'ansible_connection': 'jail'}))
else:
sys.stderr.write("Need an argument, either --list or --host <host>\n")
print("Need an argument, either --list or --host <host>")

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from subprocess import Popen, PIPE
from subprocess import Popen,PIPE
import sys
import json
@ -34,4 +34,4 @@ if len(sys.argv) == 2 and sys.argv[1] == '--list':
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
print(json.dumps({'ansible_connection': 'libvirt_lxc'}))
else:
sys.stderr.write("Need an argument, either --list or --host <host>\n")
print("Need an argument, either --list or --host <host>")

View File

@ -13,6 +13,3 @@ cache_path = /tmp
# The number of seconds a cache file is considered valid. After this many
# seconds, a new API call will be made, and the cache file will be updated.
cache_max_age = 300
# If set to true use the hosts public ip in the dictionary instead of the label
use_public_ip = false

View File

@ -15,13 +15,9 @@ installed and has a valid config at ~/.chube. If not, run:
For more details, see: https://github.com/exosite/chube
NOTE: By default, this script also assumes that the Linodes in your account all have
NOTE: This script also assumes that the Linodes in your account all have
labels that correspond to hostnames that are in your resolver search path.
Your resolver search path resides in /etc/hosts.
Optionally, if you would like to use the hosts public IP instead of it's label use
the following setting in linode.ini:
use_public_ip = true
When run against a specific host, this script returns the following variables:
@ -113,15 +109,11 @@ load_chube_config()
# Imports for ansible
import ConfigParser
class LinodeInventory(object):
def _empty_inventory(self):
return {"_meta": {"hostvars": {}}}
def __init__(self):
"""Main execution path."""
# Inventory grouped by display group
self.inventory = self._empty_inventory()
self.inventory = {}
# Index of label to Linode ID
self.index = {}
# Local cache of Datacenter objects populated by populate_datacenter_cache()
@ -142,7 +134,7 @@ class LinodeInventory(object):
data_to_print = self.get_host_info()
elif self.args.list:
# Display list of nodes for inventory
if len(self.inventory) == 1:
if len(self.inventory) == 0:
data_to_print = self.get_inventory_from_cache()
else:
data_to_print = self.json_format_dict(self.inventory, True)
@ -169,17 +161,16 @@ class LinodeInventory(object):
self.cache_path_cache = cache_path + "/ansible-linode.cache"
self.cache_path_index = cache_path + "/ansible-linode.index"
self.cache_max_age = config.getint('linode', 'cache_max_age')
self.use_public_ip = config.getboolean('linode', 'use_public_ip')
def parse_cli_args(self):
"""Command line argument processing"""
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Linode')
parser.add_argument('--list', action='store_true', default=True,
help='List nodes (default: True)')
help='List nodes (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific node')
help='Get all the variables about a specific node')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to Linode (default: False - use cache files)')
help='Force refresh of cache by making API requests to Linode (default: False - use cache files)')
self.args = parser.parse_args()
def do_api_calls_update_cache(self):
@ -194,14 +185,20 @@ class LinodeInventory(object):
for node in Linode.search(status=Linode.STATUS_RUNNING):
self.add_node(node)
except chube_api.linode_api.ApiError as e:
sys.exit("Looks like Linode's API is down:\n %s" % e)
print("Looks like Linode's API is down:")
print("")
print(e)
sys.exit(1)
def get_node(self, linode_id):
"""Gets details about a specific node."""
try:
return Linode.find(api_id=linode_id)
except chube_api.linode_api.ApiError as e:
sys.exit("Looks like Linode's API is down:\n%s" % e)
print("Looks like Linode's API is down:")
print("")
print(e)
sys.exit(1)
def populate_datacenter_cache(self):
"""Creates self._datacenter_cache, containing all Datacenters indexed by ID."""
@ -221,10 +218,8 @@ class LinodeInventory(object):
def add_node(self, node):
"""Adds an node to the inventory and index."""
if self.use_public_ip:
dest = self.get_node_public_ip(node)
else:
dest = node.label
dest = node.label
# Add to index
self.index[dest] = node.api_id
@ -235,19 +230,9 @@ class LinodeInventory(object):
# Inventory: Group by datacenter city
self.push(self.inventory, self.get_datacenter_city(node), dest)
# Inventory: Group by display group
# Inventory: Group by dipslay group
self.push(self.inventory, node.display_group, dest)
# Inventory: Add a "linode" global tag group
self.push(self.inventory, "linode", dest)
# Add host info to hostvars
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info(node)
def get_node_public_ip(self, node):
"""Returns a the public IP address of the node"""
return [addr.address for addr in node.ipaddresses if addr.is_public][0]
def get_host_info(self):
"""Get variables about a specific host."""
@ -255,19 +240,16 @@ class LinodeInventory(object):
# Need to load index from cache
self.load_index_from_cache()
if self.args.host not in self.index:
if not self.args.host in self.index:
# try updating the cache
self.do_api_calls_update_cache()
if self.args.host not in self.index:
if not self.args.host in self.index:
# host might not exist anymore
return self.json_format_dict({}, True)
node_id = self.index[self.args.host]
node = self.get_node(node_id)
return self.json_format_dict(self.get_host_info(node), True)
def get_host_info(self, node):
node_vars = {}
for direct_attr in [
"api_id",
@ -296,24 +278,19 @@ class LinodeInventory(object):
node_vars[direct_attr] = getattr(node, direct_attr)
node_vars["datacenter_city"] = self.get_datacenter_city(node)
node_vars["public_ip"] = self.get_node_public_ip(node)
# Set the SSH host information, so these inventory items can be used if
# their labels aren't FQDNs
node_vars['ansible_ssh_host'] = node_vars["public_ip"]
node_vars['ansible_host'] = node_vars["public_ip"]
node_vars["public_ip"] = [addr.address for addr in node.ipaddresses if addr.is_public][0]
private_ips = [addr.address for addr in node.ipaddresses if not addr.is_public]
if private_ips:
node_vars["private_ip"] = private_ips[0]
return node_vars
return self.json_format_dict(node_vars, True)
def push(self, my_dict, key, element):
"""Pushed an element onto an array that may not have been defined in the dict."""
if key in my_dict:
my_dict[key].append(element)
my_dict[key].append(element);
else:
my_dict[key] = [element]

View File

@ -1,69 +0,0 @@
#!/usr/bin/env python
#
# (c) 2015-16 Florian Haas, hastexo Professional Services GmbH
# <florian@hastexo.com>
# Based in part on:
# libvirt_lxc.py, (c) 2013, Michael Scherer <misc@zarb.org>
#
# 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/>.
"""
Ansible inventory script for LXC containers. Requires Python
bindings for LXC API.
In LXC, containers can be grouped by setting the lxc.group option,
which may be found more than once in a container's
configuration. So, we enumerate all containers, fetch their list
of groups, and then build the dictionary in the way Ansible expects
it.
"""
from __future__ import print_function
import sys
import lxc
import json
def build_dict():
"""Returns a dictionary keyed to the defined LXC groups. All
containers, including the ones not in any group, are included in the
"all" group."""
# Enumerate all containers, and list the groups they are in. Also,
# implicitly add every container to the 'all' group.
containers = dict([(c,
['all'] +
(lxc.Container(c).get_config_item('lxc.group') or []))
for c in lxc.list_containers()])
# Extract the groups, flatten the list, and remove duplicates
groups = set(sum([g for g in containers.values()], []))
# Create a dictionary for each group (including the 'all' group
return dict([(g, {'hosts': [k for k, v in containers.items() if g in v],
'vars': {'ansible_connection': 'lxc'}}) for g in groups])
def main(argv):
"""Returns a JSON dictionary as expected by Ansible"""
result = build_dict()
if len(argv) == 2 and argv[1] == '--list':
json.dump(result, sys.stdout)
elif len(argv) == 3 and argv[1] == '--host':
json.dump({'ansible_connection': 'lxc'}, sys.stdout)
else:
print("Need an argument, either --list or --host <host>", file=sys.stderr)
if __name__ == '__main__':
main(sys.argv)

View File

@ -1,13 +0,0 @@
# LXD external inventory script settings
[lxd]
# The default resource
#resource = local:
# The group name to add the hosts to
#group = lxd
# The connection type to return for these hosts - lxd hasn't been tested yet
#connection = lxd
connection = smart

View File

@ -1,103 +0,0 @@
#!/usr/bin/env python
# (c) 2013, Michael Scherer <misc@zarb.org>
# (c) 2014, Hiroaki Nakamura <hnakamur@gmail.com>
# (c) 2016, Andew Clarke <andrew@oscailte.org>
#
# This file is based on https://github.com/ansible/ansible/blob/devel/plugins/inventory/libvirt_lxc.py which is part of Ansible,
# and https://github.com/hnakamur/lxc-ansible-playbooks/blob/master/provisioning/inventory-lxc.py
#
# NOTE, this file has some obvious limitations, improvements welcome
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
from subprocess import Popen, PIPE
import distutils.spawn
import sys
import json
try:
import configparser
except:
from six.moves import configparser
# Set up defaults
resource = 'local:'
group = 'lxd'
connection = 'lxd'
hosts = {}
result = {}
# Read the settings from the lxd.ini file
config = configparser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/lxd.ini')
if config.has_option('lxd', 'resource'):
resource = config.get('lxd', 'resource')
if config.has_option('lxd', 'group'):
group = config.get('lxd', 'group')
if config.has_option('lxd', 'connection'):
connection = config.get('lxd', 'connection')
# Ensure executable exists
if distutils.spawn.find_executable('lxc'):
# Set up containers result and hosts array
result[group] = {}
result[group]['hosts'] = []
# Run the command and load json result
pipe = Popen(['lxc', 'list', resource, '--format', 'json'], stdout=PIPE, universal_newlines=True)
lxdjson = json.load(pipe.stdout)
# Iterate the json lxd output
for item in lxdjson:
# Check state and network
if 'state' in item and item['state'] is not None and 'network' in item['state']:
network = item['state']['network']
# Check for eth0 and addresses
if 'eth0' in network and 'addresses' in network['eth0']:
addresses = network['eth0']['addresses']
# Iterate addresses
for address in addresses:
# Only return inet family addresses
if 'family' in address and address['family'] == 'inet':
if 'address' in address:
ip = address['address']
name = item['name']
# Add the host to the results and the host array
result[group]['hosts'].append(name)
hosts[name] = ip
# Set the other containers result values
result[group]['vars'] = {}
result[group]['vars']['ansible_connection'] = connection
# Process arguments
if len(sys.argv) == 2 and sys.argv[1] == '--list':
print(json.dumps(result))
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
if sys.argv[2] == 'localhost':
print(json.dumps({'ansible_connection': 'local'}))
else:
if connection == 'lxd':
print(json.dumps({'ansible_connection': connection}))
else:
print(json.dumps({'ansible_connection': connection, 'ansible_host': hosts[sys.argv[2]]}))
else:
print("Need an argument, either --list or --host <host>")

View File

@ -1,17 +0,0 @@
[mdt]
# Set the MDT server to connect to
server = localhost.example.com
# Set the MDT Instance
instance = EXAMPLEINSTANCE
# Set the MDT database
database = MDTDB
# Configure login credentials
user = local.domain\admin
password = adminpassword
[tower]
groupname = mdt

View File

@ -1,134 +0,0 @@
#!/usr/bin/env python
# (c) 2016, Julian Barnett <jbarnett@tableau.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/>.
'''
MDT external inventory script
=================================
author: J Barnett 06/23/2016 01:15
maintainer: J Barnett (github @jbarnett1981)
'''
import argparse
import json
import pymssql
try:
import configparser
except ImportError:
import ConfigParser as configparser
class MDTInventory(object):
def __init__(self):
''' Main execution path '''
self.conn = None
# Initialize empty inventory
self.inventory = self._empty_inventory()
# Read CLI arguments
self.read_settings()
self.parse_cli_args()
# Get Hosts
if self.args.list:
self.get_hosts()
# Get specific host vars
if self.args.host:
self.get_hosts(self.args.host)
def _connect(self, query):
'''
Connect to MDT and dump contents of dbo.ComputerIdentity database
'''
if not self.conn:
self.conn = pymssql.connect(server=self.mdt_server + "\\" + self.mdt_instance, user=self.mdt_user, password=self.mdt_password,
database=self.mdt_database)
cursor = self.conn.cursor()
cursor.execute(query)
self.mdt_dump = cursor.fetchall()
self.conn.close()
def get_hosts(self, hostname=False):
'''
Gets host from MDT Database
'''
if hostname:
query = ("SELECT t1.ID, t1.Description, t1.MacAddress, t2.Role "
"FROM ComputerIdentity as t1 join Settings_Roles as t2 on t1.ID = t2.ID where t1.Description = '%s'" % hostname)
else:
query = 'SELECT t1.ID, t1.Description, t1.MacAddress, t2.Role FROM ComputerIdentity as t1 join Settings_Roles as t2 on t1.ID = t2.ID'
self._connect(query)
# Configure to group name configured in Ansible Tower for this inventory
groupname = self.mdt_groupname
# Initialize empty host list
hostlist = []
# Parse through db dump and populate inventory
for hosts in self.mdt_dump:
self.inventory['_meta']['hostvars'][hosts[1]] = {'id': hosts[0], 'name': hosts[1], 'mac': hosts[2], 'role': hosts[3]}
hostlist.append(hosts[1])
self.inventory[groupname] = hostlist
# Print it all out
print(json.dumps(self.inventory, indent=2))
def _empty_inventory(self):
'''
Create empty inventory dictionary
'''
return {"_meta": {"hostvars": {}}}
def read_settings(self):
'''
Reads the settings from the mdt.ini file
'''
config = configparser.SafeConfigParser()
config.read('mdt.ini')
# MDT Server and instance and database
self.mdt_server = config.get('mdt', 'server')
self.mdt_instance = config.get('mdt', 'instance')
self.mdt_database = config.get('mdt', 'database')
# MDT Login credentials
if config.has_option('mdt', 'user'):
self.mdt_user = config.get('mdt', 'user')
if config.has_option('mdt', 'password'):
self.mdt_password = config.get('mdt', 'password')
# Group name in Tower
if config.has_option('tower', 'groupname'):
self.mdt_groupname = config.get('tower', 'groupname')
def parse_cli_args(self):
'''
Command line argument processing
'''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on MDT')
parser.add_argument('--list', action='store_true', default=False, help='List instances')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
self.args = parser.parse_args()
if __name__ == "__main__":
# Run the script
MDTInventory()

View File

@ -1,41 +0,0 @@
# Ansible Nagios external inventory script settings
#
# To get all available possibilities, check following URL:
# http://www.naemon.org/documentation/usersguide/livestatus.html
# https://mathias-kettner.de/checkmk_livestatus.html
#
[local]
# Livestatus URI
# Example for default naemon livestatus unix socket :
# livestatus_uri=unix:/var/cache/naemon/live
[remote]
# default field name for host: name
# Uncomment to override:
# host_field=address
#
# default field group for host: groups
# Uncomment to override:
# group_field=state
# default fields retrieved: address, alias, display_name, children, parents
# To override, uncomment the following line
# fields_to_retrieve=address,alias,display_name
#
# default variable prefix: livestatus_
# To override, uncomment the following line
# var_prefix=naemon_
#
# default filter: None
#
# Uncomment to override
#
# All host with state = OK
# host_filter=state = 0
# Warning: for the moment, you can use only one filter at a time. You cannot combine various conditions.
#
# All host in groups Linux
# host_filter=groups >= Linux
#
livestatus_uri=tcp:192.168.66.137:6557

View File

@ -1,177 +0,0 @@
#!/usr/bin/env python
# (c) 2015, Yannig Perre <yannig.perre@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/>.
'''
Nagios livestatus inventory script. Before using this script, please
update nagios_livestatus.ini file.
Livestatus is a nagios/naemon/shinken module which let you retrieve
informations stored in the monitoring core.
This plugin inventory need livestatus API for python. Please install it
before using this script (apt/pip/yum/...).
Checkmk livestatus: https://mathias-kettner.de/checkmk_livestatus.html
Livestatus API: http://www.naemon.org/documentation/usersguide/livestatus.html
'''
import os
import re
import argparse
try:
import configparser
except ImportError:
import ConfigParser
configparser = ConfigParser
import json
try:
from mk_livestatus import Socket
except ImportError:
print("Error: mk_livestatus is needed. Try something like: pip install python-mk-livestatus")
exit(1)
class NagiosLivestatusInventory(object):
def parse_ini_file(self):
config = configparser.SafeConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__)) + '/nagios_livestatus.ini')
for section in config.sections():
if not config.has_option(section, 'livestatus_uri'):
continue
# If fields_to_retrieve is not set, using default fields
fields_to_retrieve = self.default_fields_to_retrieve
if config.has_option(section, 'fields_to_retrieve'):
fields_to_retrieve = [field.strip() for field in config.get(section, 'fields_to_retrieve').split(',')]
fields_to_retrieve = tuple(fields_to_retrieve)
# default section values
section_values = {
'var_prefix': 'livestatus_',
'host_filter': None,
'host_field': 'name',
'group_field': 'groups'
}
for key, value in section_values.items():
if config.has_option(section, key):
section_values[key] = config.get(section, key).strip()
# Retrieving livestatus string connection
livestatus_uri = config.get(section, 'livestatus_uri')
backend_definition = None
# Local unix socket
unix_match = re.match('unix:(.*)', livestatus_uri)
if unix_match is not None:
backend_definition = {'connection': unix_match.group(1)}
# Remote tcp connection
tcp_match = re.match('tcp:(.*):([^:]*)', livestatus_uri)
if tcp_match is not None:
backend_definition = {'connection': (tcp_match.group(1), int(tcp_match.group(2)))}
# No valid livestatus_uri => exiting
if backend_definition is None:
raise Exception('livestatus_uri field is invalid (%s). Expected: unix:/path/to/live or tcp:host:port' % livestatus_uri)
# Updating backend_definition with current value
backend_definition['name'] = section
backend_definition['fields'] = fields_to_retrieve
for key, value in section_values.items():
backend_definition[key] = value
self.backends.append(backend_definition)
def parse_options(self):
parser = argparse.ArgumentParser()
parser.add_argument('--host', nargs=1)
parser.add_argument('--list', action='store_true')
parser.add_argument('--pretty', action='store_true')
self.options = parser.parse_args()
def add_host(self, hostname, group):
if group not in self.result:
self.result[group] = {}
self.result[group]['hosts'] = []
if hostname not in self.result[group]['hosts']:
self.result[group]['hosts'].append(hostname)
def query_backend(self, backend, host=None):
'''Query a livestatus backend'''
hosts_request = Socket(backend['connection']).hosts.columns(backend['host_field'], backend['group_field'])
if backend['host_filter'] is not None:
hosts_request = hosts_request.filter(backend['host_filter'])
if host is not None:
hosts_request = hosts_request.filter('name = ' + host[0])
hosts_request._columns += backend['fields']
hosts = hosts_request.call()
for host in hosts:
hostname = host[backend['host_field']]
hostgroups = host[backend['group_field']]
if not isinstance(hostgroups, list):
hostgroups = [hostgroups]
self.add_host(hostname, 'all')
self.add_host(hostname, backend['name'])
for group in hostgroups:
self.add_host(hostname, group)
for field in backend['fields']:
var_name = backend['var_prefix'] + field
if hostname not in self.result['_meta']['hostvars']:
self.result['_meta']['hostvars'][hostname] = {}
self.result['_meta']['hostvars'][hostname][var_name] = host[field]
def __init__(self):
self.defaultgroup = 'group_all'
self.default_fields_to_retrieve = ('address', 'alias', 'display_name', 'childs', 'parents')
self.backends = []
self.options = None
self.parse_ini_file()
self.parse_options()
self.result = {}
self.result['_meta'] = {}
self.result['_meta']['hostvars'] = {}
self.json_indent = None
if self.options.pretty:
self.json_indent = 2
if len(self.backends) == 0:
print("Error: Livestatus configuration is missing. See nagios_livestatus.ini.")
exit(1)
for backend in self.backends:
self.query_backend(backend, self.options.host)
if self.options.host:
print(json.dumps(self.result['_meta']['hostvars'][self.options.host[0]], indent=self.json_indent))
elif self.options.list:
print(json.dumps(self.result, indent=self.json_indent))
else:
print("usage: --list or --host HOSTNAME [--pretty]")
exit(1)
NagiosLivestatusInventory()

View File

@ -42,7 +42,6 @@ except ImportError:
print("Error: SQLAlchemy is needed. Try something like: pip install sqlalchemy")
exit(1)
class NagiosNDOInventory(object):
def read_settings(self):

View File

@ -26,7 +26,7 @@ import re
import os
import ConfigParser
from novaclient import client as nova_client
from six import iteritems, itervalues
from six import iteritems
try:
import json
@ -105,7 +105,7 @@ def get_ips(server, access_ip=True):
# Iterate through each servers network(s), get addresses and get type
addresses = getattr(server, 'addresses', {})
if len(addresses) > 0:
for network in itervalues(addresses):
for network in addresses.itervalues():
for address in network:
if address.get('OS-EXT-IPS:type', False) == 'fixed':
private.append(address['addr'])
@ -142,7 +142,7 @@ def get_metadata(server):
key = 'os_' + re.sub(r"[^A-Za-z0-9\-]", "_", key).lower()
# Att value to instance result (exclude manager class)
# TODO: maybe use value.__class__ or similar inside of key_name
#TODO: maybe use value.__class__ or similar inside of key_name
if key != 'os_manager':
results[key] = value
return results
@ -226,4 +226,5 @@ elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
sys.exit(0)
else:
sys.exit("usage: --list ..OR.. --host <hostname>")
print("usage: --list ..OR.. --host <hostname>")
sys.exit(1)

14
contrib/inventory/nsot.py Executable file → Normal file
View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/bin/env python
'''
nsot
@ -148,11 +148,9 @@ from pynsot.client import get_api_client
from pynsot.app import HttpServerError
from click.exceptions import UsageError
from six import string_types
def warning(*objs):
print("WARNING: ", *objs, file=sys.stderr)
print("WARNING: ", *objs, file=sys.stderr)
class NSoTInventory(object):
@ -202,7 +200,7 @@ class NSoTInventory(object):
_inventory_group()
'''
inventory = dict()
for group, contents in self.config.items():
for group, contents in self.config.iteritems():
group_response = self._inventory_group(group, contents)
inventory.update(group_response)
inventory.update({'_meta': self._meta})
@ -217,7 +215,7 @@ class NSoTInventory(object):
Depending on number of devices in NSoT, could be rather slow since this
has to request every device resource to filter through
'''
device = [i for i in self.client.devices.get()
device = [i for i in self.client.devices.get()['data']['devices']
if host in i['hostname']][0]
attributes = device['attributes']
attributes.update({'site_id': device['site_id'], 'id': device['id']})
@ -253,7 +251,7 @@ class NSoTInventory(object):
obj[group]['hosts'] = []
obj[group]['vars'] = hostvars
try:
assert isinstance(query, string_types)
assert isinstance(query, basestring)
except:
sys.exit('ERR: Group queries must be a single string\n'
' Group: %s\n'
@ -278,7 +276,7 @@ class NSoTInventory(object):
# Would do a list comprehension here, but would like to save code/time
# and also acquire attributes in this step
for host in devices:
for host in devices['data']['devices']:
# Iterate through each device that matches query, assign hostname
# to the group's hosts array and then use this single iteration as
# a chance to update self._meta which will be used in the final

View File

@ -62,7 +62,8 @@ def get_config(env_var, config_var):
if not result:
result = get_from_rhc_config(config_var)
if not result:
sys.exit("failed=True msg='missing %s'" % env_var)
print("failed=True msg='missing %s'" % env_var)
sys.exit(1)
return result

View File

@ -3,7 +3,6 @@
# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com>
# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com>
# Copyright (c) 2015, Hewlett-Packard Development Company, L.P.
# Copyright (c) 2016, Rackspace Australia
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,7 +18,7 @@
# along with this software. If not, see <http://www.gnu.org/licenses/>.
# The OpenStack Inventory module uses os-client-config for configuration.
# https://github.com/openstack/os-client-config
# https://github.com/stackforge/os-client-config
# This means it will either:
# - Respect normal OS_* environment variables like other OpenStack tools
# - Read values from a clouds.yaml file.
@ -29,11 +28,8 @@
# - /etc/openstack/clouds.yaml
# - /etc/ansible/openstack.yml
# The clouds.yaml file can contain entries for multiple clouds and multiple
# regions of those clouds. If it does, this inventory module will by default
# connect to all of them and present them as one contiguous inventory. You
# can limit to one cloud by passing the `--cloud` parameter, or use the
# OS_CLOUD environment variable. If caching is enabled, and a cloud is
# selected, then per-cloud cache folders will be used.
# regions of those clouds. If it does, this inventory module will connect to
# all of them and present them as one contiguous inventory.
#
# See the adjacent openstack.yml file for an example config file
# There are two ansible inventory specific options that can be set in
@ -43,20 +39,12 @@
# use_hostnames changes the behavior from registering every host with its UUID
# and making a group of its hostname to only doing this if the
# hostname in question has more than one server
# fail_on_errors causes the inventory to fail and return no hosts if one cloud
# has failed (for example, bad credentials or being offline).
# When set to False, the inventory will return hosts from
# whichever other clouds it can contact. (Default: True)
#
# Also it is possible to pass the correct user by setting an ansible_user: $myuser
# metadata attribute.
import argparse
import collections
import os
import sys
import time
from distutils.version import StrictVersion
try:
import json
@ -67,7 +55,7 @@ import os_client_config
import shade
import shade.inventory
CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml']
CONFIG_FILES = ['/etc/ansible/openstack.yaml']
def get_groups_from_server(server_vars, namegroup=True):
@ -92,7 +80,7 @@ def get_groups_from_server(server_vars, namegroup=True):
for extra_group in metadata.get('groups', '').split(','):
if extra_group:
groups.append(extra_group.strip())
groups.append(extra_group)
groups.append('instance-%s' % server_vars['id'])
if namegroup:
@ -114,8 +102,8 @@ def get_groups_from_server(server_vars, namegroup=True):
return groups
def get_host_groups(inventory, refresh=False, cloud=None):
(cache_file, cache_expiration_time) = get_cache_settings(cloud)
def get_host_groups(inventory, refresh=False):
(cache_file, cache_expiration_time) = get_cache_settings()
if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh):
groups = to_json(get_host_groups_from_cloud(inventory))
open(cache_file, 'w').write(groups)
@ -124,20 +112,6 @@ def get_host_groups(inventory, refresh=False, cloud=None):
return groups
def append_hostvars(hostvars, groups, key, server, namegroup=False):
hostvars[key] = dict(
ansible_ssh_host=server['interface_ip'],
ansible_host=server['interface_ip'],
openstack=server)
metadata = server.get('metadata', {})
if 'ansible_user' in metadata:
hostvars[key]['ansible_user'] = metadata['ansible_user']
for group in get_groups_from_server(server, namegroup=namegroup):
groups[group].append(key)
def get_host_groups_from_cloud(inventory):
groups = collections.defaultdict(list)
firstpass = collections.defaultdict(list)
@ -146,9 +120,6 @@ def get_host_groups_from_cloud(inventory):
if hasattr(inventory, 'extra_config'):
use_hostnames = inventory.extra_config['use_hostnames']
list_args['expand'] = inventory.extra_config['expand_hostvars']
if StrictVersion(shade.__version__) >= StrictVersion("1.6.0"):
list_args['fail_on_cloud_config'] = \
inventory.extra_config['fail_on_errors']
else:
use_hostnames = False
@ -159,19 +130,20 @@ def get_host_groups_from_cloud(inventory):
firstpass[server['name']].append(server)
for name, servers in firstpass.items():
if len(servers) == 1 and use_hostnames:
append_hostvars(hostvars, groups, name, servers[0])
server = servers[0]
hostvars[name] = dict(
ansible_ssh_host=server['interface_ip'],
openstack=server)
for group in get_groups_from_server(server, namegroup=False):
groups[group].append(server['name'])
else:
server_ids = set()
# Trap for duplicate results
for server in servers:
server_ids.add(server['id'])
if len(server_ids) == 1 and use_hostnames:
append_hostvars(hostvars, groups, name, servers[0])
else:
for server in servers:
append_hostvars(
hostvars, groups, server['id'], server,
namegroup=True)
server_id = server['id']
hostvars[server_id] = dict(
ansible_ssh_host=server['interface_ip'],
openstack=server)
for group in get_groups_from_server(server, namegroup=True):
groups[group].append(server_id)
groups['_meta'] = {'hostvars': hostvars}
return groups
@ -180,7 +152,7 @@ def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
''' Determines if cache file has expired, or if it is still valid '''
if refresh:
return True
if os.path.isfile(cache_file) and os.path.getsize(cache_file) > 0:
if os.path.isfile(cache_file):
mod_time = os.path.getmtime(cache_file)
current_time = time.time()
if (mod_time + cache_expiration_time) > current_time:
@ -188,14 +160,12 @@ def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
return True
def get_cache_settings(cloud=None):
def get_cache_settings():
config = os_client_config.config.OpenStackConfig(
config_files=os_client_config.config.CONFIG_FILES + CONFIG_FILES)
# For inventory-wide caching
cache_expiration_time = config.get_cache_expiration_time()
cache_path = config.get_cache_path()
if cloud:
cache_path = '{0}_{1}'.format(cache_path, cloud)
if not os.path.exists(cache_path):
os.makedirs(cache_path)
cache_file = os.path.join(cache_path, 'ansible-inventory.cache')
@ -208,8 +178,6 @@ def to_json(in_dict):
def parse_args():
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
parser.add_argument('--cloud', default=os.environ.get('OS_CLOUD'),
help='Cloud name (default: None')
parser.add_argument('--private',
action='store_true',
help='Use private address for ansible host')
@ -234,7 +202,6 @@ def main():
refresh=args.refresh,
config_files=config_files,
private=args.private,
cloud=args.cloud,
)
if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
inventory_args.update(dict(
@ -242,14 +209,13 @@ def main():
config_defaults={
'use_hostnames': False,
'expand_hostvars': True,
'fail_on_errors': True,
}
))
inventory = shade.inventory.OpenStackInventory(**inventory_args)
if args.list:
output = get_host_groups(inventory, refresh=args.refresh, cloud=args.cloud)
output = get_host_groups(inventory, refresh=args.refresh)
elif args.host:
output = to_json(inventory.get_host(args.host))
print(output)

View File

@ -29,4 +29,3 @@ clouds:
ansible:
use_hostnames: True
expand_hostvars: False
fail_on_errors: True

View File

@ -26,47 +26,39 @@
# Groups are determined by the description field of openvz guests
# multiple groups can be separated by commas: webserver,dbserver
from subprocess import Popen, PIPE
from subprocess import Popen,PIPE
import sys
import json
# List openvz hosts
vzhosts = ['vzhost1', 'vzhost2', 'vzhost3']
# Add openvz hosts to the inventory and Add "_meta" trick
#List openvz hosts
vzhosts = ['vzhost1','vzhost2','vzhost3']
#Add openvz hosts to the inventory and Add "_meta" trick
inventory = {'vzhosts': {'hosts': vzhosts}, '_meta': {'hostvars': {}}}
# default group, when description not defined
#default group, when description not defined
default_group = ['vzguest']
def get_guests():
# Loop through vzhosts
#Loop through vzhosts
for h in vzhosts:
# SSH to vzhost and get the list of guests in json
pipe = Popen(['ssh', h, 'vzlist', '-j'], stdout=PIPE, universal_newlines=True)
#SSH to vzhost and get the list of guests in json
pipe = Popen(['ssh', h,'vzlist','-j'], stdout=PIPE, universal_newlines=True)
# Load Json info of guests
#Load Json info of guests
json_data = json.loads(pipe.stdout.read())
# loop through guests
#loop through guests
for j in json_data:
# Add information to host vars
inventory['_meta']['hostvars'][j['hostname']] = {
'ctid': j['ctid'],
'veid': j['veid'],
'vpsid': j['vpsid'],
'private_path': j['private'],
'root_path': j['root'],
'ip': j['ip']
}
#Add information to host vars
inventory['_meta']['hostvars'][j['hostname']] = {'ctid': j['ctid'], 'veid': j['veid'], 'vpsid': j['vpsid'], 'private_path': j['private'], 'root_path': j['root'], 'ip': j['ip']}
# determine group from guest description
#determine group from guest description
if j['description'] is not None:
groups = j['description'].split(",")
else:
groups = default_group
# add guest to inventory
#add guest to inventory
for g in groups:
if g not in inventory:
inventory[g] = {'hosts': []}

View File

@ -31,4 +31,3 @@ ovirt_api_secrets =
ovirt_url =
ovirt_username =
ovirt_password =
ovirt_ca_file =

View File

@ -172,9 +172,9 @@ class OVirtInventory(object):
# If the appropriate environment variables are set, they override
# other configuration; process those into our args and kwargs.
kwargs['url'] = os.environ.get('OVIRT_URL', kwargs['url'])
kwargs['username'] = next(val for val in [os.environ.get('OVIRT_EMAIL'), os.environ.get('OVIRT_USERNAME'), kwargs['username']] if val is not None)
kwargs['password'] = next(val for val in [os.environ.get('OVIRT_PASS'), os.environ.get('OVIRT_PASSWORD'), kwargs['password']] if val is not None)
kwargs['url'] = os.environ.get('OVIRT_URL')
kwargs['username'] = os.environ.get('OVIRT_EMAIL')
kwargs['password'] = os.environ.get('OVIRT_PASS')
# Retrieve and return the ovirt driver.
return API(insecure=True, **kwargs)
@ -211,7 +211,7 @@ class OVirtInventory(object):
'ovirt_uuid': inst.get_id(),
'ovirt_id': inst.get_id(),
'ovirt_image': inst.get_os().get_type(),
'ovirt_machine_type': self.get_machine_type(inst),
'ovirt_machine_type': inst.get_instance_type(),
'ovirt_ips': ips,
'ovirt_name': inst.get_name(),
'ovirt_description': inst.get_description(),
@ -230,11 +230,6 @@ class OVirtInventory(object):
"""
return [x.get_name() for x in inst.get_tags().list()]
def get_machine_type(self, inst):
inst_type = inst.get_instance_type()
if inst_type:
return self.driver.instancetypes.get(id=inst_type.id).name
# noinspection PyBroadException,PyUnusedLocal
def get_instance(self, instance_name):
"""Gets details about a specific instance """

View File

@ -1,262 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 Red Hat, Inc.
#
# 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/>.
#
"""
oVirt dynamic inventory script
=================================
Generates dynamic inventory file for oVirt.
Script will return following attributes for each virtual machine:
- id
- name
- host
- cluster
- status
- description
- fqdn
- os_type
- template
- tags
- statistics
- devices
When run in --list mode, virtual machines are grouped by the following categories:
- cluster
- tag
- status
Note: If there is some virtual machine which has has more tags it will be in both tag
records.
Examples:
# Execute update of system on webserver virtual machine:
$ ansible -i contrib/inventory/ovirt4.py webserver -m yum -a "name=* state=latest"
# Get webserver virtual machine information:
$ contrib/inventory/ovirt4.py --host webserver
Author: Ondra Machacek (@machacekondra)
"""
import argparse
import os
import sys
from collections import defaultdict
try:
import ConfigParser as configparser
except ImportError:
import configparser
try:
import json
except ImportError:
import simplejson as json
try:
import ovirtsdk4 as sdk
import ovirtsdk4.types as otypes
except ImportError:
print('oVirt inventory script requires ovirt-engine-sdk-python >= 4.0.0')
sys.exit(1)
def parse_args():
"""
Create command line parser for oVirt dynamic inventory script.
"""
parser = argparse.ArgumentParser(
description='Ansible dynamic inventory script for oVirt.',
)
parser.add_argument(
'--list',
action='store_true',
default=True,
help='Get data of all virtual machines (default: True).',
)
parser.add_argument(
'--host',
help='Get data of virtual machines running on specified host.',
)
parser.add_argument(
'--pretty',
action='store_true',
default=False,
help='Pretty format (default: False).',
)
return parser.parse_args()
def create_connection():
"""
Create a connection to oVirt engine API.
"""
# Get the path of the configuration file, by default use
# 'ovirt.ini' file in script directory:
default_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'ovirt.ini',
)
config_path = os.environ.get('OVIRT_INI_PATH', default_path)
# Create parser and add ovirt section if it doesn't exist:
config = configparser.SafeConfigParser(
defaults={
'ovirt_url': None,
'ovirt_username': None,
'ovirt_password': None,
'ovirt_ca_file': None,
}
)
if not config.has_section('ovirt'):
config.add_section('ovirt')
config.read(config_path)
# Create a connection with options defined in ini file:
return sdk.Connection(
url=config.get('ovirt', 'ovirt_url'),
username=config.get('ovirt', 'ovirt_username'),
password=config.get('ovirt', 'ovirt_password'),
ca_file=config.get('ovirt', 'ovirt_ca_file'),
insecure=config.get('ovirt', 'ovirt_ca_file') is None,
)
def get_dict_of_struct(connection, vm):
"""
Transform SDK Vm Struct type to Python dictionary.
"""
if vm is None:
return dict()
vms_service = connection.system_service().vms_service()
clusters_service = connection.system_service().clusters_service()
vm_service = vms_service.vm_service(vm.id)
devices = vm_service.reported_devices_service().list()
tags = vm_service.tags_service().list()
stats = vm_service.statistics_service().list()
labels = vm_service.affinity_labels_service().list()
groups = clusters_service.cluster_service(
vm.cluster.id
).affinity_groups_service().list()
return {
'id': vm.id,
'name': vm.name,
'host': connection.follow_link(vm.host).name if vm.host else None,
'cluster': connection.follow_link(vm.cluster).name,
'status': str(vm.status),
'description': vm.description,
'fqdn': vm.fqdn,
'os_type': vm.os.type,
'template': connection.follow_link(vm.template).name,
'tags': [tag.name for tag in tags],
'affinity_labels': [label.name for label in labels],
'affinity_groups': [
group.name for group in groups
if vm.name in [vm.name for vm in connection.follow_link(group.vms)]
],
'statistics': dict(
(stat.name, stat.values[0].datum) for stat in stats
),
'devices': dict(
(device.name, [ip.address for ip in device.ips]) for device in devices if device.ips
),
'ansible_host': next((device.ips[0].address for device in devices if device.ips), None)
}
def get_data(connection, vm_name=None):
"""
Obtain data of `vm_name` if specified, otherwise obtain data of all vms.
"""
vms_service = connection.system_service().vms_service()
clusters_service = connection.system_service().clusters_service()
if vm_name:
vm = vms_service.list(search='name=%s' % vm_name) or [None]
data = get_dict_of_struct(
connection=connection,
vm=vm[0],
)
else:
vms = dict()
data = defaultdict(list)
for vm in vms_service.list():
name = vm.name
vm_service = vms_service.vm_service(vm.id)
cluster_service = clusters_service.cluster_service(vm.cluster.id)
# Add vm to vms dict:
vms[name] = get_dict_of_struct(connection, vm)
# Add vm to cluster group:
cluster_name = connection.follow_link(vm.cluster).name
data['cluster_%s' % cluster_name].append(name)
# Add vm to tag group:
tags_service = vm_service.tags_service()
for tag in tags_service.list():
data['tag_%s' % tag.name].append(name)
# Add vm to status group:
data['status_%s' % vm.status].append(name)
# Add vm to affinity group:
for group in cluster_service.affinity_groups_service().list():
if vm.name in [
v.name for v in connection.follow_link(group.vms)
]:
data['affinity_group_%s' % group.name].append(vm.name)
# Add vm to affinity label group:
affinity_labels_service = vm_service.affinity_labels_service()
for label in affinity_labels_service.list():
data['affinity_label_%s' % label.name].append(name)
data["_meta"] = {
'hostvars': vms,
}
return data
def main():
args = parse_args()
connection = create_connection()
print(
json.dumps(
obj=get_data(
connection=connection,
vm_name=args.host,
),
sort_keys=args.pretty,
indent=args.pretty * 2,
)
)
if __name__ == '__main__':
main()

View File

@ -1,53 +0,0 @@
# Ansible Packet.net external inventory script settings
#
[packet]
# Packet projects to get info for. Set this to 'all' to get info for all
# projects in Packet and merge the results together. Alternatively, set
# this to a comma separated list of projects. E.g. 'project-1,project-3,project-4'
projects = all
projects_exclude =
# By default, packet devices in all state are returned. Specify
# packet device states to return as a comma-separated list.
# device_states = active, inactive, queued, provisioning
# items per page to retrieve from packet api at a time
items_per_page = 999
# API calls to Packet are costly. For this reason, we cache the results of an API
# call. Set this to the path you want cache files to be written to. Two files
# will be written to this directory:
# - ansible-packet.cache
# - ansible-packet.index
cache_path = ~/.ansible/tmp
# The number of seconds a cache file is considered valid. After this many
# seconds, a new API call will be made, and the cache file will be updated.
# To disable the cache, set this value to 0
cache_max_age = 300
# Organize groups into a nested/hierarchy instead of a flat namespace.
nested_groups = False
# Replace - tags when creating groups to avoid issues with ansible
replace_dash_in_groups = True
# The packet inventory output can become very large. To manage its size,
# configure which groups should be created.
group_by_device_id = True
group_by_hostname = True
group_by_facility = True
group_by_project = True
group_by_operating_system = True
group_by_plan_type = True
group_by_tags = True
group_by_tag_none = True
# If you only want to include hosts that match a certain regular expression
# pattern_include = staging-*
# If you want to exclude any hosts that match a certain regular expression
# pattern_exclude = staging-*

View File

@ -1,499 +0,0 @@
#!/usr/bin/env python
'''
Packet.net external inventory script
=================================
Generates inventory that Ansible can understand by making API request to
Packet.net using the Packet library.
NOTE: This script assumes Ansible is being executed where the environment
variable needed for Packet API Token already been set:
export PACKET_API_TOKEN=Bfse9F24SFtfs423Gsd3ifGsd43sSdfs
This script also assumes there is a packet_net.ini file alongside it. To specify a
different path to packet_net.ini, define the PACKET_NET_INI_PATH environment variable:
export PACKET_NET_INI_PATH=/path/to/my_packet_net.ini
'''
# (c) 2016, Peter Sankauskas
# (c) 2017, Tomas Karasek
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
import sys
import os
import argparse
import re
from time import time
import six
from six.moves import configparser
try:
import packet
except ImportError as e:
sys.exit("failed=True msg='`packet-python` library required for this script'")
import traceback
try:
import json
except ImportError:
import simplejson as json
ini_section = 'packet'
class PacketInventory(object):
def _empty_inventory(self):
return {"_meta": {"hostvars": {}}}
def __init__(self):
''' Main execution path '''
# Inventory grouped by device IDs, tags, security groups, regions,
# and availability zones
self.inventory = self._empty_inventory()
# Index of hostname (address) to device ID
self.index = {}
# Read settings and parse CLI arguments
self.parse_cli_args()
self.read_settings()
# Cache
if self.args.refresh_cache:
self.do_api_calls_update_cache()
elif not self.is_cache_valid():
self.do_api_calls_update_cache()
# Data to print
if self.args.host:
data_to_print = self.get_host_info()
elif self.args.list:
# Display list of devices for inventory
if self.inventory == self._empty_inventory():
data_to_print = self.get_inventory_from_cache()
else:
data_to_print = self.json_format_dict(self.inventory, True)
print(data_to_print)
def is_cache_valid(self):
''' Determines if the cache files have expired, or if it is still valid '''
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if os.path.isfile(self.cache_path_index):
return True
return False
def read_settings(self):
''' Reads the settings from the packet_net.ini file '''
if six.PY3:
config = configparser.ConfigParser()
else:
config = configparser.SafeConfigParser()
_ini_path_raw = os.environ.get('PACKET_NET_INI_PATH')
if _ini_path_raw:
packet_ini_path = os.path.expanduser(os.path.expandvars(_ini_path_raw))
else:
packet_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'packet_net.ini')
config.read(packet_ini_path)
# items per page
self.items_per_page = 999
if config.has_option(ini_section, 'items_per_page'):
config.get(ini_section, 'items_per_page')
# Instance states to be gathered in inventory. Default is all of them.
packet_valid_device_states = [
'active',
'inactive',
'queued',
'provisioning'
]
self.packet_device_states = []
if config.has_option(ini_section, 'device_states'):
for device_state in config.get(ini_section, 'device_states').split(','):
device_state = device_state.strip()
if device_state not in packet_valid_device_states:
continue
self.packet_device_states.append(device_state)
else:
self.packet_device_states = packet_valid_device_states
# Cache related
cache_dir = os.path.expanduser(config.get(ini_section, 'cache_path'))
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
self.cache_path_cache = cache_dir + "/ansible-packet.cache"
self.cache_path_index = cache_dir + "/ansible-packet.index"
self.cache_max_age = config.getint(ini_section, 'cache_max_age')
# Configure nested groups instead of flat namespace.
if config.has_option(ini_section, 'nested_groups'):
self.nested_groups = config.getboolean(ini_section, 'nested_groups')
else:
self.nested_groups = False
# Replace dash or not in group names
if config.has_option(ini_section, 'replace_dash_in_groups'):
self.replace_dash_in_groups = config.getboolean(ini_section, 'replace_dash_in_groups')
else:
self.replace_dash_in_groups = True
# Configure which groups should be created.
group_by_options = [
'group_by_device_id',
'group_by_hostname',
'group_by_facility',
'group_by_project',
'group_by_operating_system',
'group_by_plan_type',
'group_by_tags',
'group_by_tag_none',
]
for option in group_by_options:
if config.has_option(ini_section, option):
setattr(self, option, config.getboolean(ini_section, option))
else:
setattr(self, option, True)
# Do we need to just include hosts that match a pattern?
try:
pattern_include = config.get(ini_section, 'pattern_include')
if pattern_include and len(pattern_include) > 0:
self.pattern_include = re.compile(pattern_include)
else:
self.pattern_include = None
except configparser.NoOptionError:
self.pattern_include = None
# Do we need to exclude hosts that match a pattern?
try:
pattern_exclude = config.get(ini_section, 'pattern_exclude')
if pattern_exclude and len(pattern_exclude) > 0:
self.pattern_exclude = re.compile(pattern_exclude)
else:
self.pattern_exclude = None
except configparser.NoOptionError:
self.pattern_exclude = None
# Projects
self.projects = []
configProjects = config.get(ini_section, 'projects')
configProjects_exclude = config.get(ini_section, 'projects_exclude')
if (configProjects == 'all'):
for projectInfo in self.get_projects():
if projectInfo.name not in configProjects_exclude:
self.projects.append(projectInfo.name)
else:
self.projects = configProjects.split(",")
def parse_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Packet')
parser.add_argument('--list', action='store_true', default=True,
help='List Devices (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific device')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to Packet (default: False - use cache files)')
self.args = parser.parse_args()
def do_api_calls_update_cache(self):
''' Do API calls to each region, and save data in cache files '''
for projectInfo in self.get_projects():
if projectInfo.name in self.projects:
self.get_devices_by_project(projectInfo)
self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index)
def connect(self):
''' create connection to api server'''
token = os.environ.get('PACKET_API_TOKEN')
if token is None:
raise Exception("Error reading token from environment (PACKET_API_TOKEN)!")
manager = packet.Manager(auth_token=token)
return manager
def get_projects(self):
'''Makes a Packet API call to get the list of projects'''
try:
manager = self.connect()
projects = manager.list_projects()
return projects
except Exception as e:
traceback.print_exc()
self.fail_with_error(e, 'getting Packet projects')
def get_devices_by_project(self, project):
''' Makes an Packet API call to the list of devices in a particular
project '''
params = {
'per_page': self.items_per_page
}
try:
manager = self.connect()
devices = manager.list_devices(project_id=project.id, params=params)
for device in devices:
self.add_device(device, project)
except Exception as e:
traceback.print_exc()
self.fail_with_error(e, 'getting Packet devices')
def fail_with_error(self, err_msg, err_operation=None):
'''log an error to std err for ansible-playbook to consume and exit'''
if err_operation:
err_msg = 'ERROR: "{err_msg}", while: {err_operation}\n'.format(
err_msg=err_msg, err_operation=err_operation)
sys.stderr.write(err_msg)
sys.exit(1)
def get_device(self, device_id):
manager = self.connect()
device = manager.get_device(device_id)
return device
def add_device(self, device, project):
''' Adds a device to the inventory and index, as long as it is
addressable '''
# Only return devices with desired device states
if device.state not in self.packet_device_states:
return
# Select the best destination address
dest = None
for ip_address in device.ip_addresses:
if ip_address['public'] is True and ip_address['address_family'] == 4:
dest = ip_address['address']
if not dest:
# Skip devices we cannot address (e.g. private VPC subnet)
return
# if we only want to include hosts that match a pattern, skip those that don't
if self.pattern_include and not self.pattern_include.match(device.hostname):
return
# if we need to exclude hosts that match a pattern, skip those
if self.pattern_exclude and self.pattern_exclude.match(device.hostname):
return
# Add to index
self.index[dest] = [project.id, device.id]
# Inventory: Group by device ID (always a group of 1)
if self.group_by_device_id:
self.inventory[device.id] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'devices', device.id)
# Inventory: Group by device name (hopefully a group of 1)
if self.group_by_hostname:
self.push(self.inventory, device.hostname, dest)
if self.nested_groups:
self.push_group(self.inventory, 'hostnames', project.name)
# Inventory: Group by project
if self.group_by_project:
self.push(self.inventory, project.name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'projects', project.name)
# Inventory: Group by facility
if self.group_by_facility:
self.push(self.inventory, device.facility['code'], dest)
if self.nested_groups:
if self.group_by_facility:
self.push_group(self.inventory, project.name, device.facility['code'])
# Inventory: Group by OS
if self.group_by_operating_system:
self.push(self.inventory, device.operating_system.slug, dest)
if self.nested_groups:
self.push_group(self.inventory, 'operating_systems', device.operating_system.slug)
# Inventory: Group by plan type
if self.group_by_plan_type:
self.push(self.inventory, device.plan['slug'], dest)
if self.nested_groups:
self.push_group(self.inventory, 'plans', device.plan['slug'])
# Inventory: Group by tag keys
if self.group_by_tags:
for k in device.tags:
key = self.to_safe("tag_" + k)
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
# Global Tag: devices without tags
if self.group_by_tag_none and len(device.tags) == 0:
self.push(self.inventory, 'tag_none', dest)
if self.nested_groups:
self.push_group(self.inventory, 'tags', 'tag_none')
# Global Tag: tag all Packet devices
self.push(self.inventory, 'packet', dest)
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_device(device)
def get_host_info_dict_from_device(self, device):
device_vars = {}
for key in vars(device):
value = getattr(device, key)
key = self.to_safe('packet_' + key)
# Handle complex types
if key == 'packet_state':
device_vars[key] = device.state or ''
elif key == 'packet_hostname':
device_vars[key] = value
elif isinstance(value, (int, bool)):
device_vars[key] = value
elif isinstance(value, six.string_types):
device_vars[key] = value.strip()
elif value is None:
device_vars[key] = ''
elif key == 'packet_facility':
device_vars[key] = value['code']
elif key == 'packet_operating_system':
device_vars[key] = value.slug
elif key == 'packet_plan':
device_vars[key] = value['slug']
elif key == 'packet_tags':
for k in value:
key = self.to_safe('packet_tag_' + k)
device_vars[key] = k
else:
pass
# print key
# print type(value)
# print value
return device_vars
def get_host_info(self):
''' Get variables about a specific host '''
if len(self.index) == 0:
# Need to load index from cache
self.load_index_from_cache()
if self.args.host not in self.index:
# try updating the cache
self.do_api_calls_update_cache()
if self.args.host not in self.index:
# host might not exist anymore
return self.json_format_dict({}, True)
(project_id, device_id) = self.index[self.args.host]
device = self.get_device(device_id)
return self.json_format_dict(self.get_host_info_dict_from_device(device), True)
def push(self, my_dict, key, element):
''' Push an element onto an array that may not have been defined in
the dict '''
group_info = my_dict.setdefault(key, [])
if isinstance(group_info, dict):
host_list = group_info.setdefault('hosts', [])
host_list.append(element)
else:
group_info.append(element)
def push_group(self, my_dict, key, element):
''' Push a group as a child of another group. '''
parent_group = my_dict.setdefault(key, {})
if not isinstance(parent_group, dict):
parent_group = my_dict[key] = {'hosts': parent_group}
child_groups = parent_group.setdefault('children', [])
if element not in child_groups:
child_groups.append(element)
def get_inventory_from_cache(self):
''' Reads the inventory from the cache file and returns it as a JSON
object '''
cache = open(self.cache_path_cache, 'r')
json_inventory = cache.read()
return json_inventory
def load_index_from_cache(self):
''' Reads the index from the cache file sets self.index '''
cache = open(self.cache_path_index, 'r')
json_index = cache.read()
self.index = json.loads(json_index)
def write_to_cache(self, data, filename):
''' Writes data in JSON format to a file '''
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def uncammelize(self, key):
temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower()
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
regex = "[^A-Za-z0-9\_"
if not self.replace_dash_in_groups:
regex += "\-"
return re.sub(regex + "]", "_", word)
def json_format_dict(self, data, pretty=False):
''' Converts a dict to a JSON object and dumps it as a formatted
string '''
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
# Run the script
PacketInventory()

View File

@ -15,16 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Updated 2016 by Matt Harris <matthaeus.harris@gmail.com>
#
# Added support for Proxmox VE 4.x
# Added support for using the Notes field of a VM to define groups and variables:
# A well-formatted JSON object in the Notes field will be added to the _meta
# section for that VM. In addition, the "groups" key of this JSON object may be
# used to specify group membership:
#
# { "groups": ["utility", "databases"], "a": false, "b": true }
import urllib
try:
import json
except ImportError:
@ -34,54 +25,47 @@ import sys
from optparse import OptionParser
from six import iteritems
from six.moves.urllib.parse import urlencode
from ansible.module_utils.urls import open_url
class ProxmoxNodeList(list):
def get_names(self):
return [node['node'] for node in self]
class ProxmoxVM(dict):
class ProxmoxQemu(dict):
def get_variables(self):
variables = {}
for key, value in iteritems(self):
variables['proxmox_' + key] = value
return variables
class ProxmoxVMList(list):
class ProxmoxQemuList(list):
def __init__(self, data=[]):
for item in data:
self.append(ProxmoxVM(item))
self.append(ProxmoxQemu(item))
def get_names(self):
return [vm['name'] for vm in self if vm['template'] != 1]
return [qemu['name'] for qemu in self if qemu['template'] != 1]
def get_by_name(self, name):
results = [vm for vm in self if vm['name'] == name]
results = [qemu for qemu in self if qemu['name'] == name]
return results[0] if len(results) > 0 else None
def get_variables(self):
variables = {}
for vm in self:
variables[vm['name']] = vm.get_variables()
for qemu in self:
variables[qemu['name']] = qemu.get_variables()
return variables
class ProxmoxPoolList(list):
def get_names(self):
return [pool['poolid'] for pool in self]
class ProxmoxPool(dict):
def get_members_name(self):
return [member['name'] for member in self['members'] if member['template'] != 1]
class ProxmoxAPI(object):
def __init__(self, options):
self.options = options
@ -97,7 +81,7 @@ class ProxmoxAPI(object):
def auth(self):
request_path = '{}api2/json/access/ticket'.format(self.options.url)
request_params = urlencode({
request_params = urllib.urlencode({
'username': self.options.username,
'password': self.options.password,
})
@ -121,23 +105,8 @@ class ProxmoxAPI(object):
def nodes(self):
return ProxmoxNodeList(self.get('api2/json/nodes'))
def vms_by_type(self, node, type):
return ProxmoxVMList(self.get('api2/json/nodes/{}/{}'.format(node, type)))
def vm_description_by_type(self, node, vm, type):
return self.get('api2/json/nodes/{}/{}/{}/config'.format(node, type, vm))
def node_qemu(self, node):
return self.vms_by_type(node, 'qemu')
def node_qemu_description(self, node, vm):
return self.vm_description_by_type(node, vm, 'qemu')
def node_lxc(self, node):
return self.vms_by_type(node, 'lxc')
def node_lxc_description(self, node, vm):
return self.vm_description_by_type(node, vm, 'lxc')
return ProxmoxQemuList(self.get('api2/json/nodes/{}/qemu'.format(node)))
def pools(self):
return ProxmoxPoolList(self.get('api2/json/pools'))
@ -145,7 +114,6 @@ class ProxmoxAPI(object):
def pool(self, poolid):
return ProxmoxPool(self.get('api2/json/pools/{}'.format(poolid)))
def main_list(options):
results = {
'all': {
@ -163,40 +131,6 @@ def main_list(options):
qemu_list = proxmox_api.node_qemu(node)
results['all']['hosts'] += qemu_list.get_names()
results['_meta']['hostvars'].update(qemu_list.get_variables())
lxc_list = proxmox_api.node_lxc(node)
results['all']['hosts'] += lxc_list.get_names()
results['_meta']['hostvars'].update(lxc_list.get_variables())
for vm in results['_meta']['hostvars']:
vmid = results['_meta']['hostvars'][vm]['proxmox_vmid']
try:
type = results['_meta']['hostvars'][vm]['proxmox_type']
except KeyError:
type = 'qemu'
try:
description = proxmox_api.vm_description_by_type(node, vmid, type)['description']
except KeyError:
description = None
try:
metadata = json.loads(description)
except TypeError:
metadata = {}
except ValueError:
metadata = {
'notes': description
}
if 'groups' in metadata:
# print metadata
for group in metadata['groups']:
if group not in results:
results[group] = {
'hosts': []
}
results[group]['hosts'] += [vm]
results['_meta']['hostvars'][vm].update(metadata)
# pools
for pool in proxmox_api.pools().get_names():
@ -206,7 +140,6 @@ def main_list(options):
return results
def main_host(options):
proxmox_api = ProxmoxAPI(options)
proxmox_api.auth()
@ -219,7 +152,6 @@ def main_host(options):
return {}
def main():
parser = OptionParser(usage='%prog [options] --list | --host HOSTNAME')
parser.add_option('--list', action="store_true", default=False, dest="list")
@ -244,6 +176,5 @@ def main():
print(json.dumps(data, indent=indent))
if __name__ == '__main__':
main()

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import json
import os
import requests
import argparse
RACKHD_URL = 'http://localhost:8080'
class RackhdInventory(object):
def __init__(self, nodeids):
self._inventory = {}
for nodeid in nodeids:
self._load_inventory_data(nodeid)
inventory = {}
for (nodeid, info) in self._inventory.items():
inventory[nodeid] = (self._format_output(nodeid, info))
print(json.dumps(inventory))
def _load_inventory_data(self, nodeid):
info = {}
info['ohai'] = RACKHD_URL + '/api/common/nodes/{0}/catalogs/ohai'.format(nodeid)
info['lookup'] = RACKHD_URL + '/api/common/lookups/?q={0}'.format(nodeid)
results = {}
for (key, url) in info.items():
r = requests.get(url, verify=False)
results[key] = r.text
self._inventory[nodeid] = results
def _format_output(self, nodeid, info):
try:
node_info = json.loads(info['lookup'])
ipaddress = ''
if len(node_info) > 0:
ipaddress = node_info[0]['ipAddress']
output = {'hosts': [ipaddress], 'vars': {}}
for (key, result) in info.items():
output['vars'][key] = json.loads(result)
output['vars']['ansible_ssh_user'] = 'monorail'
except KeyError:
pass
return output
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--host')
parser.add_argument('--list', action='store_true')
return parser.parse_args()
try:
# check if rackhd url(ie:10.1.1.45:8080) is specified in the environment
RACKHD_URL = 'http://' + str(os.environ['RACKHD_URL'])
except:
# use default values
pass
# Use the nodeid specified in the environment to limit the data returned
# or return data for all available nodes
nodeids = []
if (parse_args().host):
try:
nodeids += parse_args().host.split(',')
RackhdInventory(nodeids)
except:
pass
if (parse_args().list):
try:
url = RACKHD_URL + '/api/common/nodes'
r = requests.get(url, verify=False)
data = json.loads(r.text)
for entry in data:
if entry['type'] == 'compute':
nodeids.append(entry['id'])
RackhdInventory(nodeids)
except:
pass

View File

@ -55,12 +55,3 @@
# will be ignored, and 4 will be used. Accepts a comma separated list,
# the first found wins.
# access_ip_version = 4
# Environment Variable: RAX_CACHE_MAX_AGE
# Default: 600
#
# A configuration the changes the behavior or the inventory cache.
# Inventory listing performed before this value will be returned from
# the cache instead of making a full request for all inventory. Setting
# this value to 0 will force a full request.
# cache_max_age = 600

View File

@ -155,6 +155,8 @@ import ConfigParser
from six import iteritems
from ansible.constants import get_config, mk_boolean
try:
import json
except ImportError:
@ -164,15 +166,13 @@ try:
import pyrax
from pyrax.utils import slugify
except ImportError:
sys.exit('pyrax is required for this module')
print('pyrax is required for this module')
sys.exit(1)
from time import time
from ansible.constants import get_config
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six import text_type
NON_CALLABLES = (text_type, str, bool, dict, int, list, type(None))
NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
def load_config_file():
@ -227,21 +227,12 @@ def _list_into_cache(regions):
prefix = get_config(p, 'rax', 'meta_prefix', 'RAX_META_PREFIX', 'meta')
networks = get_config(p, 'rax', 'access_network', 'RAX_ACCESS_NETWORK',
'public', islist=True)
try:
# Ansible 2.3+
networks = get_config(p, 'rax', 'access_network',
'RAX_ACCESS_NETWORK', 'public', value_type='list')
except TypeError:
# Ansible 2.2.x and below
networks = get_config(p, 'rax', 'access_network',
'RAX_ACCESS_NETWORK', 'public', islist=True)
try:
try:
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
'RAX_ACCESS_IP_VERSION', 4, value_type='list'))
except TypeError:
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
'RAX_ACCESS_IP_VERSION', 4, islist=True))
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
'RAX_ACCESS_IP_VERSION', 4,
islist=True))
except:
ip_versions = [4]
else:
@ -289,7 +280,7 @@ def _list_into_cache(regions):
if not cbs_attachments[region]:
cbs = pyrax.connect_to_cloud_blockstorage(region)
for vol in cbs.list():
if boolean(vol.bootable, strict=False):
if mk_boolean(vol.bootable):
for attachment in vol.attachments:
metadata = vol.volume_image_metadata
server_id = attachment['server_id']
@ -364,12 +355,9 @@ def get_cache_file_path(regions):
def _list(regions, refresh_cache=True):
cache_max_age = int(get_config(p, 'rax', 'cache_max_age',
'RAX_CACHE_MAX_AGE', 600))
if (not os.path.exists(get_cache_file_path(regions)) or
refresh_cache or
(time() - os.stat(get_cache_file_path(regions))[-1]) > cache_max_age):
refresh_cache or
(time() - os.stat(get_cache_file_path(regions))[-1]) > 600):
# Cache file doesn't exist or older than 10m or refresh cache requested
_list_into_cache(regions)
@ -411,9 +399,10 @@ def setup():
if os.path.isfile(default_creds_file):
creds_file = default_creds_file
elif not keyring_username:
sys.exit('No value in environment variable %s and/or no '
'credentials file at %s'
% ('RAX_CREDS_FILE', default_creds_file))
sys.stderr.write('No value in environment variable %s and/or no '
'credentials file at %s\n'
% ('RAX_CREDS_FILE', default_creds_file))
sys.exit(1)
identity_type = pyrax.get_setting('identity_type')
pyrax.set_setting('identity_type', identity_type or 'rackspace')
@ -426,28 +415,23 @@ def setup():
else:
pyrax.set_credential_file(creds_file, region=region)
except Exception as e:
sys.exit("%s: %s" % (e, e.message))
sys.stderr.write("%s: %s\n" % (e, e.message))
sys.exit(1)
regions = []
if region:
regions.append(region)
else:
try:
# Ansible 2.3+
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
value_type='list')
except TypeError:
# Ansible 2.2.x and below
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
islist=True)
region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all',
islist=True)
for region in region_list:
region = region.strip().upper()
if region == 'ALL':
regions = pyrax.regions
break
elif region not in pyrax.regions:
sys.exit('Unsupported region %s' % region)
sys.stderr.write('Unsupported region %s' % region)
sys.exit(1)
elif region not in regions:
regions.append(region)

View File

@ -1 +0,0 @@
ovirt4.py

View File

@ -14,7 +14,7 @@ disable_ssl_certificate_validation = True
# Your Rudder API token, created in the Web interface.
token = aaabbbccc
# Rudder API version to use, use "latest" for latest available
# Rudder API version to use, use "latest" for lastest available
# version.
version = latest
@ -23,7 +23,7 @@ version = latest
group_name = displayName
# Fail if there are two groups with the same name or two hosts with the
# same hostname in the output.
# same hostname in the output.
fail_if_name_collision = True
# We cache the results of Rudder API in a local file

View File

@ -56,8 +56,12 @@ import argparse
import six
import httplib2 as http
from time import time
from ansible.module_utils.six.moves import configparser
from ansible.module_utils.six.moves.urllib.parse import urlparse
from six.moves import configparser
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
try:
import json

View File

@ -3,7 +3,7 @@
SoftLayer external inventory script.
The SoftLayer Python API client is required. Use `pip install softlayer` to install it.
You have a few different options for configuring your username and api_key. You can pass
You have a few different options for configuring your username and api_key. You can pass
environment variables (SL_USERNAME and SL_API_KEY). You can also write INI file to
~/.softlayer or /etc/softlayer.conf. For more information see the SL API at:
- https://softlayer-python.readthedocs.org/en/latest/config_file.html
@ -35,44 +35,14 @@ via the command `sl config setup`.
import SoftLayer
import re
import argparse
import itertools
try:
import json
except:
import simplejson as json
class SoftLayerInventory(object):
common_items = [
'id',
'globalIdentifier',
'hostname',
'domain',
'fullyQualifiedDomainName',
'primaryBackendIpAddress',
'primaryIpAddress',
'datacenter',
'tagReferences.tag.name',
'userData.value',
]
vs_items = [
'lastKnownPowerState.name',
'powerState',
'maxCpu',
'maxMemory',
'activeTransaction.transactionStatus[friendlyName,name]',
'status',
]
hw_items = [
'hardwareStatusId',
'processorPhysicalCoreAmount',
'memoryCapacity',
]
def _empty_inventory(self):
return {"_meta": {"hostvars": {}}}
return {"_meta" : {"hostvars" : {}}}
def __init__(self):
'''Main path'''
@ -97,7 +67,7 @@ class SoftLayerInventory(object):
'''Push an element onto an array that may not have been defined in the dict'''
if key in my_dict:
my_dict[key].append(element)
my_dict[key].append(element);
else:
my_dict[key] = [element]
@ -106,9 +76,9 @@ class SoftLayerInventory(object):
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer')
parser.add_argument('--list', action='store_true', default=False,
help='List instances (default: False)')
help='List instances (default: False)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific instance')
help='Get all the variables about a specific instance')
self.args = parser.parse_args()
def json_format_dict(self, data, pretty=False):
@ -138,8 +108,6 @@ class SoftLayerInventory(object):
if 'primaryIpAddress' not in instance:
return
instance['userData'] = instance['userData'][0]['value'] if instance['userData'] else ''
dest = instance['primaryIpAddress']
self.inventory["_meta"]["hostvars"][dest] = instance
@ -171,15 +139,10 @@ class SoftLayerInventory(object):
# Inventory: group by type (hardware/virtual)
self.push(self.inventory, instance_type, dest)
# Inventory: group by tag
for tag in instance['tagReferences']:
self.push(self.inventory, tag['tag']['name'], dest)
def get_virtual_servers(self):
'''Get all the CCI instances'''
vs = SoftLayer.VSManager(self.client)
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.vs_items))
instances = vs.list_instances(mask=mask)
instances = vs.list_instances()
for instance in instances:
self.process_instance(instance)
@ -187,8 +150,7 @@ class SoftLayerInventory(object):
def get_physical_servers(self):
'''Get all the hardware instances'''
hw = SoftLayer.HardwareManager(self.client)
mask = "mask[%s]" % ','.join(itertools.chain(self.common_items, self.hw_items))
instances = hw.list_hardware(mask=mask)
instances = hw.list_hardware()
for instance in instances:
self.process_instance(instance, 'hardware')

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/bin/env python
"""
Spacewalk external inventory script
@ -18,10 +18,10 @@ output of "spacewalk-report system-groups-systems|inventory".
Tested with Ansible 1.9.2 and spacewalk 2.3
"""
#
#
# Author:: Jon Miller <jonEbird@gmail.com>
# Copyright:: Copyright (c) 2013, Jon Miller
#
#
# Extended for support of multiple organizations and
# adding the "_meta" dictionary to --list output by
# Bernhard Lichtinger <bernhard.lichtinger@lrz.de> 2015
@ -30,15 +30,15 @@ Tested with Ansible 1.9.2 and spacewalk 2.3
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or (at
# your option) any later version.
#
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
from __future__ import print_function
@ -56,16 +56,13 @@ try:
except:
import simplejson as json
base_dir = os.path.dirname(os.path.realpath(__file__))
default_ini_file = os.path.join(base_dir, "spacewalk.ini")
base_dir = os.path.dirname(os.path.realpath(__file__))
SW_REPORT = '/usr/bin/spacewalk-report'
CACHE_DIR = os.path.join(base_dir, ".spacewalk_reports")
CACHE_AGE = 300 # 5min
INI_FILE = os.path.expanduser(os.path.expandvars(os.environ.get("SPACEWALK_INI_PATH", default_ini_file)))
CACHE_AGE = 300 # 5min
INI_FILE = os.path.join(base_dir, "spacewalk.ini")
# Sanity check
# Sanity check
if not os.path.exists(SW_REPORT):
print('Error: %s is required for operation.' % (SW_REPORT), file=sys.stderr)
sys.exit(1)
@ -73,11 +70,10 @@ if not os.path.exists(SW_REPORT):
# Pre-startup work
if not os.path.exists(CACHE_DIR):
os.mkdir(CACHE_DIR)
os.chmod(CACHE_DIR, 0o2775)
os.chmod(CACHE_DIR, 2775)
# Helper functions
# ------------------------------
#------------------------------
def spacewalk_report(name):
"""Yield a dictionary form of each CSV output produced by the specified
@ -95,7 +91,7 @@ def spacewalk_report(name):
lines = open(cache_filename, 'r').readlines()
keys = lines[0].strip().split(',')
# add 'spacewalk_' prefix to the keys
keys = ['spacewalk_' + key for key in keys]
keys = [ 'spacewalk_' + key for key in keys ]
for line in lines[1:]:
values = line.strip().split(',')
if len(keys) == len(values):
@ -103,7 +99,7 @@ def spacewalk_report(name):
# Options
# ------------------------------
#------------------------------
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
parser.add_option('--list', default=False, dest="list", action="store_true",
@ -113,48 +109,48 @@ parser.add_option('--host', default=None, dest="host",
parser.add_option('-H', '--human', dest="human",
default=False, action="store_true",
help="Produce a friendlier version of either server list or host detail")
parser.add_option('-o', '--org', default=None, dest="org_number",
help="Limit to spacewalk organization number")
parser.add_option('-o', '--org', default=None, dest="org_number",
help="Limit to spacewalk organization number")
parser.add_option('-p', default=False, dest="prefix_org_name", action="store_true",
help="Prefix the group name with the organization number")
help="Prefix the group name with the organization number")
(options, args) = parser.parse_args()
# read spacewalk.ini if present
# ------------------------------
#------------------------------
if os.path.exists(INI_FILE):
config = ConfigParser.SafeConfigParser()
config.read(INI_FILE)
if config.has_option('spacewalk', 'cache_age'):
CACHE_AGE = config.get('spacewalk', 'cache_age')
if not options.org_number and config.has_option('spacewalk', 'org_number'):
options.org_number = config.get('spacewalk', 'org_number')
if not options.prefix_org_name and config.has_option('spacewalk', 'prefix_org_name'):
options.prefix_org_name = config.getboolean('spacewalk', 'prefix_org_name')
if config.has_option('spacewalk' , 'cache_age'):
CACHE_AGE = config.get('spacewalk' , 'cache_age')
if not options.org_number and config.has_option('spacewalk' , 'org_number'):
options.org_number = config.get('spacewalk' , 'org_number')
if not options.prefix_org_name and config.has_option('spacewalk' , 'prefix_org_name'):
options.prefix_org_name = config.getboolean('spacewalk' , 'prefix_org_name')
# Generate dictionary for mapping group_id to org_id
# ------------------------------
#------------------------------
org_groups = {}
try:
for group in spacewalk_report('system-groups'):
org_groups[group['spacewalk_group_id']] = group['spacewalk_org_id']
for group in spacewalk_report('system-groups'):
org_groups[group['spacewalk_group_id']] = group['spacewalk_org_id']
except (OSError) as e:
print('Problem executing the command "%s system-groups": %s' %
print('Problem executing the command "%s system-groups": %s' %
(SW_REPORT, str(e)), file=sys.stderr)
sys.exit(2)
sys.exit(2)
# List out the known server from Spacewalk
# ------------------------------
#------------------------------
if options.list:
# to build the "_meta"-Group with hostvars first create dictionary for later use
host_vars = {}
try:
for item in spacewalk_report('inventory'):
host_vars[item['spacewalk_profile_name']] = dict((key, (value.split(';') if ';' in value else value)) for key, value in item.items())
host_vars[ item['spacewalk_profile_name'] ] = dict( ( key, ( value.split(';') if ';' in value else value) ) for key, value in item.items() )
except (OSError) as e:
print('Problem executing the command "%s inventory": %s' %
@ -162,11 +158,11 @@ if options.list:
sys.exit(2)
groups = {}
meta = {"hostvars": {}}
meta = { "hostvars" : {} }
try:
for system in spacewalk_report('system-groups-systems'):
# first get org_id of system
org_id = org_groups[system['spacewalk_group_id']]
org_id = org_groups[ system['spacewalk_group_id'] ]
# shall we add the org_id as prefix to the group name:
if options.prefix_org_name:
@ -182,16 +178,16 @@ if options.list:
groups[group_name] = set()
groups[group_name].add(system['spacewalk_server_name'])
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]:
meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']]
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta[ "hostvars" ]:
meta[ "hostvars" ][ system['spacewalk_server_name'] ] = host_vars[ system['spacewalk_server_name'] ]
# or we list all groups and systems:
else:
if group_name not in groups:
groups[group_name] = set()
groups[group_name].add(system['spacewalk_server_name'])
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta["hostvars"]:
meta["hostvars"][system['spacewalk_server_name']] = host_vars[system['spacewalk_server_name']]
if system['spacewalk_server_name'] in host_vars and not system['spacewalk_server_name'] in meta[ "hostvars" ]:
meta[ "hostvars" ][ system['spacewalk_server_name'] ] = host_vars[ system['spacewalk_server_name'] ]
except (OSError) as e:
print('Problem executing the command "%s system-groups-systems": %s' %
@ -202,15 +198,15 @@ if options.list:
for group, systems in iteritems(groups):
print('[%s]\n%s\n' % (group, '\n'.join(systems)))
else:
final = dict([(k, list(s)) for k, s in iteritems(groups)])
final = dict( [ (k, list(s)) for k, s in iteritems(groups) ] )
final["_meta"] = meta
print(json.dumps(final))
# print(json.dumps(groups))
print(json.dumps( final ))
#print(json.dumps(groups))
sys.exit(0)
# Return a details information concerning the spacewalk server
# ------------------------------
#------------------------------
elif options.host:
host_details = {}
@ -224,13 +220,13 @@ elif options.host:
print('Problem executing the command "%s inventory": %s' %
(SW_REPORT, str(e)), file=sys.stderr)
sys.exit(2)
if options.human:
print('Host: %s' % options.host)
for k, v in iteritems(host_details):
print(' %s: %s' % (k, '\n '.join(v.split(';'))))
else:
print(json.dumps(dict((key, (value.split(';') if ';' in value else value)) for key, value in host_details.items())))
print( json.dumps( dict( ( key, ( value.split(';') if ';' in value else value) ) for key, value in host_details.items() ) ) )
sys.exit(0)
else:

View File

@ -43,24 +43,21 @@
import argparse
import os.path
import sys
from collections import MutableSequence
import paramiko
try:
import json
except ImportError:
import simplejson as json
import paramiko
SSH_CONF = '~/.ssh/config'
_key = 'ssh_config'
_ssh_to_ansible = [('user', 'ansible_ssh_user'),
('hostname', 'ansible_ssh_host'),
('identityfile', 'ansible_ssh_private_key_file'),
('port', 'ansible_ssh_port')]
('hostname', 'ansible_ssh_host'),
('identityfile', 'ansible_ssh_private_key_file'),
('port', 'ansible_ssh_port')]
def get_config():
@ -71,7 +68,7 @@ def get_config():
cfg.parse(f)
ret_dict = {}
for d in cfg._config:
if isinstance(d['host'], MutableSequence):
if type(d['host']) is list:
alias = d['host'][0]
else:
alias = d['host']
@ -96,7 +93,7 @@ def print_list():
# If the attribute is a list, just take the first element.
# Private key is returned in a list for some reason.
attr = attributes[ssh_opt]
if isinstance(attr, MutableSequence):
if type(attr) is list:
attr = attr[0]
tmp_dict[ans_opt] = attr
if tmp_dict:
@ -112,7 +109,7 @@ def print_host(host):
def get_args(args_list):
parser = argparse.ArgumentParser(
description='ansible inventory script parsing .ssh/config')
description='ansible inventory script parsing .ssh/config')
mutex_group = parser.add_mutually_exclusive_group(required=True)
help_list = 'list all hosts from .ssh/config inventory'
mutex_group.add_argument('--list', action='store_true', help=help_list)

View File

@ -1,191 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2016, Hugh Ma <hugh.ma@flextronics.com>
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
# Stacki inventory script
# Configure stacki.yml with proper auth information and place in the following:
# - ../inventory/stacki.yml
# - /etc/stacki/stacki.yml
# - /etc/ansible/stacki.yml
# The stacki.yml file can contain entries for authentication information
# regarding the Stacki front-end node.
#
# use_hostnames uses hostname rather than interface ip as connection
#
#
"""
Example Usage:
List Stacki Nodes
$ ./stack.py --list
Example Configuration:
---
stacki:
auth:
stacki_user: admin
stacki_password: abc12345678910
stacki_endpoint: http://192.168.200.50/stack
use_hostnames: false
"""
import argparse
import os
import sys
import yaml
from distutils.version import StrictVersion
try:
import json
except:
import simplejson as json
try:
import requests
except:
sys.exit('requests package is required for this inventory script')
CONFIG_FILES = ['/etc/stacki/stacki.yml', '/etc/ansible/stacki.yml']
def stack_auth(params):
endpoint = params['stacki_endpoint']
auth_creds = {'USERNAME': params['stacki_user'],
'PASSWORD': params['stacki_password']}
client = requests.session()
client.get(endpoint)
init_csrf = client.cookies['csrftoken']
header = {'csrftoken': init_csrf, 'X-CSRFToken': init_csrf,
'Content-type': 'application/x-www-form-urlencoded'}
login_endpoint = endpoint + "/login"
login_req = client.post(login_endpoint, data=auth_creds, headers=header)
csrftoken = login_req.cookies['csrftoken']
sessionid = login_req.cookies['sessionid']
auth_creds.update(CSRFTOKEN=csrftoken, SESSIONID=sessionid)
return client, auth_creds
def stack_build_header(auth_creds):
header = {'csrftoken': auth_creds['CSRFTOKEN'],
'X-CSRFToken': auth_creds['CSRFTOKEN'],
'sessionid': auth_creds['SESSIONID'],
'Content-type': 'application/json'}
return header
def stack_host_list(endpoint, header, client):
stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host"}),
headers=header)
return json.loads(stack_r.json())
def stack_net_list(endpoint, header, client):
stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host interface"}),
headers=header)
return json.loads(stack_r.json())
def format_meta(hostdata, intfdata, config):
use_hostnames = config['use_hostnames']
meta = dict(all=dict(hosts=list()),
frontends=dict(hosts=list()),
backends=dict(hosts=list()),
_meta=dict(hostvars=dict()))
# Iterate through list of dicts of hosts and remove
# environment key as it causes conflicts
for host in hostdata:
del host['environment']
meta['_meta']['hostvars'][host['host']] = host
meta['_meta']['hostvars'][host['host']]['interfaces'] = list()
# @bbyhuy to improve readability in next iteration
for intf in intfdata:
if intf['host'] in meta['_meta']['hostvars']:
meta['_meta']['hostvars'][intf['host']]['interfaces'].append(intf)
if intf['default'] is True:
meta['_meta']['hostvars'][intf['host']]['ansible_host'] = intf['ip']
if not use_hostnames:
meta['all']['hosts'].append(intf['ip'])
if meta['_meta']['hostvars'][intf['host']]['appliance'] != 'frontend':
meta['backends']['hosts'].append(intf['ip'])
else:
meta['frontends']['hosts'].append(intf['ip'])
else:
meta['all']['hosts'].append(intf['host'])
if meta['_meta']['hostvars'][intf['host']]['appliance'] != 'frontend':
meta['backends']['hosts'].append(intf['host'])
else:
meta['frontends']['hosts'].append(intf['host'])
return meta
def parse_args():
parser = argparse.ArgumentParser(description='Stacki Inventory Module')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--list', action='store_true',
help='List active hosts')
group.add_argument('--host', help='List details about the specific host')
return parser.parse_args()
def main():
args = parse_args()
if StrictVersion(requests.__version__) < StrictVersion("2.4.3"):
sys.exit('requests>=2.4.3 is required for this inventory script')
try:
config_files = CONFIG_FILES
config_files.append(os.path.dirname(os.path.realpath(__file__)) + '/stacki.yml')
config = None
for cfg_file in config_files:
if os.path.isfile(cfg_file):
stream = open(cfg_file, 'r')
config = yaml.safe_load(stream)
break
if not config:
sys.stderr.write("No config file found at {}\n".format(config_files))
sys.exit(1)
client, auth_creds = stack_auth(config['stacki']['auth'])
header = stack_build_header(auth_creds)
host_list = stack_host_list(config['stacki']['auth']['stacki_endpoint'], header, client)
intf_list = stack_net_list(config['stacki']['auth']['stacki_endpoint'], header, client)
final_meta = format_meta(host_list, intf_list, config)
print(json.dumps(final_meta, indent=4))
except Exception as e:
sys.stderr.write('%s\n' % e.message)
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()

View File

@ -1,7 +0,0 @@
---
stacki:
auth:
stacki_user: admin
stacki_password: GhYgWut1hfGbbnstmbW3m-bJbeME-3EvC20rF1LHrDM
stacki_endpoint: http://192.168.200.50/stack
use_hostnames: false

View File

@ -69,7 +69,7 @@ parser.add_option('--host', default=None, dest="host",
# get all the ssh configs for all boxes in an array of dictionaries.
def get_ssh_config():
return dict((k, get_a_ssh_config(k)) for k in list_running_boxes())
return {k: get_a_ssh_config(k) for k in list_running_boxes()}
# list all the running boxes
@ -102,7 +102,7 @@ def get_a_ssh_config(box_name):
if os.path.isfile(id):
host_config['identityfile'] = id
return dict((v, host_config[k]) for k, v in _ssh_to_ansible)
return {v: host_config[k] for k, v in _ssh_to_ansible}
# List out servers that vagrant has running
# ------------------------------

View File

@ -16,21 +16,20 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import sys
from subprocess import Popen, PIPE
from subprocess import Popen,PIPE
try:
import json
except ImportError:
import simplejson as json
class SetEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
VBOX = "VBoxManage"
VBOX="VBoxManage"
def get_hosts(host=None):
@ -40,7 +39,7 @@ def get_hosts(host=None):
if host:
p = Popen([VBOX, 'showvminfo', host], stdout=PIPE)
else:
returned = {'all': set(), '_metadata': {}}
returned = { 'all': set(), '_metadata': {} }
p = Popen([VBOX, 'list', '-l', 'vms'], stdout=PIPE)
except:
sys.exit(1)
@ -51,7 +50,7 @@ def get_hosts(host=None):
for line in p.stdout.readlines():
try:
k, v = line.split(':', 1)
k,v = line.split(':',1)
except:
continue
@ -63,11 +62,11 @@ def get_hosts(host=None):
if v not in hostvars:
curname = v
hostvars[curname] = {}
try: # try to get network info
x = Popen([VBOX, 'guestproperty', 'get', curname, "/VirtualBox/GuestInfo/Net/0/V4/IP"], stdout=PIPE)
try: # try to get network info
x = Popen([VBOX, 'guestproperty', 'get', curname,"/VirtualBox/GuestInfo/Net/0/V4/IP"],stdout=PIPE)
ipinfo = x.stdout.read()
if 'Value' in ipinfo:
a, ip = ipinfo.split(':', 1)
a,ip = ipinfo.split(':',1)
hostvars[curname]['ansible_ssh_host'] = ip.strip()
except:
pass
@ -84,11 +83,11 @@ def get_hosts(host=None):
returned['all'].add(curname)
continue
pref_k = 'vbox_' + k.strip().replace(' ', '_')
pref_k = 'vbox_' + k.strip().replace(' ','_')
if k.startswith(' '):
if prevkey not in hostvars[curname]:
hostvars[curname][prevkey] = {}
hostvars[curname][prevkey][pref_k] = v
hostvars[curname][prevkey][pref_k]= v
else:
if v != '':
hostvars[curname][pref_k] = v

View File

@ -27,10 +27,6 @@ guests_only = True
# not be returned.
# prefix_filter = test_
# Specify a cluster filter list (colon delimited). Only clusters matching by
# name will be scanned for virtualmachines
#clusters = cluster1,cluster2
[auth]
# Specify hostname or IP address of vCenter/ESXi server. A port may be
@ -45,6 +41,3 @@ user = ihasaccess
# Specify a password to access the vCenter host. This setting may also be
# defined with the VMWARE_PASSWORD environment variable.
password = ssshverysecret
# Force SSL certificate checking by default or ignore self-signed certs.
#sslcheck=True

View File

@ -35,40 +35,37 @@ import json
import logging
import optparse
import os
import ssl
import sys
import time
import ConfigParser
from six import integer_types, text_type, string_types
from six.moves import configparser
from six import text_type
# Disable logging message trigged by pSphere/suds.
try:
from logging import NullHandler
except ImportError:
from logging import Handler
class NullHandler(Handler):
def emit(self, record):
pass
logging.getLogger('psphere').addHandler(NullHandler())
logging.getLogger('suds').addHandler(NullHandler())
from psphere.client import Client
from psphere.errors import ObjectNotFoundError
from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network, ClusterComputeResource
from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network
from suds.sudsobject import Object as SudsObject
class VMwareInventory(object):
def __init__(self, guests_only=None):
self.config = configparser.SafeConfigParser()
self.config = ConfigParser.SafeConfigParser()
if os.environ.get('VMWARE_INI', ''):
config_files = [os.environ['VMWARE_INI']]
else:
config_files = [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']
config_files = [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']
for config_file in config_files:
if os.path.exists(config_file):
self.config.read(config_file)
@ -93,28 +90,6 @@ class VMwareInventory(object):
auth_password = os.environ.get('VMWARE_PASSWORD')
if not auth_password and self.config.has_option('auth', 'password'):
auth_password = self.config.get('auth', 'password')
sslcheck = os.environ.get('VMWARE_SSLCHECK')
if not sslcheck and self.config.has_option('auth', 'sslcheck'):
sslcheck = self.config.get('auth', 'sslcheck')
if not sslcheck:
sslcheck = True
else:
if sslcheck.lower() in ['no', 'false']:
sslcheck = False
else:
sslcheck = True
# Limit the clusters being scanned
self.filter_clusters = os.environ.get('VMWARE_CLUSTERS')
if not self.filter_clusters and self.config.has_option('defaults', 'clusters'):
self.filter_clusters = self.config.get('defaults', 'clusters')
if self.filter_clusters:
self.filter_clusters = [x.strip() for x in self.filter_clusters.split(',') if x.strip()]
# Override certificate checks
if not sslcheck:
if hasattr(ssl, '_create_unverified_context'):
ssl._create_default_https_context = ssl._create_unverified_context
# Create the VMware client connection.
self.client = Client(auth_host, auth_user, auth_password)
@ -162,7 +137,7 @@ class VMwareInventory(object):
if isinstance(v, collections.MutableMapping):
items.extend(self._flatten_dict(v, new_key, sep).items())
elif isinstance(v, (list, tuple)):
if all([isinstance(x, string_types) for x in v]):
if all([isinstance(x, basestring) for x in v]):
items.append((new_key, v))
else:
items.append((new_key, v))
@ -210,7 +185,7 @@ class VMwareInventory(object):
if obj_info != ():
l.append(obj_info)
return l
elif isinstance(obj, (type(None), bool, float) + string_types + integer_types):
elif isinstance(obj, (type(None), bool, int, long, float, basestring)):
return obj
else:
return ()
@ -339,19 +314,8 @@ class VMwareInventory(object):
else:
prefix_filter = None
if self.filter_clusters:
# Loop through clusters and find hosts:
hosts = []
for cluster in ClusterComputeResource.all(self.client):
if cluster.name in self.filter_clusters:
for host in cluster.host:
hosts.append(host)
else:
# Get list of all physical hosts
hosts = HostSystem.all(self.client)
# Loop through physical hosts:
for host in hosts:
for host in HostSystem.all(self.client):
if not self.guests_only:
self._add_host(inv, 'all', host.name)
@ -364,7 +328,7 @@ class VMwareInventory(object):
# Loop through all VMs on physical host.
for vm in host.vm:
if prefix_filter:
if vm.name.startswith(prefix_filter):
if vm.name.startswith( prefix_filter ):
continue
self._add_host(inv, 'all', vm.name)
self._add_host(inv, vm_group, vm.name)

View File

@ -1,113 +0,0 @@
# Ansible VMware external inventory script settings
[vmware]
# The resolvable hostname or ip address of the vsphere
server=vcenter
# The port for the vsphere API
#port=443
# The username with access to the vsphere API. This setting
# may also be defined via the VMWARE_USERNAME environment variable.
username=administrator@vsphere.local
# The password for the vsphere API. This setting
# may also be defined via the VMWARE_PASSWORD environment variable.
password=vmware
# Verify the server's SSL certificate
#validate_certs = True
# Specify the number of seconds to use the inventory cache before it is
# considered stale. If not defined, defaults to 0 seconds.
#cache_max_age = 3600
# Specify the directory used for storing the inventory cache. If not defined,
# caching will be disabled.
#cache_path = ~/.cache/ansible
# Max object level refers to the level of recursion the script will delve into
# the objects returned from pyvomi to find serializable facts. The default
# level of 0 is sufficient for most tasks and will be the most performant.
# Beware that the recursion can exceed python's limit (causing traceback),
# cause sluggish script performance and return huge blobs of facts.
# If you do not know what you are doing, leave this set to 1.
#max_object_level=1
# Lower the keynames for facts to make addressing them easier.
#lower_var_keys=True
# Don't retrieve and process some VMware attribute keys
# Default values permit to sanitize inventory meta and to improve a little bit
# performance by removing non-common group attributes.
#skip_keys = declaredalarmstate,disabledmethod,dynamicproperty,dynamictype,environmentbrowser,managedby,parent,childtype,resourceconfig
# Host alias for objects in the inventory. VMWare allows duplicate VM names
# so they can not be considered unique. Use this setting to alter the alias
# returned for the hosts. Any atributes for the guest can be used to build
# this alias. The default combines the config name and the config uuid and
# expects that the ansible_host will be set by the host_pattern.
#alias_pattern={{ config.name + '_' + config.uuid }}
# Host pattern is the value set for ansible_host and ansible_ssh_host, which
# needs to be a hostname or ipaddress the ansible controlhost can reach.
#host_pattern={{ guest.ipaddress }}
# Host filters are a comma separated list of jinja patterns to remove
# non-matching hosts from the final result.
# EXAMPLES:
# host_filters={{ config.guestid == 'rhel7_64Guest' }}
# host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }}
# host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }}
# The default is only gueststate of 'running'
#host_filters={{ guest.gueststate == "running" }}
# Groupby patterns enable the user to create groups via any possible jinja
# expression. The resulting value will the groupname and the host will be added
# to that group. Be careful to not make expressions that simply return True/False
# because those values will become the literal group name. The patterns can be
# comma delimited to create as many groups as necessary
#groupby_patterns={{ guest.guestid }},{{ 'templates' if config.template else 'guests'}}
# Group by custom fields will use VMware custom fields to generate hostgroups
# based on {{ custom_field_group_prefix }} + field_name + _ + field_value
# Set groupby_custom_field to True will enable this feature
# If custom field value is comma separated, multiple groups are created.
# Warning: This required max_object_level to be set to 2 or greater.
#groupby_custom_field = False
# You can customize prefix used by custom field hostgroups generation here.
# vmware_tag_ prefix is the default and consistent with ec2_tag_
#custom_field_group_prefix = 'vmware_tag_'
# The script attempts to recurse into virtualmachine objects and serialize
# all available data. The serialization is comprehensive but slow. If the
# vcenter environment is large and the desired properties are known, create
# a 'properties' section in this config and make an arbitrary list of
# key=value settings where the value is a path to a specific property. If
# If this feature is enabled, be sure to fetch every property that is used
# in the jinja expressions defined above. For performance tuning, reduce
# the number of properties to the smallest amount possible and limit the
# use of properties that are not direct attributes of vim.VirtualMachine
#[properties]
#prop01=name
#prop02=config.cpuHotAddEnabled
#prop03=config.cpuHotRemoveEnabled
#prop04=config.instanceUuid
#prop05=config.hardware.numCPU
#prop06=config.template
#prop07=config.name
#prop08=guest.hostName
#prop09=guest.ipAddress
#prop10=guest.guestId
#prop11=guest.guestState
#prop12=runtime.maxMemoryUsage

View File

@ -1,771 +0,0 @@
#!/usr/bin/env python
#
# 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/>.
# Requirements
# - pyvmomi >= 6.0.0.2016.4
# TODO:
# * more jq examples
# * optional folder heriarchy
"""
$ jq '._meta.hostvars[].config' data.json | head
{
"alternateguestname": "",
"instanceuuid": "5035a5cd-b8e8-d717-e133-2d383eb0d675",
"memoryhotaddenabled": false,
"guestfullname": "Red Hat Enterprise Linux 7 (64-bit)",
"changeversion": "2016-05-16T18:43:14.977925Z",
"uuid": "4235fc97-5ddb-7a17-193b-9a3ac97dc7b4",
"cpuhotremoveenabled": false,
"vpmcenabled": false,
"firmware": "bios",
"""
from __future__ import print_function
import atexit
import datetime
import getpass
import json
import os
import re
import ssl
import sys
import uuid
from collections import defaultdict
from time import time
import six
from jinja2 import Environment
from six import integer_types, string_types
from six.moves import configparser
try:
import argparse
except ImportError:
sys.exit('Error: This inventory script required "argparse" python module. Please install it or upgrade to python-2.7')
try:
from pyVmomi import vim, vmodl
from pyVim.connect import SmartConnect, Disconnect
except ImportError:
sys.exit("ERROR: This inventory script required 'pyVmomi' Python module, it was not able to load it")
hasvcr = False
try:
import vcr
hasvcr = True
except ImportError:
pass
def regex_match(s, pattern):
'''Custom filter for regex matching'''
reg = re.compile(pattern)
if reg.match(s):
return True
else:
return False
class VMwareMissingHostException(Exception):
pass
class VMWareInventory(object):
__name__ = 'VMWareInventory'
guest_props = False
instances = []
debug = False
load_dumpfile = None
write_dumpfile = None
maxlevel = 1
lowerkeys = True
config = None
cache_max_age = None
cache_path_cache = None
cache_path_index = None
cache_dir = None
server = None
port = None
username = None
password = None
validate_certs = True
host_filters = []
skip_keys = []
groupby_patterns = []
safe_types = [bool, str, float, None] + list(integer_types)
iter_types = [dict, list]
bad_types = ['Array', 'disabledMethod', 'declaredAlarmState']
vimTableMaxDepth = {
"vim.HostSystem": 2,
"vim.VirtualMachine": 2,
}
custom_fields = {}
# use jinja environments to allow for custom filters
env = Environment()
env.filters['regex_match'] = regex_match
# translation table for attributes to fetch for known vim types
vimTable = {
vim.Datastore: ['_moId', 'name'],
vim.ResourcePool: ['_moId', 'name'],
vim.HostSystem: ['_moId', 'name'],
}
@staticmethod
def _empty_inventory():
return {"_meta": {"hostvars": {}}}
def __init__(self, load=True):
self.inventory = VMWareInventory._empty_inventory()
if load:
# Read settings and parse CLI arguments
self.parse_cli_args()
self.read_settings()
# Check the cache
cache_valid = self.is_cache_valid()
# Handle Cache
if self.args.refresh_cache or not cache_valid:
self.do_api_calls_update_cache()
else:
self.debugl('loading inventory from cache')
self.inventory = self.get_inventory_from_cache()
def debugl(self, text):
if self.args.debug:
try:
text = str(text)
except UnicodeEncodeError:
text = text.encode('ascii', 'ignore')
print('%s %s' % (datetime.datetime.now(), text))
def show(self):
# Data to print
self.debugl('dumping results')
data_to_print = None
if self.args.host:
data_to_print = self.get_host_info(self.args.host)
elif self.args.list:
# Display list of instances for inventory
data_to_print = self.inventory
return json.dumps(data_to_print, indent=2)
def is_cache_valid(self):
''' Determines if the cache files have expired, or if it is still valid '''
valid = False
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
valid = True
return valid
def do_api_calls_update_cache(self):
''' Get instances and cache the data '''
self.inventory = self.instances_to_inventory(self.get_instances())
self.write_to_cache(self.inventory)
def write_to_cache(self, data):
''' Dump inventory to json file '''
with open(self.cache_path_cache, 'wb') as f:
f.write(json.dumps(data))
def get_inventory_from_cache(self):
''' Read in jsonified inventory '''
jdata = None
with open(self.cache_path_cache, 'rb') as f:
jdata = f.read()
return json.loads(jdata)
def read_settings(self):
''' Reads the settings from the vmware_inventory.ini file '''
scriptbasename = __file__
scriptbasename = os.path.basename(scriptbasename)
scriptbasename = scriptbasename.replace('.py', '')
defaults = {'vmware': {
'server': '',
'port': 443,
'username': '',
'password': '',
'validate_certs': True,
'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename),
'cache_name': 'ansible-vmware',
'cache_path': '~/.ansible/tmp',
'cache_max_age': 3600,
'max_object_level': 1,
'skip_keys': 'declaredalarmstate,'
'disabledmethod,'
'dynamicproperty,'
'dynamictype,'
'environmentbrowser,'
'managedby,'
'parent,'
'childtype,'
'resourceconfig',
'alias_pattern': '{{ config.name + "_" + config.uuid }}',
'host_pattern': '{{ guest.ipaddress }}',
'host_filters': '{{ guest.gueststate == "running" }}',
'groupby_patterns': '{{ guest.guestid }},{{ "templates" if config.template else "guests"}}',
'lower_var_keys': True,
'custom_field_group_prefix': 'vmware_tag_',
'groupby_custom_field': False}
}
if six.PY3:
config = configparser.ConfigParser()
else:
config = configparser.SafeConfigParser()
# where is the config?
vmware_ini_path = os.environ.get('VMWARE_INI_PATH', defaults['vmware']['ini_path'])
vmware_ini_path = os.path.expanduser(os.path.expandvars(vmware_ini_path))
config.read(vmware_ini_path)
# apply defaults
for k, v in defaults['vmware'].items():
if not config.has_option('vmware', k):
config.set('vmware', k, str(v))
# where is the cache?
self.cache_dir = os.path.expanduser(config.get('vmware', 'cache_path'))
if self.cache_dir and not os.path.exists(self.cache_dir):
os.makedirs(self.cache_dir)
# set the cache filename and max age
cache_name = config.get('vmware', 'cache_name')
self.cache_path_cache = self.cache_dir + "/%s.cache" % cache_name
self.debugl('cache path is %s' % self.cache_path_cache)
self.cache_max_age = int(config.getint('vmware', 'cache_max_age'))
# mark the connection info
self.server = os.environ.get('VMWARE_SERVER', config.get('vmware', 'server'))
self.debugl('server is %s' % self.server)
self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port')))
self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username'))
self.debugl('username is %s' % self.username)
self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password'))
self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs'))
if self.validate_certs in ['no', 'false', 'False', False]:
self.validate_certs = False
self.debugl('cert validation is %s' % self.validate_certs)
# behavior control
self.maxlevel = int(config.get('vmware', 'max_object_level'))
self.debugl('max object level is %s' % self.maxlevel)
self.lowerkeys = config.get('vmware', 'lower_var_keys')
if type(self.lowerkeys) != bool:
if str(self.lowerkeys).lower() in ['yes', 'true', '1']:
self.lowerkeys = True
else:
self.lowerkeys = False
self.debugl('lower keys is %s' % self.lowerkeys)
self.skip_keys = list(config.get('vmware', 'skip_keys').split(','))
self.debugl('skip keys is %s' % self.skip_keys)
self.host_filters = list(config.get('vmware', 'host_filters').split(','))
self.debugl('host filters are %s' % self.host_filters)
self.groupby_patterns = list(config.get('vmware', 'groupby_patterns').split(','))
self.debugl('groupby patterns are %s' % self.groupby_patterns)
# Special feature to disable the brute force serialization of the
# virtulmachine objects. The key name for these properties does not
# matter because the values are just items for a larger list.
if config.has_section('properties'):
self.guest_props = []
for prop in config.items('properties'):
self.guest_props.append(prop[1])
# save the config
self.config = config
def parse_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on PyVmomi')
parser.add_argument('--debug', action='store_true', default=False,
help='show debug info')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to VSphere (default: False - use cache files)')
parser.add_argument('--max-instances', default=None, type=int,
help='maximum number of instances to retrieve')
self.args = parser.parse_args()
def get_instances(self):
''' Get a list of vm instances with pyvmomi '''
kwargs = {'host': self.server,
'user': self.username,
'pwd': self.password,
'port': int(self.port)}
if hasattr(ssl, 'SSLContext') and not self.validate_certs:
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_NONE
kwargs['sslContext'] = context
return self._get_instances(kwargs)
def _get_instances(self, inkwargs):
''' Make API calls '''
instances = []
try:
si = SmartConnect(**inkwargs)
except ssl.SSLError as connection_error:
if '[SSL: CERTIFICATE_VERIFY_FAILED]' in str(connection_error) and self.validate_certs:
sys.exit("Unable to connect to ESXi server due to %s, "
"please specify validate_certs=False and try again" % connection_error)
except Exception as exc:
self.debugl("Unable to connect to ESXi server due to %s" % exc)
sys.exit("Unable to connect to ESXi server due to %s" % exc)
self.debugl('retrieving all instances')
if not si:
sys.exit("Could not connect to the specified host using specified "
"username and password")
atexit.register(Disconnect, si)
content = si.RetrieveContent()
# Create a search container for virtualmachines
self.debugl('creating containerview for virtualmachines')
container = content.rootFolder
viewType = [vim.VirtualMachine]
recursive = True
containerView = content.viewManager.CreateContainerView(container, viewType, recursive)
children = containerView.view
for child in children:
# If requested, limit the total number of instances
if self.args.max_instances:
if len(instances) >= self.args.max_instances:
break
instances.append(child)
self.debugl("%s total instances in container view" % len(instances))
if self.args.host:
instances = [x for x in instances if x.name == self.args.host]
instance_tuples = []
for instance in sorted(instances):
if self.guest_props:
ifacts = self.facts_from_proplist(instance)
else:
ifacts = self.facts_from_vobj(instance)
instance_tuples.append((instance, ifacts))
self.debugl('facts collected for all instances')
try:
cfm = content.customFieldsManager
if cfm is not None and cfm.field:
for f in cfm.field:
if f.managedObjectType == vim.VirtualMachine:
self.custom_fields[f.key] = f.name
self.debugl('%d custom fields collected' % len(self.custom_fields))
except vmodl.RuntimeFault as exc:
self.debugl("Unable to gather custom fields due to %s" % exc.msg)
return instance_tuples
def instances_to_inventory(self, instances):
''' Convert a list of vm objects into a json compliant inventory '''
self.debugl('re-indexing instances based on ini settings')
inventory = VMWareInventory._empty_inventory()
inventory['all'] = {}
inventory['all']['hosts'] = []
for idx, instance in enumerate(instances):
# make a unique id for this object to avoid vmware's
# numerous uuid's which aren't all unique.
thisid = str(uuid.uuid4())
idata = instance[1]
# Put it in the inventory
inventory['all']['hosts'].append(thisid)
inventory['_meta']['hostvars'][thisid] = idata.copy()
inventory['_meta']['hostvars'][thisid]['ansible_uuid'] = thisid
# Make a map of the uuid to the alias the user wants
name_mapping = self.create_template_mapping(
inventory,
self.config.get('vmware', 'alias_pattern')
)
# Make a map of the uuid to the ssh hostname the user wants
host_mapping = self.create_template_mapping(
inventory,
self.config.get('vmware', 'host_pattern')
)
# Reset the inventory keys
for k, v in name_mapping.items():
if not host_mapping or k not in host_mapping:
continue
# set ansible_host (2.x)
try:
inventory['_meta']['hostvars'][k]['ansible_host'] = host_mapping[k]
# 1.9.x backwards compliance
inventory['_meta']['hostvars'][k]['ansible_ssh_host'] = host_mapping[k]
except Exception:
continue
if k == v:
continue
# add new key
inventory['all']['hosts'].append(v)
inventory['_meta']['hostvars'][v] = inventory['_meta']['hostvars'][k]
# cleanup old key
inventory['all']['hosts'].remove(k)
inventory['_meta']['hostvars'].pop(k, None)
self.debugl('pre-filtered hosts:')
for i in inventory['all']['hosts']:
self.debugl(' * %s' % i)
# Apply host filters
for hf in self.host_filters:
if not hf:
continue
self.debugl('filter: %s' % hf)
filter_map = self.create_template_mapping(inventory, hf, dtype='boolean')
for k, v in filter_map.items():
if not v:
# delete this host
inventory['all']['hosts'].remove(k)
inventory['_meta']['hostvars'].pop(k, None)
self.debugl('post-filter hosts:')
for i in inventory['all']['hosts']:
self.debugl(' * %s' % i)
# Create groups
for gbp in self.groupby_patterns:
groupby_map = self.create_template_mapping(inventory, gbp)
for k, v in groupby_map.items():
if v not in inventory:
inventory[v] = {}
inventory[v]['hosts'] = []
if k not in inventory[v]['hosts']:
inventory[v]['hosts'].append(k)
if self.config.get('vmware', 'groupby_custom_field'):
for k, v in inventory['_meta']['hostvars'].items():
if 'customvalue' in v:
for tv in v['customvalue']:
if not isinstance(tv['value'], string_types):
continue
newkey = None
field_name = self.custom_fields[tv['key']] if tv['key'] in self.custom_fields else tv['key']
values = []
keylist = map(lambda x: x.strip(), tv['value'].split(','))
for kl in keylist:
try:
newkey = self.config.get('vmware', 'custom_field_group_prefix') + field_name + '_' + kl
newkey = newkey.strip()
except Exception as e:
self.debugl(e)
values.append(newkey)
for tag in values:
if not tag:
continue
if tag not in inventory:
inventory[tag] = {}
inventory[tag]['hosts'] = []
if k not in inventory[tag]['hosts']:
inventory[tag]['hosts'].append(k)
return inventory
def create_template_mapping(self, inventory, pattern, dtype='string'):
''' Return a hash of uuid to templated string from pattern '''
mapping = {}
for k, v in inventory['_meta']['hostvars'].items():
t = self.env.from_string(pattern)
newkey = None
try:
newkey = t.render(v)
newkey = newkey.strip()
except Exception as e:
self.debugl(e)
if not newkey:
continue
elif dtype == 'integer':
newkey = int(newkey)
elif dtype == 'boolean':
if newkey.lower() == 'false':
newkey = False
elif newkey.lower() == 'true':
newkey = True
elif dtype == 'string':
pass
mapping[k] = newkey
return mapping
def facts_from_proplist(self, vm):
'''Get specific properties instead of serializing everything'''
rdata = {}
for prop in self.guest_props:
self.debugl('getting %s property for %s' % (prop, vm.name))
key = prop
if self.lowerkeys:
key = key.lower()
if '.' not in prop:
# props without periods are direct attributes of the parent
rdata[key] = getattr(vm, prop)
else:
# props with periods are subkeys of parent attributes
parts = prop.split('.')
total = len(parts) - 1
# pointer to the current object
val = None
# pointer to the current result key
lastref = rdata
for idx, x in enumerate(parts):
if isinstance(val, dict):
if x in val:
val = val.get(x)
elif x.lower() in val:
val = val.get(x.lower())
else:
# if the val wasn't set yet, get it from the parent
if not val:
try:
val = getattr(vm, x)
except AttributeError as e:
self.debugl(e)
else:
# in a subkey, get the subprop from the previous attrib
try:
val = getattr(val, x)
except AttributeError as e:
self.debugl(e)
# make sure it serializes
val = self._process_object_types(val)
# lowercase keys if requested
if self.lowerkeys:
x = x.lower()
# change the pointer or set the final value
if idx != total:
if x not in lastref:
lastref[x] = {}
lastref = lastref[x]
else:
lastref[x] = val
return rdata
def facts_from_vobj(self, vobj, level=0):
''' Traverse a VM object and return a json compliant data structure '''
# pyvmomi objects are not yet serializable, but may be one day ...
# https://github.com/vmware/pyvmomi/issues/21
# WARNING:
# Accessing an object attribute will trigger a SOAP call to the remote.
# Increasing the attributes collected or the depth of recursion greatly
# increases runtime duration and potentially memory+network utilization.
if level == 0:
try:
self.debugl("get facts for %s" % vobj.name)
except Exception as e:
self.debugl(e)
rdata = {}
methods = dir(vobj)
methods = [str(x) for x in methods if not x.startswith('_')]
methods = [x for x in methods if x not in self.bad_types]
methods = [x for x in methods if not x.lower() in self.skip_keys]
methods = sorted(methods)
for method in methods:
# Attempt to get the method, skip on fail
try:
methodToCall = getattr(vobj, method)
except Exception as e:
continue
# Skip callable methods
if callable(methodToCall):
continue
if self.lowerkeys:
method = method.lower()
rdata[method] = self._process_object_types(
methodToCall,
thisvm=vobj,
inkey=method,
)
return rdata
def _process_object_types(self, vobj, thisvm=None, inkey='', level=0):
''' Serialize an object '''
rdata = {}
if type(vobj).__name__ in self.vimTableMaxDepth and level >= self.vimTableMaxDepth[type(vobj).__name__]:
return rdata
if vobj is None:
rdata = None
elif type(vobj) in self.vimTable:
rdata = {}
for key in self.vimTable[type(vobj)]:
try:
rdata[key] = getattr(vobj, key)
except Exception as e:
self.debugl(e)
elif issubclass(type(vobj), str) or isinstance(vobj, str):
if vobj.isalnum():
rdata = vobj
else:
rdata = vobj.decode('ascii', 'ignore')
elif issubclass(type(vobj), bool) or isinstance(vobj, bool):
rdata = vobj
elif issubclass(type(vobj), integer_types) or isinstance(vobj, integer_types):
rdata = vobj
elif issubclass(type(vobj), float) or isinstance(vobj, float):
rdata = vobj
elif issubclass(type(vobj), list) or issubclass(type(vobj), tuple):
rdata = []
try:
vobj = sorted(vobj)
except Exception:
pass
for idv, vii in enumerate(vobj):
if level + 1 <= self.maxlevel:
vid = self._process_object_types(
vii,
thisvm=thisvm,
inkey=inkey + '[' + str(idv) + ']',
level=(level + 1)
)
if vid:
rdata.append(vid)
elif issubclass(type(vobj), dict):
pass
elif issubclass(type(vobj), object):
methods = dir(vobj)
methods = [str(x) for x in methods if not x.startswith('_')]
methods = [x for x in methods if x not in self.bad_types]
methods = [x for x in methods if not inkey + '.' + x.lower() in self.skip_keys]
methods = sorted(methods)
for method in methods:
# Attempt to get the method, skip on fail
try:
methodToCall = getattr(vobj, method)
except Exception as e:
continue
if callable(methodToCall):
continue
if self.lowerkeys:
method = method.lower()
if level + 1 <= self.maxlevel:
try:
rdata[method] = self._process_object_types(
methodToCall,
thisvm=thisvm,
inkey=inkey + '.' + method,
level=(level + 1)
)
except vim.fault.NoPermission:
self.debugl("Skipping method %s (NoPermission)" % method)
else:
pass
return rdata
def get_host_info(self, host):
''' Return hostvars for a single host '''
if host in self.inventory['_meta']['hostvars']:
return self.inventory['_meta']['hostvars'][host]
elif self.args.host and self.inventory['_meta']['hostvars']:
match = None
for k, v in self.inventory['_meta']['hostvars'].items():
if self.inventory['_meta']['hostvars'][k]['name'] == self.args.host:
match = k
break
if match:
return self.inventory['_meta']['hostvars'][match]
else:
raise VMwareMissingHostException('%s not found' % host)
else:
raise VMwareMissingHostException('%s not found' % host)
if __name__ == "__main__":
# Run the script
print(VMWareInventory().show())

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